Есть ли в Java эквивалент ключевого слова yield в C #?

112

Я знаю, что в самой Java нет прямого эквивалента, но, возможно, стороннего производителя?

Это действительно удобно. В настоящее время я хотел бы реализовать итератор, который выдает все узлы в дереве, что составляет около пяти строк кода с yield.

ripper234
источник
6
Я знаю я знаю. Но я думаю, что знание большего количества языков - большая сила. Кроме того, внутренняя разработка (которой я занимаюсь) в компании, в которой я сейчас работаю, выполняется на Java, поэтому я не могу выбрать язык :(
ripper234

Ответы:

91

Два варианта, о которых я знаю, - это библиотека infomancers-collection Aviad Ben Dov с 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);
        }
    };
}

Я объяснил немного больше здесь.

Benjiweber
источник
4
Yielderable? Разве это не должно быть просто так Yieldable? (глагол просто «уступать», а не «уступать» или «уступать» или что-то еще)
Йохем Куиджперс
yield -> { ... }не будет работать с JDK 13, поскольку yieldдобавляется как новый оператор Java / зарезервированное ключевое слово.
Люк Хатчисон
1

Я знаю, что это очень старый вопрос, и есть два описанных выше способа:

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

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

Любомир Шайдарев
источник
1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (action) - это не то же самое, что оператор yield, но может быть полезно написать свои собственные генераторы следующим образом:

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 в качестве «уступчика». Его можно использовать аналогичным образом, если вы создадите свой собственный 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
    }
}

Наблюдаемые объекты можно преобразовать в итераторы, чтобы их можно было использовать даже в более традиционных циклах for. Также 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);
}
Люк Хатчисон
источник