Gibt es ein Java-Äquivalent zum Schlüsselwort 'Yield' von C #?


112

Ich weiß, dass es in Java selbst kein direktes Äquivalent gibt, aber vielleicht einen Dritten?

Es ist wirklich praktisch. Derzeit möchte ich einen Iterator implementieren, der alle Knoten in einem Baum ergibt, dh etwa fünf Codezeilen mit Ausbeute.


6
Ich weiß, ich weiß. Aber ich denke, mehr Sprachen zu kennen ist mehr Macht. Darüber hinaus wird die Backend-Entwicklung (die ich gerade mache) in dem Unternehmen, für das ich gerade arbeite, in Java durchgeführt, sodass ich die Sprache nicht wirklich auswählen kann :(
ripper234

Antworten:


91

Die beiden mir bekannten Optionen sind die Infomancers-Collections-Bibliothek von Aviad Ben Dov aus dem Jahr 2007 und die YieldAdapter-Bibliothek von Jim Blackler aus dem Jahr 2008 (die auch in der anderen Antwort erwähnt wird).

Mit beiden können Sie Code mit einem yield returnähnlichen Konstrukt in Java schreiben , sodass beide Ihre Anforderung erfüllen. Die bemerkenswerten Unterschiede zwischen den beiden sind:

Mechanik

Die Bibliothek von Aviad verwendet die Bytecode-Manipulation, während die von Jim Multithreading verwendet. Abhängig von Ihren Anforderungen kann jeder seine eigenen Vor- und Nachteile haben. Es ist wahrscheinlich, dass die Lösung von Aviad schneller ist, während die von Jim portabler ist (zum Beispiel glaube ich nicht, dass die Bibliothek von Aviad unter Android funktioniert).

Schnittstelle

Die Bibliothek von Aviad verfügt über eine übersichtlichere Benutzeroberfläche - hier ein Beispiel:

Iterable<Integer> it = new Yielder<Integer>() {
    @Override protected void yieldNextCore() {
        for (int i = 0; i < 10; i++) {
            yieldReturn(i);
            if (i == 5) yieldBreak();
        }
    }
};

Während Jims viel komplizierter ist, müssen Sie adeptein Generikum verwenden, Collectordas eine collect(ResultHandler)Methode hat ... ugh. Sie könnten jedoch so etwas wie diesen Wrapper um Jims Code von Zoom Information verwenden, was das erheblich vereinfacht:

Iterable<Integer> it = new Generator<Integer>() {
    @Override protected void run() {
        for (int i = 0; i < 10; i++) {
            yield(i);
            if (i == 5) return;
        }
    }
};

Lizenz

Die Lösung von Aviad ist BSD.

Jims Lösung ist gemeinfrei, ebenso wie der oben erwähnte Wrapper.


3
Fantastische Antwort. Sie haben die Frage nicht nur vollständig beantwortet, sondern auch auf sehr klare Weise. Außerdem gefällt mir das Format Ihrer Antwort und wie Sie Lizenzinformationen angegeben haben. Mach weiter so! :)
Malcolm

1
Vergiss Guavas nicht AbstractIterator.
Shmosel

Ich habe hier gerade eine andere (MIT-lizenzierte) Lösung veröffentlicht, die einen separaten Thread für den Produzenten startet und eine begrenzte Warteschlange zwischen dem Produzenten und dem Konsumenten einrichtet: github.com/lukehutch/Producer
Luke Hutchison

Beachten Sie, dass dies yield(i)ab JDK 13 nicht mehr funktioniert, da yieldes als neue Java-Anweisung / reserviertes Schlüsselwort hinzugefügt wird.
Luke Hutchison

14

Beide Ansätze können jetzt etwas sauberer gemacht werden, da Java Lambdas hat. Sie können so etwas tun

public Yielderable<Integer> oneToFive() {
    return yield -> {
        for (int i = 1; i < 10; i++) {
            if (i == 6) yield.breaking();
            yield.returning(i);
        }
    };
}

Ich habe hier etwas mehr erklärt.


4
Yielderable? Sollte es nicht einfach so sein Yieldable? (Das Verb ist nur "Ausbeute", nicht "Ertrag" oder "Ertrag" oder was auch immer)
Jochem Kuijpers

yield -> { ... }wird ab JDK 13 unterbrochen, da yieldes als neue Java-Anweisung / reserviertes Schlüsselwort hinzugefügt wird.
Luke Hutchison

1

Ich weiß, dass es hier eine sehr alte Frage ist, und es gibt zwei oben beschriebene Möglichkeiten:

  • Bytecode-Manipulation, die beim Portieren nicht so einfach ist;
  • Thread-basiert yield, das hat offensichtlich Ressourcenkosten.

Es gibt jedoch eine andere, dritte und wahrscheinlich natürlichste Möglichkeit, den yieldGenerator in Java zu implementieren, die der Implementierung von C # 2.0+ -Compilern zur yield return/breakGenerierung am nächsten kommt : lombok-pg . Es basiert vollständig auf einer Zustandsmaschine und erfordert eine enge Zusammenarbeit javac, um den Quellcode AST zu manipulieren. Leider scheint die lombok-pg-Unterstützung eingestellt zu sein (seit mehr als ein oder zwei Jahren keine Repository-Aktivität mehr), und dem ursprünglichen Projekt Lombok fehlt leider die yieldFunktion (es hat jedoch eine bessere IDE wie Eclipse, IntelliJ IDEA-Unterstützung).


1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (action) ist nicht dasselbe wie Yield Operator, aber es kann nützlich sein, eigene Generatoren folgendermaßen zu schreiben:

import java.util.stream.Stream;
public class Test01 {
    private static void myFoo(int someVar){
        //do some work
        System.out.println(someVar);
    }
    private static void myFoo2(){
        //do some work
        System.out.println("some work");
    }
    public static void main(String[] args) {
        Stream.iterate(1, x -> x + 1).limit(15).forEach(Test01::myFoo);     //var1
        Stream.iterate(1, x -> x + 1).limit(10).forEach(item -> myFoo2());  //var2
    }
}

0

Ich würde auch vorschlagen, wenn Sie RXJava bereits in Ihrem Projekt verwenden, um ein Observable als "Yielder" zu verwenden. Es kann auf ähnliche Weise verwendet werden, wenn Sie Ihr eigenes Observable erstellen.

public class Example extends Observable<String> {

    public static void main(String[] args) {
        new Example().blockingSubscribe(System.out::println); // "a", "b", "c", "d"
    }

    @Override
    protected void subscribeActual(Observer<? super String> observer) {
        observer.onNext("a"); // yield
        observer.onNext("b"); // yield
        observer.onNext("c"); // yield
        observer.onNext("d"); // yield
        observer.onComplete(); // finish
    }
}

Observables können in Iteratoren umgewandelt werden, sodass Sie sie sogar in traditionelleren for-Schleifen verwenden können. Auch RXJava bietet Ihnen wirklich leistungsstarke Tools, aber wenn Sie nur etwas Einfaches benötigen, wäre dies möglicherweise ein Overkill.


0

Ich habe hier gerade eine andere (MIT-lizenzierte) Lösung veröffentlicht , die den Produzenten in einem separaten Thread startet und eine begrenzte Warteschlange zwischen dem Produzenten und dem Konsumenten einrichtet , die Pufferung, Flusskontrolle und paralleles Pipelining zwischen Produzent und Konsument ermöglicht (so) dass der Verbraucher daran arbeiten kann, den vorherigen Artikel zu konsumieren, während der Produzent daran arbeitet, den nächsten Artikel zu produzieren).

Sie können dieses anonyme Formular für die innere Klasse verwenden:

Iterable<T> iterable = new Producer<T>(queueSize) {
    @Override
    public void producer() {
        produce(someT);
    }
};

beispielsweise:

for (Integer item : new Producer<Integer>(/* queueSize = */ 5) {
    @Override
    public void producer() {
        for (int i = 0; i < 20; i++) {
            System.out.println("Producing " + i);
            produce(i);
        }
        System.out.println("Producer exiting");
    }
}) {
    System.out.println("  Consuming " + item);
    Thread.sleep(200);
}

Oder Sie können die Lambda-Notation verwenden, um die Boilerplate zu reduzieren:

for (Integer item : new Producer<Integer>(/* queueSize = */ 5, producer -> {
    for (int i = 0; i < 20; i++) {
        System.out.println("Producing " + i);
        producer.produce(i);
    }
    System.out.println("Producer exiting");
})) {
    System.out.println("  Consuming " + item);
    Thread.sleep(200);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.