114 votes

Y a-t-il un équivalent Java du mot-clé \'yield\' de C#?

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

C'est vraiment pratique. Actuellement, j'aimerais implémenter un itérateur qui renvoie tous les nœuds dans un arbre, ce qui revient à environ cinq lignes de code avec yield.

6 votes

Je sais, je sais. Mais je pense que savoir plus de langues, c'est plus de puissance. De plus, le développement backend (que je fais) dans l'entreprise pour laquelle je travaille actuellement est fait en Java, donc je ne peux pas vraiment choisir le langage :(

95voto

Oak Points 10667

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

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

Mécanismes

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

Interface

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

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

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

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

Licence

La solution de Aviad est sous licence BSD.

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

3 votes

Réponse fantastique. Non seulement avez-vous 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 comment vous avez inclus les informations de licence. Continuez avec les réponses géniales! :)

1 votes

N'oubliez pas de Guava's AbstractIterator.

0 votes

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

23voto

Ichorus Points 2497

Voici un article sur ce sujet et une bibliothèque de Jim Blackler qui le fait uniquement en Java.

14voto

benjiweber Points 386

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

public Yielderable 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.

2voto

Luke Hutchison Points 3228

J'ai publié une autre solution (sous licence MIT) ici, qui lance le producteur dans un thread séparé, et met en place une file d'attente bornée entre le producteur et le consommateur, permettant la mise en tampon, le contrôle de flux et la mise en parallèle des tuyaux entre le producteur et le consommateur (de sorte que le consommateur puisse travailler sur la consommation de l'élément précédent pendant que le producteur travaille sur la production du prochain élément).

Vous pouvez utiliser cette forme de classe interne anonyme :

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

par exemple :

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

Ou vous pouvez utiliser la notation lambda pour réduire le code redondant :

for (Integer item : new Producer(/* queueSize = */ 5, producer -> {
    for (int i = 0; i < 20; i++) {
        System.out.println("Producing " + i);
        producer.produce(i);
    }
    System.out.println("Producteur quitte");
})) {
    System.out.println("  Consommation de " + item);
    Thread.sleep(200);
}

2voto

Lyubomyr Shaydariv Points 1703

Je sais que c'est une question très ancienne ici, et il existe deux moyens décrits ci-dessus :

  • la manipulation du bytecode qui n'est pas si facile lors du portage ;
  • le yield basé sur les threads qui a évidemment des coûts en ressources.

Cependant, il existe une autre, la troisième et probablement la plus naturelle, façon d'implémenter le générateur yield en Java qui est la mise en œuvre la plus proche de ce que les compilateurs C# 2.0+ font pour la génération de yield return/break : lombok-pg. Cela repose entièrement sur une machine à états, et nécessite une étroite collaboration avec javac pour manipuler l'AST du code source. Malheureusement, le support lombok-pg semble avoir été abandonné (aucune activité dans le dépôt depuis plus d'un an ou deux), et le Project Lombok original manque malheureusement de la fonctionnalité yield (bien qu'il ait un meilleur support des IDE comme Eclipse, IntelliJ IDEA).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X