Convertir un flux en IntStream

J'ai l'impression qu'il me manque quelque chose ici. Je me suis retrouvé à faire ce qui suit

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}

Mon problème est avec la conversion stupide de Stream à IntStream via le mapToInt(Integer::intValue)

Y a-t-il une meilleure façon de faire la conversion? tout cela est d'éviter d'utiliser max() from Stream, ce qui nécessite de passer un Comparator mais la question est spécifiquement sur la conversion de Stream à IntStream

21
demandé sur Lonely Neuron 2015-01-19 00:59:15

4 réponses

En raison de l'effacement de type, l'implémentation Stream n'a aucune connaissance sur le type de ses éléments et ne peut vous fournir ni une opération max simplifiée ni une méthode de conversion en IntStream.

Dans les deux cas, il faut une fonction, a Comparator ou a ToIntFunction, respectivement, pour effectuer l'opération en utilisant le type de référence inconnu des éléments de Stream.

La forme La plus simple pour l'opération que vous voulez effectuer est

return countMap.values().stream().max(Comparator.naturalOrder()).get();

Compte tenu du fait que le naturel le comparateur order est implémenté en tant que singleton. C'est donc le seul comparateur qui offre la chance d'être reconnu par l'implémentation Stream si Il y a une optimisation concernant les éléments Comparable. S'il n'y a pas une telle optimisation, ce sera toujours la variante avec l'empreinte mémoire la plus faible en raison de sa nature singleton.

Si vous insistez pour faire une conversion du Stream en un IntStream Il n'y a aucun moyen de fournir un ToIntFunction et il n'y a pas de singleton prédéfini pour un type de fonction Number::intValue, l'utilisation de Integer::intValue est déjà le meilleur choix. Vous pouvez écrire i->i à la place, ce qui est plus court mais cache simplement l'opération de déballage.

8
répondu Holger 2015-01-19 10:03:27

Je me rends compte que vous essayez d'éviter un comparateur, mais vous pouvez utiliser le microphone pour ce en se référant à Integer.compareTo:

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().max(Integer::compareTo).get();
}

Ou comme le suggère @fge, en utilisant ::compare:

private static int getHighestValue(Map<Character, Integer> countMap) {
    return countMap.values().stream().max(Integer::compare).get();
}
9
répondu Todd 2015-01-18 22:03:38

Une autre façon de faire la conversion est avec un lambda: mapToInt(i -> i). Si vous devez utiliser une référence lambda ou une méthode est discutée en détail ici, mais le résumé est que vous devez utiliser celui que vous trouvez le plus lisible.

5
répondu Alex 2018-04-22 07:00:54

Si la question Est "puis-je éviter de passer le convertisseur lors de la conversion de Stream<T> à IntStream?"une réponse possible pourrait être "il n'y a aucun moyen en Java de rendre une telle conversion sûre et de la faire partie de l'interface Stream en même temps".

En effet, la méthode qui convertit Stream<T> en IntStream sans convertisseur pourrait ressembler à ceci:

public interface Stream<T> {
    // other methods

    default IntStream mapToInt() {
        Stream<Integer> intStream = (Stream<Integer>)this;
        return intStream.mapToInt(Integer::intValue);
    }
}

Donc, il suppose d'être appelé sur Stream<Integer> et échouera sur d'autres types de flux. Mais parce que les flux sont paresseux évalués et parce que du type erasure (rappelez-vous que Stream<T> est générique), le code échouera à l'endroit où le flux est consommé, ce qui pourrait être loin de l'appel mapToInt(). Et il échouera d'une manière qui est extrêmement difficile de localiser la source du problème.

Supposons que vous ayez du code:

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);
    }
}

Il échouera lors de l'appel consumeIntStream() avec:

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)

Ayant cette stacktrace pouvez-vous identifier rapidement que le problème est dans produceIntStream() parce que mapToInt() a été appelé sur le flux du mauvais type de?

Bien sûr, on peut écrire une méthode de conversion qui est de type safe car elle accepte le béton Stream<Integer>:

public static IntStream mapToInt(Stream<Integer> stream) {
    return stream.mapToInt(Integer::intValue);
}

// usage
IntStream intStream = mapToInt(Arrays.asList(1, 2, 3).stream())

Mais ce n'est pas très pratique car il casse la nature de l'interface fluide des flux.

BTW:

Les fonctions d'extension de Kotlin permettent d'appeler du code car il fait partie de l'interface de la classe. Vous pouvez donc appeler cette méthode de type safe en tant que méthode de 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) }
}
1
répondu Lu55 2017-10-29 16:17:28