Java - сопоставить список объектов списку со значениями атрибутов их свойств - PullRequest
33 голосов
/ 10 апреля 2009

У меня есть класс ViewValue, определенный следующим образом:

class ViewValue {

private Long id;
private Integer value;
private String description;
private View view;
private Double defaultFeeRate;

// getters and setters for all properties
}

Где-то в моем коде мне нужно преобразовать список экземпляров ViewValue в список, содержащий значения полей id из соответствующего ViewValue.

Я делаю это с помощью цикла foreach:

List<Long> toIdsList(List<ViewValue> viewValues) {

   List<Long> ids = new ArrayList<Long>();

   for (ViewValue viewValue : viewValues) {
      ids.add(viewValue.getId());
   }

   return ids;

}

Есть ли лучший подход к этой проблеме?

Ответы [ 7 ]

33 голосов
/ 30 октября 2009

Вы можете сделать это в одну строку, используя Commons BeanUtils и Collections:
(зачем писать свой собственный код, когда другие сделали это для вас?)

import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.collections.CollectionUtils;

...

List<Long> ids = (List<Long>) CollectionUtils.collect(viewValues, 
                                       new BeanToPropertyValueTransformer("id"));
28 голосов
/ 10 июня 2016

Мы можем сделать это в одной строке кода, используя Java 8

List<Long> ids = viewValues.stream().map(ViewValue::getId).collect(Collectors.toList());

Для получения дополнительной информации: Java 8 - Потоки

28 голосов
/ 16 февраля 2011

Используйте коллекции Google. Пример:

    Function<ViewValue, Long> transform = new Function<ViewValue, Long>() {
        @Override
        public Long apply(ViewValue from) {
            return from.getId();
        }
    };
    List<ViewValue> list = Lists.newArrayList();
    List<Long> idsList = Lists.transform(list, transform);

UPDATE:

В Java 8 вам не нужна гуава. Вы можете:

import com.example.ViewValue;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

Function<ViewValue, Long> transform = ViewValue::getId;
List<ViewValue> source = new ArrayList<>();
List<Long> result = source.stream().map(transform).collect(Collectors.toList());

Или просто:

List<ViewValue> source= new ArrayList<>();
List<Long> result = source.stream().map(ViewValue::getId).collect(Collectors.toList());

СЛЕДУЮЩЕЕ ОБНОВЛЕНИЕ (последнее после смены имени Javaslang на Vavr):

В настоящее время стоит упомянуть о решении с библиотекой Javaslang (http://www.javaslang.io/) библиотекой Vavr (http://www.vavr.io/).) Давайте предположим, что у нас есть список с подлинными объектами:

List<ViewValue> source = newArrayList(new ViewValue(1), new ViewValue(2), new ViewValue(2));

Мы могли бы сделать преобразование с помощью класса List из библиотеки Javaslang (в долгосрочной перспективе собирать не удобно):

List<Long> result = io.vavr.collection.List.ofAll(source).map(ViewValue::getId).toJavaList();

Но вы увидите силу только со списками Javaslang:

io.vavr.collection.List<ViewValue> source = javaslang.collection.List.of(new ViewValue(1), new ViewValue(2), new ViewValue(3));
io.vavr.collection.List<Long> res = source.map(ViewValue::getId);

Я рекомендую взглянуть на доступные коллекции и новые типы в этой библиотеке (мне особенно нравится тип Try). Вы найдете документацию по следующему адресу: http://www.javaslang.io/javaslang-docs/ http://www.vavr.io/vavr-docs/.

PS. Из-за Oracle и слова «Java» в названии им пришлось изменить имя библиотеки с javaslang на другое. Они решили Вавр.

27 голосов
/ 10 апреля 2009

РЕДАКТИРОВАТЬ: Этот ответ основан на идее, что вам нужно сделать аналогичные вещи для разных объектов и различных свойств в другом месте вашего кода. Если вам только необходимо преобразовать список значений ViewValues ​​в список длинных по идентификатору, то придерживайтесь своего исходного кода. Однако если вы хотите более многоразового решения, читайте дальше ...

Я бы объявил интерфейс для проекции, например

public interface Function<Arg,Result>
{
    public Result apply(Arg arg);
}

Затем вы можете написать один общий метод преобразования:

public <Source, Result> List<Result> convertAll(List<Source> source,
    Function<Source, Result> projection)
{
    ArrayList<Result> results = new ArrayList<Result>();
    for (Source element : source)
    {
         results.add(projection.apply(element));
    }
    return results;
}

Тогда вы можете определить простые проекции следующим образом:

private static final Function<ViewValue, Long> ID_PROJECTION =
    new Function<ViewValue, Long>()
    {
        public Long apply(ViewValue x)
        {
            return x.getId();
        }
    };

И примените это так:

List<Long> ids = convertAll(values, ID_PROJECTION);

(Очевидно, что использование связей K & R и более длинных линий делает объявление проекции немного короче:)

Честно говоря, все это будет намного приятнее с лямбда-выражениями, но не берите в голову ...

4 голосов
/ 11 апреля 2009

Я реализовал небольшую функциональную библиотеку для этого варианта использования. Один из методов имеет эту подпись:

<T> List<T> mapToProperty(List<?> objectList, String property, Class<T> returnType)

, который принимает строку и использует отражение для создания вызова свойства, затем возвращает список, поддерживаемый objectList, где get и итератор реализованы с использованием этого вызова свойства.

Функции mapToProperty реализованы в терминах общей функции карты, которая воспринимает функцию как маппер, как описано в другом посте. Очень полезно.

Я предлагаю вам ознакомиться с основами программирования функций и, в частности, взглянуть на Functors (объекты, реализующие функцию карты)

Редактировать: Отражение действительно не должно быть дорогим. JVM значительно улучшилась в этой области. Просто убедитесь, что один раз скомпилировали вызов и использовали его повторно.

Edit2: пример кода

public class MapExample {
    public static interface Function<A,R>
    {
        public R apply(A b);
    }

    public static <A,R> Function<A,R> compilePropertyMapper(Class<A> objectType, String property, Class<R> propertyType)
    {
        try {
            final Method m = objectType.getMethod("get" + property.substring(0,1).toUpperCase() + property.substring(1));

            if(!propertyType.isAssignableFrom(m.getReturnType()))
                throw new IllegalArgumentException(
                    "Property "+property+" on class "+objectType.getSimpleName()+" is not a "+propertyType.getSimpleName()
                );

            return new Function<A,R>() 
            {
                @SuppressWarnings("unchecked")
                public R apply(A b)
                {
                    try {
                        return (R)m.invoke(b);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                };
            };

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T1,T2> List<T2> map(final List<T1> list, final Function<T1,T2> mapper)
    {
        return new AbstractList<T2>()
        {
            @Override
            public T2 get(int index) {
                return mapper.apply(list.get(index));
            }

            @Override
            public int size() {
                return list.size();
            }
        };
    }

    @SuppressWarnings("unchecked")
    public static <T1,T2> List<T2> mapToProperty(List<T1> list, String property, Class<T2> propertyType)
    {
        if(list == null)
            return null;
        else if(list.isEmpty())
            return Collections.emptyList();

        return map(list,compilePropertyMapper((Class<T1>)list.get(0).getClass(), property, propertyType));
    }
}
3 голосов
/ 10 апреля 2009

Вы можете использовать упаковщик:

public class IdList impements List<Long>
{
    private List<ViewValue> underlying;

    pubic IdList(List<ViewValue> underying)
    {
        this.underlying = underying;
    }

    public Long get(int index)
    {
        return underlying.get(index).getId()
    }

    // other List methods
}

хотя это еще более утомительная работа, это может улучшить производительность.

Вы также можете реализовать свое и мое решение в общем, используя рефлексию, но это очень плохо для перфорации.

Нет, нет короткого и простого универсального решения на Java, я боюсь. В Groovy вы бы просто использовали collect (), но я считаю, что это также подразумевает рефлексию.

2 голосов
/ 10 апреля 2009

Это зависит от того, что вы затем делаете с List<Long> и List<ViewValue>

Например, вы можете получить достаточную функциональность, создав собственную реализацию List, которая обернет List<ViewValue>, реализуя iterator() с реализацией итератора, которая перебирает ViewValues ​​и возвращает идентификатор.

...