Почему Stream не имеет метода toList()?
При использовании потоков Java 8 довольно часто можно взять список, создать поток из него, выполнить бизнес и преобразовать его. Что-то вроде:
Stream.of(-2,1,2,-5)
.filter(n -> n > 0)
.map(n -> n * n)
.collect(Collectors.toList());
Почему нет короткого/удобного метода для части ".collect(Collectors.toList())
"? В интерфейсе Stream есть метод преобразования результатов в массив с именем toArray()
, почему отсутствует toList()
?
IMHO, преобразование результата в список чаще, чем для массива. Я могу жить с этим, но это довольно раздражает, чтобы называть это уродство.
Любые идеи?
Ответы
Ответ 1
Недавно я написал небольшую библиотеку под названием StreamEx, которая расширяет потоки Java и предоставляет этот точный метод среди многих других функций:
StreamEx.of(-2,1,2,-5)
.filter(n -> n > 0)
.map(n -> n * n)
.toList();
Также доступны toSet(), toCollection (Поставщик), join(), groupingBy() и другие методы быстрого доступа.
Ответ 2
Что касается "почему", я считаю, что в комментариях есть довольно много аргументов. Однако я согласен с вами в том, что это довольно раздражает, если у вас нет метода toList()
. То же самое происходит с методом toIterable()
.
Итак, я покажу вам трюк, который позволит вам использовать эти два метода в любом случае. К счастью, Java очень гибкий и позволяет делать всевозможные интересные вещи. Около 10 лет назад я прочитал эту статью, в которой описывается остроумный трюк, чтобы "подключить" методы к любому данному интерфейсу. Трюк состоит в использовании прокси для адаптации интерфейса, который не имеет методов, которые вы хотите. На протяжении многих лет я обнаружил, что у нее есть все преимущества адаптера, в то время как у него отсутствуют все его недостатки. Это то, что я называю большой сделкой.
Вот пример кода, просто чтобы показать идею:
public class Streams {
public interface EnhancedStream<T>
extends Stream<T> {
List<T> toList();
Iterable<T> toIterable();
}
@SuppressWarnings("unchecked")
public static <T> EnhancedStream<T> enhance(Stream<T> stream) {
return (EnhancedStream<T>) Proxy.newProxyInstance(
EnhancedStream.class.getClassLoader(),
new Class<?>[] {EnhancedStream.class},
(proxy, method, args) -> {
if ("toList".equals(method.getName())) {
return stream.collect(Collectors.toList());
} else if ("toIterable".equals(method.getName())) {
return (Iterable<T>) stream::iterator;
} else {
// invoke method on the actual stream
return method.invoke(stream, args);
}
});
}
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.of(-2, 1, 2, -5).
filter(n -> n > 0).map(n -> n * n);
List<Integer> list = Streams.enhance(stream1).toList();
System.out.println(list); // [1, 4]
Stream<Integer> stream2 = Stream.of(-2, 1, 2, -5).
filter(n -> n > 0).map(n -> n * n);
Iterable<Integer> iterable = Streams.enhance(stream2).toIterable();
iterable.forEach(System.out::println); // 1
// 4
}
}
Идея состоит в использовании интерфейса EnhancedStream
, который расширяет интерфейс Java Stream
, определяя методы, которые вы хотите добавить. Затем динамический прокси-сервер реализует этот расширенный интерфейс, делегируя оригинальные методы Stream
для адаптируемого фактического потока, в то время как он просто предоставляет встроенную реализацию новым методам (те, которые не определены в Stream
).
Этот прокси доступен с помощью статического метода, который прозрачно выполняет все проксирование.
Обратите внимание, что я не утверждаю, что это окончательное решение. Вместо этого это просто пример, который может быть значительно улучшен, т.е. Для каждого метода Stream
, который возвращает другой Stream
, вы также можете вернуть прокси-сервер для этого. Это позволит привязать EnhancedStream
(вам нужно переопределить эти методы в интерфейсе EnhancedStream
, чтобы они возвращали тип возвращаемого ковариатора EnhancedStream
). Кроме того, отсутствует правильная обработка исключений, а также более надежный код, чтобы решить, следует ли делегировать выполнение методов исходному потоку или нет.
Ответ 3
Вот простой класс-помощник, который делает это проще:
public class Li {
public static <T> List<T> st(final Stream<T> stream) {
return stream.collect(Collectors.toList());
}
}
Пример использования:
List<String> lowered = Li.st(Stream.of("HELLO", "WORLD").map(String::toLowerCase));
Ответ 4
Это довольно удобно:
resultList =
originalList.stream().
filter(ball -> ball.isRed()).
collect(Collectors.toCollection(ArrayList::new));