我能否使这个java pluck()方法更加安全?

6
我写了这个实用函数:
public static <T> List<T> pluck(String fieldName, List list) 
        throws NoSuchFieldException, IllegalAccessException {
    if (list.isEmpty()) {
        return new ArrayList<T>();
    }
    Class c = list.get(0).getClass();
    Field f = c.getField(fieldName);
    ArrayList<T> result = Lists.newArrayList();
    for (Object object : list) {
        result.add((T) f.get(object));
    }
    return result;
}

我从underscore.js中借鉴了这个想法。使用情况是:

ArrayList<Person> people = new ArrayList<Person>;
people.add(new Person("Alice", "Applebee"));
people.add(new Person("Bob", "Bedmington"));
people.add(new Person("Charlie", "Chang"));

List<String> firstNames = pluck("firstName", people);

我的问题在于,如果调用者类型错误,直到调用者尝试从列表中获取对象时才会抛出异常。理想情况下,我希望从pluck方法本身抛出ClassCastException。但是,我看不到在运行时访问列表类型的方法。
有什么技巧可以确保调用者不会得到无效的列表吗?

编辑:所以根据我收到的反馈,一个安全的实现方法是:

public static <T,F> List<F> pluck(String fieldName, Class<F> fieldType, 
        List<T> list, Class<T> listType) 
        throws NoSuchFieldException, IllegalAccessException {
    Field f = listType.getField(fieldName);
    ArrayList<F> result = new ArrayList<F>();
    for (T element : list) {
        result.add(fieldType.cast(f.get(element)));
    }
    return result;
}

但实际上,lambdaj似乎已经做到了我想要的,所以我想我会使用它。谢谢Mike!

免责声明:LambdaJ@GoogleCode | @GitHub)-自JDK8发布(JSR 335JEP 126)以来,此项目不再维护。


1
Lambdaj以类型安全的方式实现了这一点,还有更多功能。它是我最喜欢的库之一。 - mike9322
1
list.get(0).getClass() 可能无法获取正确的类。如果 list.get(0) 为空,它将会崩溃。此外,list 的后续元素可能不是 list.get(0) 类的实例。 - newacct
而且,如果您想获取私有类字段,可以这样做: Field f = listType.getDeclaredField(fieldName); f.setAccessible(true); 而不是使用 Field f = listType.getField(fieldName); - Dr Jorge
8个回答

2
为什么不这样定义签名:

为什么不像这样定义签名:

public static <T, U> List<T> pluck(String fieldName, Class<T> fieldType, List<U> list);

这将会:

1)强制客户端提供他想要“pluck”的字段类型,以便您可以在方法中进行适当的类型检查。

2)强制客户端提供一个通用列表来“pluck”,以便防止另一个错误源(客户端提供包含不同类型对象的列表)。

我认为这是最安全的方式。


这样可以防止另一个错误源(客户端提供包含不同类型对象的列表)。实际上,它并不能防止任何事情;按照您编写的方式,它将接受任何列表。 - newacct
@newacct 它只接受包含特定类型对象的列表。因此,您不能传递包含不同类型对象的列表。原始签名将接受非泛型列表,这将增加传递不包含该字段的对象的概率。 - quaylar
@newacct 你可以不传递类型“List”。但是你需要传递一个例如List<Person>或List<Whatever>的列表。 - quaylar

2
您可以将您的签名更改为以下内容:
public static <T, F> List<F> pluck(String fieldName, Class<F> fieldType, 
                                           List<T> list, Class<T> listType)

你有列表类型和字段类型。

1

你应该使用泛型用于类型参数,并传递返回类型的类对象:

public static <TItem, TResult> List<TResult> pluck(String fieldName, List<TItem> list, Class<TResult> resultType) 
        throws NoSuchFieldException, IllegalAccessException {
    if(list.isEmpty()) return new ArrayList<TResult>();

    Class c = list.get(0).getClass();
    Field f = c.getField(fieldName);
    ArrayList<TResult> result = new ArrayList<TResult>();
    for(Object object : list) {
        result.add(resultType.cast(f.get(object)));
    }
    return result;
}

通常情况下,当您收到关于不安全转换为类型参数的警告时,您应该查看是否可以将其替换为对 Class.cast 的调用。

1

什么是无效列表?如果你的意思是它们试图将其强制转换为不适合的类型,那么尝试将声明更改为public static <T> List<T> pluck(String fieldName, List<T> list)

我对“但是,我看不到在运行时访问列表类型的方法。”这一评论感到困惑。然而,如果我理解正确,那么:由于Java中泛型的实现是通过“擦除”,因此在运行时没有“类型”。这意味着编译器在编译时检查它是否有效,然后将其转换为常规的强制转换,就像我们在泛型之前所做的那样。他们认为这是必要的,以实现向前和向后兼容性。


1
你可以尝试使用Google Collections库提供的Collections2.transform而不是维护一个新的库,用法如下:

Collections2.transform

Collection<Y> yourCollection...
...
Collection<X> expected = Collections2.transform(yourCollection, new Function<Y, X>() {
  public X apply(Y element) {
    return element.getX();
  }
}

1
使用Google的Guava集合库,您可以使用Collections2.transform()
用法
给定一个接口/类,例如称为Entity,您的类可以实现/扩展此接口/类。
public abstract class Entity {
    private long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}

public interface Entity {
    long getId();
}

现在您可以检索每个实体的ID列表。
import com.google.common.base.Function;
import com.google.common.collect.Collections2;

public class Main {
    public static void main(String[] args) {
        List<Entity> entities = ...
        List<Long> ids = pluckIds(entities);
    }

    public static <E extends Entity> List<Long> pluckIds(List<E> list) {
        return new ArrayList<Long>(Collections2.transform(list, new Function<E, Long>() {
            public Long apply(E entity) {
                return entity.getId();
            }
        });
    }
}

这是最安全的方法。它符合正确的面向对象编程原则和Java 5-7的要求。
在Java 8中,您可以使用流、映射和lambda表达式实现相同的效果。
public static <E extends Entity> List<Long> pluckIds(List<E> list) {
    return list.stream().map(e -> e.getId()).collect(Collectors.toList());
}

或者

public static <T,F> List<F> pluck(String fieldName, Class<F> fieldType, 
        List<T> list, Class<T> listType) throws NoSuchFieldException,
        IllegalAccessException, IllegalArgumentException {
    Field f = listType.getDeclaredField(fieldName);
    f.setAccessible(true);
    return list.stream().map(e -> {
        try { return fieldType.cast(f.get(e)); } catch (Exception e1) { return null; }
    }).collect(Collectors.toList());
}

0

不确定你在问什么,但你可以尝试:

Class c = list.get(0).getClass();
if (!c.equals(Person.class))
  throw new ClassCastException();

0
你可以将列表转换为 java.lang.reflect.ParameterizedType,并检查由 getActualTypeArguments() 返回的数组是否包含所需的类。除此之外,你就没有其他选择了。

1
只有当他得到的列表是一个子类,例如 StringList extends ArrayList<String> 时,这才能起作用。对于使用 new ArrayList<String>() 得到的列表,类型信息不会被保留。 - millimoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接