In vielen anderen Sprachen, z. Haskell, es ist einfach, einen Wert oder eine Funktion mehrmals zu wiederholen, z. um eine Liste von 8 Kopien des Wertes 1 zu erhalten:
take 8 (repeat 1)
Aber ich habe dies in Java 8 noch nicht gefunden. Gibt es eine solche Funktion im JDK von Java 8?
Oder alternativ etwas, das einem Bereich wie entspricht
[1..8]
Es scheint ein offensichtlicher Ersatz für eine ausführliche Aussage in Java zu sein
for (int i = 1; i <= 8; i++) {
System.out.println(i);
}
so etwas haben
Range.from(1, 8).forEach(i -> System.out.println(i))
Obwohl dieses Beispiel nicht viel prägnanter aussieht ... aber hoffentlich ist es besser lesbar.
Antworten:
Für dieses spezielle Beispiel könnten Sie Folgendes tun:
Wenn Sie einen anderen Schritt als 1 benötigen, können Sie eine Zuordnungsfunktion verwenden, z. B. für einen Schritt von 2:
Oder erstellen Sie eine benutzerdefinierte Iteration und begrenzen Sie die Größe der Iteration:
quelle
IntStream.rangeClosed(1, 8).forEach(i -> methodNoArgs());
) verwenden, aber es verwirrt die Sache IMO und in diesem Fall scheint eine Schleife angezeigt zu sein.Hier ist eine andere Technik, die ich neulich kennengelernt habe:
Der
Collections.nCopies
Aufruf erzeugt einList
enthältn
Kopien von was auch immer Wert , den Sie bieten. In diesem Fall ist es der BoxwertInteger
1. Natürlich wird keine Liste mitn
Elementen erstellt. Es wird eine "virtualisierte" Liste erstellt, die nur den Wert und die Länge enthält, und jeder Aufrufget
innerhalb des Bereichs gibt nur den Wert zurück. DienCopies
Methode gibt es schon seit der Einführung des Collections Framework in JDK 1.2. Natürlich wurde in Java SE 8 die Möglichkeit hinzugefügt, aus seinem Ergebnis einen Stream zu erstellen.Große Sache, eine andere Möglichkeit, dasselbe in ungefähr der gleichen Anzahl von Zeilen zu tun.
Allerdings ist diese Technik schneller als das
IntStream.generate
undIntStream.iterate
nähert sich, und überraschend, es ist auch schneller als derIntStream.range
Ansatz.Denn
iterate
undgenerate
das Ergebnis ist vielleicht nicht allzu überraschend. Das Streams-Framework (eigentlich die Spliterators für diese Streams) basiert auf der Annahme, dass die Lambdas möglicherweise jedes Mal unterschiedliche Werte generieren und eine unbegrenzte Anzahl von Ergebnissen generieren. Dies macht eine parallele Aufteilung besonders schwierig. Dieiterate
Methode ist auch in diesem Fall problematisch, da jeder Aufruf das Ergebnis des vorherigen erfordert. So sind die Ströme mitgenerate
unditerate
nicht sehr gut tun für wiederholte Konstanten zu erzeugen.Die relativ schlechte Leistung von
range
ist überraschend. Auch dies ist virtualisiert, sodass nicht alle Elemente im Speicher vorhanden sind und die Größe im Voraus bekannt ist. Dies sollte zu einem schnellen und leicht parallelisierbaren Spliterator führen. Aber es lief überraschenderweise nicht sehr gut. Vielleicht liegt der Grund darin, dassrange
für jedes Element des Bereichs ein Wert berechnet und dann eine Funktion darauf aufgerufen werden muss. Diese Funktion ignoriert jedoch nur ihre Eingabe und gibt eine Konstante zurück. Ich bin überrascht, dass dies nicht inline und getötet ist.Die
Collections.nCopies
Technik muss Boxen / Unboxen durchführen, um die Werte zu verarbeiten, da es keine primitiven Spezialisierungen von gibtList
. Da der Wert jedes Mal der gleiche ist, wird er grundsätzlich einmal eingerahmt und von allenn
Kopien gemeinsam genutzt. Ich vermute, dass das Boxen / Unboxen stark optimiert ist, sogar intrinsisch, und es kann gut inliniert werden.Hier ist der Code:
Und hier sind die JMH-Ergebnisse: (2,8 GHz Core2Duo)
In der ncopies-Version gibt es einiges an Varianz, aber insgesamt scheint sie bequem 20x schneller zu sein als die Range-Version. (Ich wäre durchaus bereit zu glauben, dass ich etwas falsch gemacht habe.)
Ich bin überrascht, wie gut die
nCopies
Technik funktioniert. Intern macht es nicht viel Besonderes, da der Stream der virtualisierten Liste einfach mit implementiert wirdIntStream.range
! Ich hatte erwartet, dass es notwendig sein würde, einen speziellen Spliterator zu erstellen, damit dies schnell geht, aber es scheint bereits ziemlich gut zu sein.quelle
nCopies
nichts kopiert wird und die "Kopien" alle auf dieses eine Objekt verweisen. Es ist immer sicher, wenn dieses Objekt unveränderlich ist , wie in diesem Beispiel ein Boxed-Primitiv. Sie spielen in Ihrer "Boxed Once" -Anweisung darauf an, aber es könnte hilfreich sein, die Vorbehalte hier explizit hervorzuheben, da dieses Verhalten nicht spezifisch für das automatische Boxen ist.LongStream.range
das deutlich langsamer ist alsIntStream.range
? Es ist also gut, dass die Idee, keineIntStream
(aberLongStream
für alle ganzzahligen Typen zu verwenden), weggelassen wurde. Beachten Sie, dass es für den sequentiellen Anwendungsfall überhaupt keinen Grund gibt, Stream zu verwenden: ErCollections.nCopies(8, 1).forEach(i -> System.out.println(i));
macht dasselbe, istCollections.nCopies(8, 1).stream().forEach(i -> System.out.println(i));
aber möglicherweise noch effizienterCollections.<Runnable>nCopies(8, () -> System.out.println(1)).forEach(Runnable::run);
LongStream.range
schlechter, weil es zwei Karten mitLongFunction
innen hat, währendncopies
es drei Karten mit und hatIntFunction
,ToLongFunction
undLongFunction
daher sind alle Lambdas monomorph. Das Ausführen dieses Tests für ein vorverschmutztes Typprofil (das näher am realen Fall liegt) zeigt, dassncopies
es 1,5-mal langsamer ist.for
Schleife vergleicht. Ihre Lösung ist zwar schneller als derStream
Code, aber ich vermute, dass einefor
Schleife beide mit einem signifikanten Vorsprung schlagen würde.Der Vollständigkeit halber und auch, weil ich mir nicht helfen konnte :)
Das Generieren einer begrenzten Folge von Konstanten kommt dem, was Sie in Haskell sehen würden, ziemlich nahe, nur mit Ausführlichkeit auf Java-Ebene.
quelle
() -> 1
würde nur Einsen erzeugen, ist das beabsichtigt? Die Ausgabe wäre also1 1 1 1 1 1 1 1
.take 8 (repeat 1)
. Assylias deckte so ziemlich alle anderen Fälle ab.Stream<T>
hat auch eine generischegenerate
Methode, um einen unendlichen Stream eines anderen Typs zu erhalten, der auf die gleiche Weise eingeschränkt werden kann.Einmal ist eine Wiederholungsfunktion irgendwo definiert als
Sie können es ab und zu folgendermaßen verwenden, z.
Zu bekommen und gleichwertig mit Haskell
Du könntest schreiben
quelle
Runnable
zuFunction<Integer, ?>
und dann mitf.apply(i)
.Dies ist meine Lösung zur Implementierung der Zeitfunktion. Ich bin ein Junior, also gebe ich zu, dass es nicht ideal sein könnte. Ich würde mich freuen zu hören, wenn dies aus irgendeinem Grund keine gute Idee ist.
Hier einige Anwendungsbeispiele:
quelle