Чи існує Java, еквівалентна ключовому слову "урожайність" C #?


112

Я знаю, що в самій Java немає прямого еквівалента, але, можливо, третя сторона?

Це дійсно зручно. В даний час я хотів би реалізувати ітератор, який видає всі вузли на дереві, тобто приблизно п’ять рядків коду з урожайністю.


6
Я знаю, я знаю. Але я думаю, що знати більше мов - це більше сили. Крім того, розробка резервного сервера (що я роблю) в компанії, над якою зараз працюю, робиться на Java, тому я не можу реально вибрати мову :(
ripper234

Відповіді:


91

Мені відомі два варіанти - бібліотека колекцій рекламних колективів Авіада Бен Дова з 2007 року та бібліотека YieldAdapter Джима Блеклера з 2008 року (про що також йдеться в іншій відповіді).

І yield returnте й інше дозволить вам писати код на конструкції -like на Java, тому обидва задовольнять ваш запит. Помітні відмінності між ними:

Механіка

Бібліотека Aviad використовує маніпуляції байт-кодами, тоді як Джим використовує багатопотоковість. Залежно від ваших потреб, кожен може мати свої переваги та недоліки. Можливо, рішення Aviad швидше, тоді як Джим більш портативний (наприклад, я не думаю, що бібліотека Aviad буде працювати на Android).

Інтерфейс

Бібліотека Aviad має більш чистий інтерфейс - ось приклад:

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

У той час як Джим набагато складніше, вимагає від вас adeptзагального, Collectorякий має collect(ResultHandler)метод ... ух. Однак ви можете використовувати щось подібне до цієї обгортки навколо коду Джима за допомогою Zoom Information, що значно спрощує це:

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

Ліцензія

Рішення Aviad - BSD.

Рішення Джима - це суспільне надбання, і його обгортка згадана вище.


3
Фантастична відповідь. Ви не лише повністю відповідали на запитання, це робили дуже чітко. Також мені подобається формат вашої відповіді та те, як ви включили інформацію про ліцензії. Продовжуйте дивовижні відповіді! :)
Малькольм

1
Не забувайте Гуава AbstractIterator.
shmosel

Я щойно опублікував тут ще одне (ліцензійне MIT) рішення, яке запускає окрему нитку для виробника та встановлює обмежену чергу між виробником та споживачем: github.com/lukehutch/Producer
Люк Хатчісон

Зауважте, що вона yield(i)буде розбита з JDK 13, оскільки yieldдодається як новий оператор Java / зарезервоване ключове слово.
Люк Хатчісон

14

Обидва ці підходи можна зробити трохи чистішими, тепер у Java є Lambdas. Можна зробити щось на кшталт

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

Я трохи більше пояснив тут.


4
Yielderable? Чи не повинно бути так Yieldable? (дієслово просто «врожайність», а не «врожай» або «врожай» чи інше)
Джохем Куйперс,

yield -> { ... }розірветься з JDK 13, оскільки yieldдодається як новий оператор Java / зарезервоване ключове слово.
Люк Хатчісон

1

Я знаю, що тут дуже давнє питання, і є два способи, описані вище:

  • маніпуляція байт-кодом, що не так просто під час перенесення;
  • на основі потоку, yieldщо, очевидно, має ресурсні витрати.

Однак існує ще один, третій і, мабуть, самий природний спосіб реалізації yieldгенератора на Java, який є найближчим до виконання тим, що компілятори C # 2.0+ роблять для yield return/breakпокоління: lombok-pg . Він повністю базується на державній машині і вимагає тісної співпраці з javacманіпулюванням вихідним кодом AST. На жаль, підтримка lombok-pg, здається, припинена (відсутність активності сховища більше року-двох), а в оригінальному Project Lombok, на жаль, немає такої yieldфункції (хоча вона має кращу IDE, як Eclipse, підтримка IntelliJ IDEA).


1

Stream.iterate (насіння, seedOperator) .limit (n) .foreach (дія) не є тим самим, як оператор урожайності, але може бути корисним записати власні генератори таким чином:

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

Я б також запропонував, якщо ви вже використовуєте RXJava у своєму проекті, щоб використовувати 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
    }
}

Спостережні дані можуть бути перетворені в ітератори, так що ви навіть можете використовувати їх у більш традиційних для циклів. Також RXJava надає вам дійсно потужні інструменти, але якщо вам потрібно лише щось просте, то, можливо, це буде надмірне вбивство.


0

Я тільки що опублікував інше (MIT ліцензії) рішення тут , який запускає виробник в окремому потоці, і встановлює обмежену чергу між виробником і споживачем, що дозволяє буферизацию, управління потоком і паралельно конвеєрного між виробником і споживачем (так що споживач може працювати над споживанням попереднього товару, поки виробник працює над виготовленням наступного товару).

Ви можете використовувати цю анонімну форму внутрішнього класу:

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

наприклад:

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

Або ви можете використовувати лямбда-позначення для висічення на котловій панелі:

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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.