Получение списка элементов из ArrayList

Допустим, у меня есть фасоль, как показано ниже.

class Customer{
  private String code;
  private String name;
  private Integer value;
  //getters setters omitted for brevity
}

Затем из метода я получаю List<Customer> обратно. Теперь предположим, что я хочу получить список всех «имен» участников из Списка. Очевидно, я могу пройти и построить List<String> элемента «name» сам.

Однако мне было интересно, есть ли более короткий или более эффективный путь к этой технике, который кто-нибудь знает. Например, если я хочу получить список всех ключей в объекте Map, я выполняю map.keySet (). Что-то в этом роде я пытаюсь выяснить.


person CoolBeans    schedule 24.06.2010    source источник
comment
Я думаю, вы застряли в цикле, или, если вы любите приключения, можете взглянуть на quaere. Взгляните на Что такое эквивалент LINQ в Java? для некоторых связанная информация.   -  person R0MANARMY    schedule 24.06.2010
comment
Я думаю, вы ищете не более эффективный способ, но, возможно, более эффективный. Цикл for по умолчанию уже достаточно эффективен.   -  person OscarRyz    schedule 25.06.2010
comment
Не обязательно с точки зрения эффективности, но я пытался понять, что делают другие для таких рутинных задач, как эта. Я узнал о Guava и Lambdaj - некоторых из популярных альтернатив. Так что хорошо!   -  person CoolBeans    schedule 25.06.2010


Ответы (8)


Гуава имеет _ 1_, которая может преобразовывать List<F> в List<T>, используя предоставленный _ 4_ (точнее, Function<? super F,? extends T>).

Из документации:

public static <F,T>
   List<T> transform(
               List<F> fromList,
               Function<? super F,? extends T> function
           )

Возвращает список, который применяет function к каждому элементу fromList. Возвращенный список представляет собой преобразованный вид fromList; изменения в fromList будут отражены в возвращаемом списке и наоборот.

function применяется лениво, вызывается при необходимости.

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

  • _ 12_Iterable<F> по Iterable<T>)
  • _ 15_Iterator<F> по Iterator<T>)
  • _ 18_Collection<F> по Collection<T>)
  • _ 21_Map<K,V1> по Map<K,V2>)
person polygenelubricants    schedule 24.06.2010
comment
Круто, приятно видеть ссылку на библиотеку, которая делает то, что я предлагал в своем посте. - person Mark Peters; 24.06.2010
comment
@Mark: большая разница в том, что реализация Guava применяет функцию по запросу. Этакое ленивое преобразование, куда как твой рвется. - person polygenelubricants; 24.06.2010
comment
Ах, мне было любопытно, почему они специально использовали итерацию. Это объясняет рассуждение. Теперь мне это нравится даже больше (при условии, что в Guava есть фабричный метод для создания коллекции из итерируемого объекта?) - person Mark Peters; 24.06.2010
comment
@Mark: Я огляделся и нашел Collections2.transform. Но да, там Iterables.addAll от Iterable до Collection. - person polygenelubricants; 24.06.2010
comment
@Mark Peters: Да, Guava может создать любой тип Collection из Iterable с помощью фабричного метода из класса, такого как Lists или copyOf(Iterable) фабричного метода для всех неизменяемых классов коллекций. - person ColinD; 24.06.2010

Похоже, вы ищете Java-эквивалент функции Perl map в Perl. Подобные вещи могут быть добавлены в библиотеку коллекций после того, как (если) Java получит закрытие. А пока я думаю, что это лучшее, что вы можете сделать:

List<String> list = new ArrayList<String>(customers.size());
for ( Customer c : customers ) {
    list.add(c.getName());
}

Вы также можете написать map функцию, которая использует простой интерфейс для предоставления функции сопоставления. Что-то вроде этого:

public interface Transform<I, O> {
    O transform(I in);
}
public <I, O> List<O> map(Collection<I> coll, Transform<? super I, ? extends O> xfrm) {
    List<O> list = new ArrayList<O>(coll.size());
    for ( I in : coll ) {
        list.add(xfrm.transform(in));
    }
    return list;
}
person Mark Peters    schedule 24.06.2010
comment
Функция карты Perl? Хе-хе! Разве вы не имеете в виду Java-эквивалент Perl-эквивалента функции отображения Lisp. - person dsmith; 24.06.2010

можно было бы использовать что-то вроде этого: http://code.google.com/p/lambdaj/

person JoshP    schedule 24.06.2010

Я думаю, что это то, что вам придется закодировать самостоятельно, в цикле.

person froadie    schedule 24.06.2010
comment
Используя LambdaJ, вам просто нужно написать конвертер Customer- ›String - person David Rabinowitz; 24.06.2010
comment
Или еще лучше ;-) Guava's Lists.transform guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/, com.google.common.base.Function) - person Dimitris Andreou; 24.06.2010

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

List<String> customerNames = convert(customerList, new Converter<Customer,String>() {
  public String convert(Customer customer) {
    return customer.getName();
  }
});
person David Rabinowitz    schedule 24.06.2010
comment
Интересный. Вы знаете, работает ли он с jdk 1.5 или только с Java 6? - person CoolBeans; 24.06.2010

Вам нужно использовать цикл, но функция, которую вы ищете, в функциональных языках называется map. В Java можно реализовать map, хотя это, как правило, довольно неэлегантно; вот версия, которую я реализовал много лет назад в моей библиотеке «вещи, которые должны быть в Java, но по какой-то причине нет»:

public interface MapFunction<T, U> {
    public U map(T source);
}

public static <T, U> U[] map(T[] objects, MapFunction<T, U> f) {
    if(objects.length == 0) {throw new IllegalArgumentException("Can't map onto an empty array");}
    @SuppressWarnings("unchecked") U[] rtn = (U[])Array.newInstance(f.map(objects[0]).getClass(), objects.length);
    for(int i = 0; i < objects.length; i++)
        rtn[i] = f.map(objects[i]);
    return rtn;
}

Используя это, вы могли:

List<Customer> list = yourFunction();
List<String> names = Arrays.asList(map(list.toArray(new Customer[0]), new MapFunction<Customer, String>() {
    public String map(Customer c) {
        return c.getName();
    }
}));

Вы можете естественным образом изменить карту, чтобы она принимала коллекции вместо массивов, что устранит необходимость в Arrays.asList и List.toArray

person Michael Mrozek    schedule 24.06.2010

Используя Guava, вы можете использовать Функция вместе с Iterables.transform, Collections2.transform или Lists.transform для создания Iterable, Collection или List соответственно.

Iterable<String> names = Iterables.transform(customers, 
    new Function<Customer, String>() {
      public String apply(Customer from) {
        return from.getName();
      }
    });

Возвращенный Iterable является ленивым и применяет функцию к базовому списку, когда вы его просматриваете. Для List<String>, содержащего имена, вы можете использовать:

List<String> names = Lists.transform(...);

or

ImmutableList<String> names = ImmutableList.copyOf(Iterables.transform(...));

Конечно, записывать реализацию анонимного внутреннего класса Function каждый раз, когда вы хотите это сделать, некрасиво и многословно, поэтому вы можете сделать Function константой, доступной из класса Customer, например, с именем Customer.NAME.

Тогда преобразование выглядит намного лучше (особенно со статическим импортом):

for (String name : transform(customers, Customer.NAME)) { ... }

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

person ColinD    schedule 24.06.2010
comment
Хорошо, и вы, и полиген как бы ответили примерно одинаково. Если бы я мог принять два ответа как правильные, я бы принял и ваш. Спасибо! - person CoolBeans; 24.06.2010

.... есть короткий путь или более эффективный способ

Итак, вы ищете более эффективный способ сделать это:

 List<String> names = new ArrayList<String>();
 for( Customer customer : yourCustomerList ) {
     names.add( customer.getName() );
 }

?!!!!

Или просто другой способ?

Все предыдущие ответы на самом деле не более эффективны с точки зрения времени выполнения или кодирования. Однако они, без сомнения, более гибкие.

Другой альтернативой было бы включить Scala Groovy в ваш Java-код и использовать это:

list.map( _.name )

list.collect { it.name }

В случае компиляции классы Groovy можно использовать из Java, или вы можете подключить их как сценарий.

Вот пример для данного класса Customer, использующего Groovy в качестве скрипта.

    List<Customer> customers = Arrays.asList( new Customer[]{
       new Customer("A","123",1),
       new Customer("B","456",2),
       new Customer("C","789",3),
       new Customer("D","012",4)
    });

    setVariable(customers, "list");
    evaluate("names = list.collect { it.name } ");
    List<String> names = (List<String>) getVariable("names");
    System.out.println("names = " + names);

Вывод:

names = [A, B, C, D]

примечание: я извлек метод для удобства чтения, но вы можете увидеть их ниже

Но, опять же, это просто другое, не более эффективное, чем обычный цикл for.

Вот полный исходный код. Для его запуска вам просто нужны Java1.6 и Groovy в пути к классам.

person OscarRyz    schedule 24.06.2010
comment
Спасибо, я слышал о Groovy, но еще не испортил. Это выглядит аккуратно. - person CoolBeans; 25.06.2010