Могу ли я сделать этот метод java pluck () более безопасным? - PullRequest
6 голосов
/ 26 января 2012

Я написал эту служебную функцию:

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 ( @ GoogleCode | @ GitHub ) - Этот проект больше не поддерживается с момента выпуска JDK8 ( JSR 335 , JEP 126 ).

Ответы [ 8 ]

2 голосов
/ 26 января 2012

Вы можете изменить свою подпись следующим образом:

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

У вас есть тип списка и тип поля.

2 голосов
/ 26 января 2012

Почему бы вам не определить сигнатуру следующим образом:

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

Это будет:

1) Принудительно указать клиенту тип поля, которое он хочет "собрать", поэтомувы можете выполнить правильную проверку типов в своем методе.

2) Заставить клиента предоставить общий список, из которого нужно «извлечь», поэтому вы предотвращаете другой источник ошибок (клиент, предоставляющий список, содержащий объекты)различных типов).

Я думаю, что это настолько безопасно, насколько это возможно ..

1 голос
/ 18 декабря 2014

В библиотеке коллекций 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.

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 вы можете добиться того же эффекта с помощью потока, карты и лямбды

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());
}
1 голос
/ 12 апреля 2013

вы можете попробовать тот, который указан в библиотеке коллекций Google вместо сохранения нового: Collections2.transform следующим образом

Collection<Y> yourCollection...
...
Collection<X> expected = Collections2.transform(yourCollection, new Function<Y, X>() {
  public X apply(Y element) {
    return element.getX();
  }
}
1 голос
/ 26 января 2012

Вы должны использовать обобщенные значения для параметра типа и передать объект класса возвращаемого типа:

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 голос
/ 26 января 2012

Что вы подразумеваете под недействительным списком? Если вы имеете в виду, что они пытаются привести его к чему-то, чего нет, то попробуйте изменить объявление на 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.. Тем не менее, если я вас правильно понимаю, то во время выполнения «типа» не существует, потому что обобщение в Java реализовано с помощью «стирания». Это означает, что компилятор проверяет во время компиляции, что он работает, а затем превращает его в обычные приведения, как мы делали до генериков. Они чувствовали, что это необходимо для обеспечения обратной и прямой совместимости.

0 голосов
/ 26 января 2012

Вы можете привести список к java.lang.reflect.ParameterizedType и проверить, что массив, возвращаемый getActualTypeArguments(), содержит нужный вам класс. Кроме этого, вам не повезло.

0 голосов
/ 26 января 2012

Не уверен, что вы спрашиваете, но вы можете попробовать:

Class c = list.get(0).getClass();
if (!c.equals(Person.class))
  throw new ClassCastException();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...