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