Преобразование потока в IntStream
У меня такое чувство, что я что-то пропустил. Я обнаружил, что делаю следующее
private static int getHighestValue(Map<Character, Integer> countMap) {
return countMap.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}
Моя проблема заключается в глупом преобразовании от Stream
до IntStream
через mapToInt(Integer::intValue)
Есть ли лучший способ сделать конверсию? все это заключается в том, чтобы избежать использования max()
из Stream
, для чего требуется передать Comparator
, но вопрос конкретно связан с преобразованием потока в IntStream
Ответы
Ответ 1
Из-за стирания типа реализация Stream
не знает о типе своих элементов и не может предоставить вам ни упрощенную операцию max
, ни преобразование в метод IntStream
.
В обоих случаях для выполнения операции с использованием неизвестного ссылочного типа элементов Stream
s требуется функция a Comparator
или ToIntFunction
.
Простейшей формой операции, которую вы хотите выполнить, является
return countMap.values().stream().max(Comparator.naturalOrder()).get();
учитывая тот факт, что компаратор естественного порядка реализован как одноэлементный. Таким образом, это единственный компаратор, который дает возможность распознаваться реализацией Stream
, если есть какая-либо оптимизация относительно элементов Comparable
. Если нет такой оптимизации, это будет по-прежнему вариант с наименьшим объемом памяти из-за его одноэлементности.
Если вы настаиваете на том, чтобы сделать преобразование Stream
в IntStream
, не существует способа обеспечить ToIntFunction
, и для функции Number::intValue
нет предопределенного одноэлементного режима, поэтому используйте Integer::intValue
это уже лучший выбор. Вместо этого вы можете написать i->i
, который короче, но просто скрывает операцию unboxing.
Ответ 2
Я понимаю, что вы пытаетесь избежать компаратора, но вы можете использовать встроенный для этого, ссылаясь на Integer.compareTo
:
private static int getHighestValue(Map<Character, Integer> countMap) {
return countMap.values().stream().max(Integer::compareTo).get();
}
Или, как предлагает @fge, используя ::compare
:
private static int getHighestValue(Map<Character, Integer> countMap) {
return countMap.values().stream().max(Integer::compare).get();
}
Ответ 3
Другой способ, которым вы могли бы сделать преобразование, - это lambda: mapToInt(i -> i)
.
Нужно ли использовать лямбда или ссылку на метод подробно обсуждать здесь, но резюме заключается в том, что вы должны использовать то, что вы найдете более читаемым.
Ответ 4
Если вопрос: "Могу ли я избежать передачи конвертера при преобразовании с Stream<T>
в IntStream
?" одним из возможных ответов может быть "В Java нет способа сделать такое преобразование безопасным и сделать его частью интерфейса Stream
одновременно".
Действительно метод, который преобразует Stream<T>
в IntStream
без конвертера, может выглядеть так:
public interface Stream<T> {
// other methods
default IntStream mapToInt() {
Stream<Integer> intStream = (Stream<Integer>)this;
return intStream.mapToInt(Integer::intValue);
}
}
Поэтому он должен быть вызван на Stream<Integer>
и будет терпеть неудачу в других типах потоков. Но поскольку потоки ленивы оцениваются и из-за стирания типа (помните, что Stream<T>
является общим), код будет терпеть неудачу в том месте, где будет потребляться поток, который может быть далеко от вызова mapToInt()
. И это провалится таким образом, что крайне сложно найти источник проблемы.
Предположим, что у вас есть код:
public class IntStreamTest {
public static void main(String[] args) {
IntStream intStream = produceIntStream();
consumeIntStream(intStream);
}
private static IntStream produceIntStream() {
Stream<String> stream = Arrays.asList("1", "2", "3").stream();
return mapToInt(stream);
}
public static <T> IntStream mapToInt(Stream<T> stream) {
Stream<Integer> intStream = (Stream<Integer>)stream;
return intStream.mapToInt(Integer::intValue);
}
private static void consumeIntStream(IntStream intStream) {
intStream.filter(i -> i >= 2)
.forEach(System.out::println);
}
}
Он не выполнит вызов consumeIntStream()
с помощью:
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at java.util.stream.ReferencePipeline$4$1.accept(ReferencePipeline.java:210)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateSequential(ForEachOps.java:189)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
at streams.IntStreamTest.consumeIntStream(IntStreamTest.java:25)
at streams.IntStreamTest.main(IntStreamTest.java:10)
С помощью этой stacktrace вы можете быстро определить, что проблема находится в produceIntStream()
, потому что mapToInt()
был вызван в поток неправильного типа?
Конечно, можно написать метод преобразования, который безопасен по типу, потому что он принимает конкретный Stream<Integer>
:
public static IntStream mapToInt(Stream<Integer> stream) {
return stream.mapToInt(Integer::intValue);
}
// usage
IntStream intStream = mapToInt(Arrays.asList(1, 2, 3).stream())
но это не очень удобно, потому что это нарушает плавный характер интерфейса потоков.
BTW:
Функции расширения Kotlin позволяют вызывать некоторый код, поскольку он является частью интерфейса класса. Таким образом, вы можете назвать этот тип безопасного метода как метод Stream<java.lang.Integer>
:
// "adds" mapToInt() to Stream<java.lang.Integer>
fun Stream<java.lang.Integer>.mapToInt(): IntStream {
return this.mapToInt { it.toInt() }
}
@Test
fun test() {
Arrays.asList<java.lang.Integer>(java.lang.Integer(1), java.lang.Integer(2))
.stream()
.mapToInt()
.forEach { println(it) }
}