Bietet Java 8 eine gute Möglichkeit, einen Wert oder eine Funktion zu wiederholen?

Lesezeit: 9 Minuten

In vielen anderen Sprachen, z. Haskell ist es 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 das noch nicht in Java 8 gefunden. Gibt es eine solche Funktion im JDK von Java 8?

Oder alternativ etwas Äquivalent zu einem Bereich wie

[1..8]

Es scheint ein offensichtlicher Ersatz für eine ausführliche Anweisung in Java zu sein

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}

sowas zu haben

Range.from(1, 8).forEach(i -> System.out.println(i))

Obwohl dieses spezielle Beispiel nicht viel prägnanter aussieht … aber hoffentlich besser lesbar ist.

  • Hast du die studiert Streams-API? Das sollte für das JDK die beste Wahl sein. Es hat ein Angebot Funktion, das habe ich bisher gefunden.

    – Marko Topolnik

    30. August 2013 um 12:06 Uhr


  • @MarkoTopolnik Die Streams-Klasse wurde entfernt (genauer gesagt wurde sie auf mehrere andere Klassen aufgeteilt und einige Methoden wurden vollständig entfernt).

    – Assylias

    30. August 2013 um 12:09 Uhr


  • Sie rufen eine for-Schleife ausführlich auf! Gut, dass Sie in den Cobol-Tagen nicht dabei waren. Es dauerte über 10 deklarative Aussagen in Cobol, um aufsteigende Zahlen anzuzeigen. Heutzutage wissen junge Leute nicht zu schätzen, wie gut sie es haben.

    – Gilbert LeBlanc

    30. August 2013 um 12:59 Uhr

  • @GilbertLeBlanc Ausführlichkeit hat nichts damit zu tun. Loops sind nicht komponierbar, Streams schon. Schleifen führen zu unvermeidbaren Wiederholungen, während Streams eine Wiederverwendung ermöglichen. Als solche sind Streams eine quantitativ bessere Abstraktion als Schleifen und sollten bevorzugt werden.

    – Alain O’Dea

    11. November 2015 um 21:40 Uhr

  • @GilbertLeBlanc und wir mussten barfuß im Schnee programmieren.

    – Dawud ibn Kareem

    15. Dezember 2016 um 21:24 Uhr

Benutzer-Avatar
Assyrien

Für dieses spezielle Beispiel könnten Sie Folgendes tun:

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);

Wenn Sie einen anderen Schritt als 1 benötigen, können Sie eine Abbildungsfunktion verwenden, z. B. für einen Schritt von 2:

IntStream.rangeClosed(1, 8)
         .map(i -> 2 * i - 1)
         .forEach(System.out::println);

Oder erstellen Sie eine benutzerdefinierte Iteration und begrenzen Sie die Größe der Iteration:

IntStream.iterate(1, i -> i + 2)
         .limit(8)
         .forEach(System.out::println);

  • Closures werden den Java-Code vollständig zum Besseren verändern. Ich freue mich auf diesen Tag…

    – Marko Topolnik

    30. August 2013 um 12:11 Uhr

  • @jwenting Es kommt wirklich darauf an – typischerweise mit GUI-Zeug (Swing oder JavaFX), das entfernt wird viel von Boiler Plate aufgrund anonymer Klassen.

    – Assylias

    30. August 2013 um 12:12 Uhr

  • @jwenting Für jeden mit Erfahrung in FP ist Code, der sich um Funktionen höherer Ordnung dreht, ein reiner Gewinn. Für alle ohne diese Erfahrung ist es an der Zeit, Ihre Fähigkeiten zu verbessern – oder zu riskieren, im Staub zurückgelassen zu werden.

    – Marko Topolnik

    30. August 2013 um 12:14 Uhr


  • @MarkoTopolnik Möglicherweise möchten Sie eine etwas neuere Version des Javadoc verwenden (Sie verweisen auf Build 78, das neueste ist Build 105: download.java.net/lambda/b105/docs/api/java/util/stream/… )

    – Mark Rotteveel

    30. August 2013 um 12:22 Uhr


  • @GraemeMoss Sie könnten immer noch dasselbe Muster verwenden (IntStream.rangeClosed(1, 8).forEach(i -> methodNoArgs());), aber es verwirrt die Sache IMO und in diesem Fall scheint eine Schleife angezeigt zu werden.

    – Assylias

    30. August 2013 um 22:36 Uhr


Hier ist eine andere Technik, die mir neulich begegnet ist:

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));

Das Collections.nCopies Anruf erstellt a List enthält n Kopien von beliebigem Wert, den Sie bereitstellen. In diesem Fall ist es die Box Integer Wert 1. Natürlich erstellt es nicht wirklich eine Liste mit n Elemente; Es erstellt eine “virtualisierte” Liste, die nur den Wert und die Länge sowie alle Aufrufe enthält get within range gibt nur den Wert zurück. Das nCopies -Methode gibt es, seit das Collections Framework vor langer Zeit in JDK 1.2 eingeführt wurde. Natürlich wurde in Java SE 8 die Möglichkeit hinzugefügt, einen Stream aus seinem Ergebnis 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 die IntStream.generate und IntStream.iterate nähert, und überraschenderweise ist es auch schneller als die IntStream.range sich nähern.

Zum iterate und generate das Ergebnis ist vielleicht nicht allzu überraschend. Das Streams-Framework (eigentlich die Spliteratoren für diese Streams) basiert auf der Annahme, dass die Lambdas möglicherweise jedes Mal unterschiedliche Werte generieren und dass sie eine unbegrenzte Anzahl von Ergebnissen generieren. Dies macht paralleles Splitten besonders schwierig. Das iterate -Methode ist auch für diesen Fall problematisch, da jeder Aufruf das Ergebnis des vorherigen benötigt. Also die Streams mit generate und iterate eignen sich nicht sehr gut zum Generieren wiederholter Konstanten.

Die relativ schlechte Leistung von range ist überraschend. Auch dies ist virtualisiert, sodass die Elemente nicht wirklich alle im Speicher vorhanden sind und die Größe im Voraus bekannt ist. Dies sollte einen schnellen und leicht parallelisierbaren Splitter ergeben. Aber es ging überraschenderweise nicht sehr gut. Vielleicht ist das der Grund range muss für jedes Element des Bereichs einen Wert berechnen und dann eine Funktion darauf aufrufen. Aber diese Funktion ignoriert einfach ihre Eingabe und gibt eine Konstante zurück, daher bin ich überrascht, dass dies nicht inline und beendet ist.

Das Collections.nCopies Die Technik muss boxen/unboxen, um mit den Werten umzugehen, da es keine primitiven Spezialisierungen von gibt List. Da der Wert der ist gleich Jedes Mal wird es im Grunde genommen einmal verpackt und diese Box wird von allen geteilt n Kopien. Ich vermute, Boxen/Unboxing ist hochgradig optimiert, sogar intrinsisch, und es kann gut eingebettet werden.

Hier ist der Code:

    public static final int LIMIT = 500_000_000;
    public static final long VALUE = 3L;

    public long range() {
        return
            LongStream.range(0, LIMIT)
                .parallel()
                .map(i -> VALUE)
                .map(i -> i % 73 % 13)
                .sum();
}

    public long ncopies() {
        return
            Collections.nCopies(LIMIT, VALUE)
                .parallelStream()
                .mapToLong(i -> i)
                .map(i -> i % 73 % 13)
                .sum();
}

Und hier sind die JMH-Ergebnisse: (2,8 GHz Core2Duo)

Benchmark                    Mode   Samples         Mean   Mean error    Units
c.s.q.SO18532488.ncopies    thrpt         5        7.547        2.904    ops/s
c.s.q.SO18532488.range      thrpt         5        0.317        0.064    ops/s

Es gibt eine Menge Abweichungen in der ncopies-Version, aber insgesamt scheint sie bequem 20x schneller zu sein als die Range-Version. (Ich wäre jedoch 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 wird IntStream.range! Ich hatte erwartet, dass es notwendig sein würde, einen spezialisierten Splitter zu erstellen, um dies schnell zum Laufen zu bringen, aber es scheint bereits ziemlich gut zu sein.

  • Weniger erfahrene Entwickler könnten verwirrt sein oder in Schwierigkeiten geraten, wenn sie das erfahren nCopies eigentlich nicht Kopieren irgendetwas und die “Kopien” zeigen alle auf dieses eine einzelne Objekt. Es ist immer sicher, wenn dieses Objekt es ist unveränderlich, wie in diesem Beispiel ein eingerahmtes Primitiv. Sie spielen darauf in Ihrer „boxed once“-Aussage an, aber es könnte nett sein, die Vorbehalte hier ausdrücklich hervorzuheben, da dieses Verhalten nicht spezifisch für Auto-Boxing ist.

    – William Preis

    4. März 2015 um 0:04 Uhr

  • Das impliziert also LongStream.range ist deutlich langsamer als IntStream.range? Daher ist es gut, dass die Idee, keine anzubieten IntStream (aber verwenden LongStream für alle Integer-Typen) wurde entfernt. Beachten Sie, dass es für den sequentiellen Anwendungsfall überhaupt keinen Grund gibt, Stream zu verwenden: Collections.nCopies(8, 1).forEach(i -> System.out.println(i)); macht das gleiche wie Collections.nCopies(8, 1).stream().forEach(i -> System.out.println(i)); aber noch effizienter sein könnte Collections.<Runnable>nCopies(8, () -> System.out.println(1)).forEach(Runnable::run);

    – Holger

    11. Juni 2015 um 14:44 Uhr


  • @Holger, diese Tests wurden mit einem sauberen Typprofil durchgeführt, sodass sie nichts mit der realen Welt zu tun haben. Wahrscheinlich LongStream.range schneidet schlechter ab, weil es zwei Karten mit hat LongFunction innen, während ncopies hat drei Karten mit IntFunction, ToLongFunction und LongFunction, also sind alle Lambdas monomorph. Die Durchführung dieses Tests mit einem vorverschmutzten Typprofil (das näher am realen Fall liegt) zeigt dies ncopies ist 1,5x langsamer.

    – Tagir Walejew

    16. Juli 2015 um 11:44 Uhr

  • Vorzeitige Optimierung FTW

    – Rafael Bugajewski

    10. April 2016 um 21:53 Uhr

  • Der Vollständigkeit halber wäre es schön, einen Benchmark zu sehen, der diese beiden Techniken mit einem einfachen alten vergleicht for Schleife. Ihre Lösung ist zwar schneller als die Stream Code, meine Vermutung ist, dass a for loop würde beides deutlich schlagen.

    – Schreibmaschine

    1. November 2019 um 4:52 Uhr

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 der Ausführlichkeit auf Java-Ebene.

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);

  • () -> 1 würde nur Einsen erzeugen, ist das beabsichtigt? Die Ausgabe wäre also 1 1 1 1 1 1 1 1.

    – Christian Ullenboom

    13. Januar 2014 um 3:56 Uhr


  • Ja, gemäß dem ersten Haskell-Beispiel des OP take 8 (repeat 1). Assylias deckte so ziemlich alle anderen Fälle ab.

    – clstrfsck

    13. Januar 2014 um 12:20 Uhr


  • Stream<T> hat auch ein Generikum generate Methode, um einen unendlichen Strom eines anderen Typs zu erhalten, der auf die gleiche Weise begrenzt werden kann.

    – zstewart

    16. Oktober 2017 um 21:54 Uhr

Benutzer-Avatar
Hartmut Pfarr

Sobald eine Wiederholungsfunktion irgendwo definiert ist als

public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
    for (int i = 1; i <= n; i++)
        f.run();
};

Sie können es hin und wieder auf diese Weise verwenden, z.

repeat.accept(8, () -> System.out.println("Yes"));

Zu bekommen und gleichbedeutend mit Haskell’s

take 8 (repeat 1)

Du könntest schreiben

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));

Eine weitere Alternative ist die Verwendung von Stream.generate() Methode. Das folgende Snippet erstellt beispielsweise eine Liste mit 5 Instanzen von MyClass:

List<MyClass> timezones = Stream
    .generate(MyClass::createInstance)
    .limit(5)
    .collect(Collectors.toList());

Aus dem Java-Dokument:

generate(Supplier s) Gibt einen unendlichen sequenziellen, ungeordneten Stream zurück, in dem jedes Element vom bereitgestellten Supplier generiert wird.

Benutzer-Avatar
JH

Dies ist meine Lösung zur Implementierung der Times-Funktion. 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.

public static <T extends Object, R extends Void> R times(int count, Function<T, R> f, T t) {
    while (count > 0) {
        f.apply
        count--;
    }
    return null;
}

Hier ist ein Beispiel für die Verwendung:

Function<String, Void> greet = greeting -> {
    System.out.println(greeting);
    return null;
};

times(3, greet, "Hello World!");

1334260cookie-checkBietet Java 8 eine gute Möglichkeit, einen Wert oder eine Funktion zu wiederholen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy