Esiste un Java equivalente alla parola chiave "yield" di C #?


112

So che non esiste un equivalente diretto in Java stesso, ma forse una terza parte?

È davvero conveniente. Attualmente vorrei implementare un iteratore che restituisca tutti i nodi in un albero, che è di circa cinque righe di codice con rendimento.


6
Lo so, lo so. Ma penso che conoscere più lingue sia più potente. Inoltre, lo sviluppo del backend (che sto facendo) nell'azienda per cui lavoro in questo momento viene eseguito in Java, quindi non posso davvero scegliere la lingua :(
ripper234

Risposte:


91

Le due opzioni che conosco sono la libreria di collezioni di infomanti di Aviad Ben Dov del 2007 e la libreria YieldAdapter di Jim Blackler del 2008 (che è anche menzionata nell'altra risposta).

Entrambi ti permetteranno di scrivere codice con il yield returncostrutto -like in Java, quindi entrambi soddisferanno la tua richiesta. Le differenze notevoli tra i due sono:

Meccanica

La libreria di Aviad utilizza la manipolazione del bytecode mentre quella di Jim utilizza il multithreading. A seconda delle tue esigenze, ognuna può avere i suoi vantaggi e svantaggi. È probabile che la soluzione di Aviad sia più veloce, mentre quella di Jim è più portabile (ad esempio, non credo che la libreria di Aviad funzionerà su Android).

Interfaccia

La libreria di Aviad ha un'interfaccia più pulita: ecco un esempio:

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

Mentre quello di Jim è molto più complicato, ti richiede adeptun generico Collectorche abbia un collect(ResultHandler)metodo ... ugh. Tuttavia, potresti usare qualcosa come questo wrapper attorno al codice di Jim di Zoom Information che semplifica notevolmente che:

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

Licenza

La soluzione di Aviad è BSD.

La soluzione di Jim è di pubblico dominio, così come il suo wrapper menzionato sopra.


3
Risposta fantastica. Non solo hai risposto totalmente alla domanda, lo hai fatto in modo molto chiaro. Inoltre, mi piace il formato della tua risposta e il modo in cui hai incluso le informazioni sulla licenza. Continuate con la fantastica risposta! :)
Malcolm

1
Non dimenticare Guava's AbstractIterator.
shmosel

Ho appena pubblicato un'altra soluzione (con licenza MIT) qui, che lancia un thread separato per il produttore e crea una coda delimitata tra il produttore e il consumatore: github.com/lukehutch/Producer
Luke Hutchison

Si noti che yield(i)si interromperà a partire da JDK 13, poiché yieldviene aggiunto come nuova istruzione Java / parola chiave riservata.
Luke Hutchison

14

Entrambi questi approcci possono essere resi un po 'più puliti ora che Java ha Lambdas. Puoi fare qualcosa di simile

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

Ho spiegato un po 'di più qui.


4
Yielderable? Non dovrebbe essere solo Yieldable? (il verbo è solo 'cedere', non 'prodigare' o 'prodigare' o qualsiasi altra cosa)
Jochem Kuijpers

yield -> { ... }si interromperà a partire da JDK 13, poiché yieldviene aggiunto come nuova istruzione Java / parola chiave riservata.
Luke Hutchison

1

So che qui è una domanda molto vecchia e ci sono due modi descritti sopra:

  • manipolazione del bytecode che non è così facile durante il porting;
  • basato su thread yieldche ovviamente ha costi in risorse.

Tuttavia, c'è un altro modo, il terzo e probabilmente il più naturale, di implementare il yieldgeneratore in Java che è l'implementazione più vicina a ciò che i compilatori C # 2.0+ fanno per la yield return/breakgenerazione: lombok-pg . È completamente basato su una macchina a stati e richiede una stretta collaborazione javacper manipolare il codice sorgente AST. Sfortunatamente, il supporto lombok-pg sembra essere interrotto (nessuna attività di repository per più di un anno o due), e l'originale Project Lombok purtroppo manca della yieldfunzionalità (ha un IDE migliore come Eclipse, supporto IntelliJ IDEA, però).


1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (action) non è la stessa cosa dell'operatore yield, ma potrebbe essere utile scrivere i propri generatori in questo modo:

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

Suggerirei anche se stai già usando RXJava nel tuo progetto di usare un Observable come "yielder". Può essere utilizzato in modo simile se crei il tuo Observable.

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

Gli osservabili possono essere trasformati in iteratori in modo da poterli persino utilizzare in cicli for più tradizionali. Inoltre RXJava ti offre strumenti davvero potenti, ma se hai solo bisogno di qualcosa di semplice, forse questo sarebbe eccessivo.


0

Ho appena pubblicato un'altra soluzione (con licenza MIT) qui , che avvia il produttore in un thread separato e imposta una coda delimitata tra il produttore e il consumatore, consentendo il buffering, il controllo del flusso e il pipelining parallelo tra produttore e consumatore (quindi che il consumatore può lavorare sul consumo dell'articolo precedente mentre il produttore sta lavorando sulla produzione dell'articolo successivo).

Puoi usare questo modulo anonimo della classe interna:

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

per esempio:

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

Oppure puoi usare la notazione lambda per ridurre il boilerplate:

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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.