我可以使这个java pluck()方法更安全吗?

时间:2012-01-26 14:35:43

标签: java generics reflection

我写了这个效用函数:

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);

我的问题是,如果调用者输入的类型错误,则在调用者尝试从列表中获取对象之前不会抛出任何异常。理想情况下,我想从ClassCastException方法本身抛出pluck。但是,我没有看到在运行时访问列表类型的方法。

我可以使用一些技巧来确保调用者最终没有无效列表吗?


编辑:因此,使用我得到的反馈,安全的实施将是:

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似乎做了我想要的,所以我想我会用它。谢谢迈克!

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

8 个答案:

答案 0 :(得分:2)

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

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

这会:

1)强制客户端提供他想要“拔出”的字段类型,这样你就可以在你的方法中进行适当的类型检查。

2)强制客户端提供一个“拔出”的通用列表,这样就可以防止出现另一个错误源(客户端提供包含不同类型对象的列表)。

我认为这样可以安全......

答案 1 :(得分:2)

您可以将签名更改为:

public static <T, F> List<F> pluck(String fieldName, Class<F> fieldType, 
                                           List<T> list, Class<T> listType)

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

答案 2 :(得分:1)

无效列表是什么意思?如果你的意思是他们试图将它转换为某种东西,那么请尝试将声明更改为public static <T> List<T> pluck(String fieldName, List<T> list)

我对However, I don't see a way to access the type of the list on run time.评论感到困惑。但是,如果我理解正确,那么:没有&#34;类型&#34;在运行时,因为Java中的泛型是由&#34; erasure&#34;实现的。这意味着编译器在编译时检查它是否正常工作,然后将其转换为普通的转换,就像我们在泛型之前一样。他们认为这是实现向后和向前兼容的必要条件。

答案 3 :(得分:1)

您应该使用泛型作为type参数,并传入返回类型的类对象:

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来替换它

答案 4 :(得分:1)

您可以尝试使用Google集合库提供的库而不是维护新的集合库:Collections2.transform就是这样

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

答案 5 :(得分: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();
}

现在,您可以检索每个Entity 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();
            }
        });
    }
}

这是最安全的。这满足了正确的OOP原则和Java 5-7。

在Java 8中,您可以使用stream,map和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());
}

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

您可以将列表转换为java.lang.reflect.ParameterizedType并检查getActualTypeArguments()返回的数组是否包含您需要的类。除此之外,你运气不好。