Existe-t-il un équivalent Java au mot-clé «yield» de C #?


112

Je sais qu'il n'y a pas d'équivalent direct dans Java lui-même, mais peut-être un tiers?

C'est vraiment pratique. Actuellement, j'aimerais implémenter un itérateur qui donne tous les nœuds dans un arbre, soit environ cinq lignes de code avec rendement.


6
Je sais je sais. Mais je pense que connaître plus de langues est plus puissant. De plus, le développement du backend (que je fais) dans l'entreprise pour laquelle je travaille en ce moment se fait en Java, donc je ne peux pas vraiment choisir le langage :(
ripper234

Réponses:


91

Les deux options que je connais sont la bibliothèque infomancers-collections d'Aviad Ben Dov de 2007 et la bibliothèque YieldAdapter de Jim Blackler de 2008 (qui est également mentionnée dans l'autre réponse).

Les deux vous permettront d'écrire du code avec une yield returnconstruction -like en Java, donc les deux satisferont votre demande. Les différences notables entre les deux sont:

Mécanique

La bibliothèque d'Aviad utilise la manipulation de bytecode tandis que celle de Jim utilise le multithreading. En fonction de vos besoins, chacun peut avoir ses propres avantages et inconvénients. Il est probable que la solution d'Aviad soit plus rapide, tandis que celle de Jim est plus portable (par exemple, je ne pense pas que la bibliothèque d'Aviad fonctionnera sur Android).

Interface

La bibliothèque d'Aviad a une interface plus propre - voici un exemple:

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

Alors que Jim est beaucoup plus compliqué, vous obligeant à adeptun générique Collectorqui a une collect(ResultHandler)méthode ... ugh. Cependant, vous pouvez utiliser quelque chose comme ce wrapper autour du code de Jim par Zoom Information, ce qui simplifie grandement cela:

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

Licence

La solution d'Aviad est BSD.

La solution de Jim est du domaine public, tout comme son wrapper mentionné ci-dessus.


3
Réponse fantastique. Non seulement vous avez totalement répondu à la question, mais vous l'avez fait de manière très claire. De plus, j'aime le format de votre réponse et la façon dont vous avez inclus les informations de licence. Continuez à répondre génial! :)
Malcolm

1
N'oubliez pas celui de Guava AbstractIterator.
shmosel

Je viens de publier une autre solution (sous licence MIT) ici, qui lance un fil distinct pour le producteur et met en place une file d'attente délimitée entre le producteur et le consommateur: github.com/lukehutch/Producer
Luke Hutchison

Notez que cela yield(i)sera interrompu à partir du JDK 13, car il yieldest ajouté en tant que nouvelle instruction Java / mot clé réservé.
Luke Hutchison

14

Ces deux approches peuvent être rendues un peu plus propres maintenant que Java a Lambdas. Vous pouvez faire quelque chose comme

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

J'ai expliqué un peu plus ici.


4
Yielderable? Ne devrait-il pas être juste Yieldable? (le verbe étant simplement 'yield', pas 'yielder' ou 'yielderate' ou autre)
Jochem Kuijpers

yield -> { ... }sera interrompu à partir du JDK 13, car il yieldest ajouté en tant que nouvelle instruction Java / mot-clé réservé.
Luke Hutchison

1

Je sais que c'est une question très ancienne ici, et il y a deux manières décrites ci-dessus:

  • la manipulation du bytecode n'est pas si simple lors du portage;
  • basé sur les threads yieldqui a évidemment des coûts de ressources.

Cependant, il existe une autre façon, la troisième et probablement la plus naturelle, d'implémenter le yieldgénérateur en Java qui est l'implémentation la plus proche de ce que font les compilateurs C # 2.0+ pour la yield return/breakgénération: lombok-pg . Il est entièrement basé sur une machine à états et nécessite une coopération étroite avec javacpour manipuler le code source AST. Malheureusement, le support lombok-pg semble être interrompu (aucune activité de référentiel pendant plus d'un an ou deux), et le projet original Lombok n'a malheureusement pas la yieldfonctionnalité (il a un meilleur IDE comme Eclipse, support IntelliJ IDEA, cependant).


1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (action) n'est pas le même que l'opérateur yield, mais il peut être utile d'écrire vos propres générateurs de cette façon:

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

Je suggérerais également si vous utilisez déjà RXJava dans votre projet d'utiliser un Observable comme "yielder". Il peut être utilisé de la même manière si vous créez votre propre 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
    }
}

Les observables peuvent être transformés en itérateurs afin que vous puissiez même les utiliser dans des boucles for plus traditionnelles. RXJava vous offre également des outils vraiment puissants, mais si vous n'avez besoin que de quelque chose de simple, ce serait peut-être exagéré.


0

Je viens de publier une autre solution (sous licence MIT) ici , qui lance le producteur dans un thread séparé et met en place une file d'attente limitée entre le producteur et le consommateur, permettant la mise en mémoire tampon, le contrôle de flux et le pipelining parallèle entre le producteur et le consommateur (donc que le consommateur peut travailler à la consommation de l'article précédent pendant que le producteur travaille à la production de l'article suivant).

Vous pouvez utiliser ce formulaire de classe interne anonyme:

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

par exemple:

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

Ou vous pouvez utiliser la notation lambda pour réduire le passe-partout:

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);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.