Existe um Java equivalente à palavra-chave 'yield' do C #?


112

Eu sei que não há equivalente direto no próprio Java, mas talvez um terceiro?

É muito conveniente. Atualmente, gostaria de implementar um iterador que produza todos os nós em uma árvore, que tem cerca de cinco linhas de código com rendimento.


6
Eu sei eu sei. Mas acho que saber mais idiomas é mais poderoso. Além disso, o desenvolvimento de back-end (que estou fazendo) na empresa para a qual trabalho agora está sendo feito em Java, então não posso escolher a linguagem :(
ripper234

Respostas:


91

As duas opções que conheço são a biblioteca infomancers-collection de Aviad Ben Dov de 2007 e a biblioteca YieldAdapter de Jim Blackler de 2008 (que também é mencionada na outra resposta).

Ambos permitirão que você escreva código com a yield returnconstrução -like em Java, então ambos irão satisfazer sua solicitação. As diferenças notáveis ​​entre os dois são:

Mecânica

A biblioteca do Aviad está usando manipulação de bytecode, enquanto a do Jim usa multithreading. Dependendo de suas necessidades, cada um pode ter suas próprias vantagens e desvantagens. É provável que a solução do Aviad seja mais rápida, enquanto a do Jim é mais portátil (por exemplo, não acho que a biblioteca do Aviad funcione no Android).

Interface

A biblioteca do Aviad tem uma interface mais limpa - aqui está um exemplo:

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

Enquanto Jim's é muito mais complicado, exigindo adeptum genérico Collectorque tem um collect(ResultHandler)método ... ugh. No entanto, você pode usar algo como este wrapper em torno do código de Jim por Zoom Information, que simplifica muito isso:

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

Licença

A solução da Aviad é BSD.

A solução de Jim é de domínio público, assim como seu invólucro mencionado acima.


3
Resposta fantástica. Você não apenas respondeu totalmente à pergunta, como o fez de maneira muito clara. Além disso, gostei do formato da sua resposta e de como você incluiu as informações da licença. Continue respondendo demais! :)
Malcolm

1
Não se esqueça do Goiaba AbstractIterator.
shmosel

Acabei de publicar outra solução (licenciada pelo MIT) aqui, que lança um segmento separado para o produtor e configura uma fila limitada entre o produtor e o consumidor: github.com/lukehutch/Producer
Luke Hutchison

Observe que yield(i)será interrompido a partir do JDK 13, já que yieldestá sendo adicionado como uma nova instrução Java / palavra-chave reservada.
Luke Hutchison

14

Ambas as abordagens podem ser um pouco mais claras agora que o Java tem Lambdas. Você pode fazer algo como

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

Eu expliquei um pouco mais aqui.


4
Yielderable? Não deveria ser Yieldable? (o verbo sendo apenas 'render', não 'yielder' ou 'yielderate' ou qualquer outro)
Jochem Kuijpers

yield -> { ... }será interrompido a partir do JDK 13, pois yieldestá sendo adicionado como uma nova instrução Java / palavra-chave reservada.
Luke Hutchison

1

Eu sei que é uma questão muito antiga aqui, e existem duas maneiras descritas acima:

  • manipulação de bytecode que não é tão fácil durante a portabilidade;
  • baseado em thread yieldque obviamente tem custos de recursos.

No entanto, existe outra maneira, a terceira e provavelmente a mais natural, de implementar o yieldgerador em Java que é a implementação mais próxima do que os compiladores C # 2.0+ fazem para yield return/breakgeração: lombok-pg . É totalmente baseado em uma máquina de estado e requer estreita cooperação com a javacpara manipular o código-fonte AST. Infelizmente, o suporte lombok-pg parece ter sido descontinuado (sem atividade de repositório por mais de um ano ou dois), e o Projeto Lombok original infelizmente não tem o yieldrecurso (ele tem um IDE melhor como Eclipse, suporte IntelliJ IDEA, entretanto).


1

Stream.iterate (seed, seedOperator) .limit (n) .foreach (action) não é o mesmo que operador de rendimento, mas pode ser útil escrever seus próprios geradores desta maneira:

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

Eu também sugiro se você já estiver usando RXJava em seu projeto, use um Observable como um "rendimento". Ele pode ser usado de maneira semelhante se você fizer seu próprio Observável.

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

Os observáveis ​​podem ser transformados em iteradores para que você possa até mesmo usá-los em loops for mais tradicionais. Além disso, RXJava oferece ferramentas realmente poderosas, mas se você só precisa de algo simples, talvez isso seja um exagero.


0

Acabei de publicar outra solução (licenciada pelo MIT) aqui , que inicia o produtor em um thread separado e configura uma fila limitada entre o produtor e o consumidor, permitindo buffer, controle de fluxo e pipelining paralelo entre produtor e consumidor (então que o consumidor pode estar trabalhando no consumo do item anterior enquanto o produtor está trabalhando na produção do próximo item).

Você pode usar este formulário de classe interna anônimo:

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

por exemplo:

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 você pode usar a notação lambda para reduzir o clichê:

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);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.