Расширение списка<T> в Java 8

Я часто хочу отобразить один список в другой. Например, если бы у меня был список людей, и я хотел бы получить список их имен, я бы хотел сделать:

ЦЕЛЬ

List<Person> people = ... ;

List<String> names = people.map(x -> x.getName());

Нечто подобное возможно с Java 8:

JAVA 8 VERSION

List<String> names = people.stream()
                           .map(x -> x.getName())
                           .collect(Collectors.toList());

Но это явно не так приятно. На самом деле, я думаю, что использование Guava чище:

GUAVA VERSION

List<String> names = Lists.transform(people, x -> x.getName());

Тем не менее, я люблю цепочки. Итак, моя цель возможна?

Я слышал, что люди говорят, что методы Java 8 по умолчанию похожи на методы расширения C#. С помощью метода расширения C# я мог бы легко добавить вспомогательный метод IEnumerable<T>:

public static IEnumerable<TRet> Map<T, TRet>(this IEnumerable<T> list, Func<T, TRet> selector)
{
    return list.Select(selector);
}

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

Кроме того, это, очевидно, тривиальный пример. В общем, я хотел бы иметь возможность продлить List<T> а также Iterable<T> интерфейсы для облегчения взаимодействия с потоками API.

4 ответа

Решение

Нет; ты не можешь сделать это.

Методы по умолчанию не совпадают с методами расширения; они могут быть определены только в исходном интерфейсе.

Если вы хотите иметь легкий вид на List применяя Function и поддерживая цепочку вы можете сделать это так:

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

public class MappingList<E> extends AbstractList<E> {
  // using this helper class we avoid carrying <S> with the public API
  static final class Source<E,S> {
      final List<S> list;
      final Function<? super S, ? extends E> mapper;
      Source(List<S> l, Function<? super S, ? extends E> m) {
          list=l;
          mapper=m;
      }
      E get(int index) { return mapper.apply(list.get(index)); }
      <T> Source map(Function<? super E, ? extends T> f) {
          Objects.requireNonNull(f);
          return new Source<>(list, mapper.andThen(f));
      }
      Stream<E> stream() { return list.stream().map(mapper); }
      Stream<E> parallelStream() { return list.parallelStream().map(mapper); }
    }
    final Source<E,?> source;

    private MappingList(Source<E,?> s) {
        Objects.requireNonNull(s);
        source=s;
    }
    @Override
    public E get(int index) {
        return source.get(index);
    }
    @Override
    public int size() {
        return source.list.size();
    }
    @Override
    public Stream<E> stream() {
        return source.stream();
    }
    @Override
    public Stream<E> parallelStream() {
        return source.parallelStream();
    }
    public <T> MappingList<T> map(Function<? super E, ? extends T> f) {
        return new MappingList<>(source.map(f));
    }
    public static <S,T> MappingList<T> map(
      List<S> l, Function<? super S, ? extends T> f) {
        Objects.requireNonNull(l);
        if(l instanceof MappingList)
            return ((MappingList<S>)l).map(f);
        return new MappingList<>(new Source<>(l, f));
    }
}

Он поддерживает создание отображенного списка в стиле GUAVA, в то же время позволяя использовать Stream API с отображенным списком, лениво оценивающий все значения:

public static void main(String[] arg) {
    List<String> strings=Arrays.asList("a", "simple", "list");
    List<Integer> ints=MappingList.map(strings, s->compute(s));
    List<Integer> results=MappingList.map(ints, i->compute(i));
    for(int result:results) {
        System.out.println("first result: "+result);
        System.out.println("Not computing any more values");
        break;
    }
    System.out.println();
    System.out.println("  interacting with stream API:");
    System.out.println(results.stream().filter(i-> i>500).findFirst());
}
public static int compute(String s) {
    System.out.println("doing computation for "+s);
    return Integer.parseInt(s, 36);
}
public static int compute(int i) {
    System.out.println("doing computation for "+i);
    return i*i;
}
делать вычисления для
делать вычисления для 10
первый результат: 100
Не вычисляя больше значений

  взаимодействуя с потоковым API:
делать вычисления для
делать вычисления для 10
делать вычисления для простого
делать вычисления для 1724345618
Дополнительно [410277188]

Если вы хотите создать List с предварительно рассчитанными значениями из него вы можете просто использовать new ArrayList<>(mappedList),

Методы расширения C# великолепны и предоставляют и простой механизм для расширения коллекций C# с помощью собственных методов.

Но сейчас в Яве есть потоки. Основываясь на ответе fge, я пришел с этим фрагментом, чтобы написать собственные методы расширения для потоков:

public final class StreamExtender<T>
{
    private final Stream<T> _inputStream;

    public static <T> StreamExtender<T> extend(final Stream<T> inputStream)
    {
        return new StreamExtender<>(inputStream);
    }

    private StreamExtender(final Stream<T> inputStream)
    {
        this._inputStream = inputStream;
    }

    public <U> List<U> extensionMethod(final Function<T, U> f)
    {
        // your own code.
        return _inputStream.map(f).collect(Collectors.toList());
    }
}

И увидеть это в действии:

Integer[] array = { 1, 2, 3};
List<String> result = StreamExtender.extend(stream(array)).extensionMethod(Object::toString);

Употребление в пищу моей собственной собаки и реализация того, что я предложил в комментарии (НЕ ПРОВЕРЕНО, но это должно сработать - обратите внимание, что вы должны использовать super где уместно, это не моя сильная сторона)

public final class ListTransformer<T>
{
    private final List<T> inputList;

    public static <X> ListTransformer<X> transform(final List<X> inputList)
    {
        return new ListTransformer<X>(inputList);
    }

    private ListTransformer(final List<T> inputList)
    {
        this.inputList = inputList;
    }

    public <U> List<U> using(final Function<T, U> f)
    {
        return inputList.stream().map(f).collect(Collectors.toList());
    }
}

Использование:

import static wherever.is.ListTransformer.transform;

//
final List<String> names = transform(personList).using(x -> x.getName());
Другие вопросы по тегам