Преобразование потока в 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) }
}