O Java tem avaliação preguiçosa?

Eu sei que Java tem avaliação inteligente / lenta neste caso:

public boolean isTrue() { boolean a = false; boolean b = true; return b || (a && b); // (a && b) is not evaluated since b is true } 

Mas o que dizer:

 public boolean isTrue() { boolean a = isATrue(); boolean b = isBTrue(); return b || a; } 

Is isATrue() chamado mesmo se isBTrue() retornar true?

Em Java (e outras linguagens semelhantes a C), isso é chamado de avaliação de curto-circuito . *

E sim, no segundo exemplo isATrue é sempre chamado. Ou seja, a menos que o compilador / JVM possa determinar que não tem efeitos colaterais observáveis, nesse caso, pode optar por otimizar, mas, nesse caso, você não notaria a diferença de qualquer maneira.


* Os dois são bem distintos; o primeiro é essencialmente uma técnica de otimização, enquanto o segundo é determinado pela linguagem e pode afetar o comportamento do programa observável.

Originalmente sugeri que isso era bem diferente da avaliação lenta, mas como a @Ingo aponta nos comentários abaixo, isso é uma afirmação duvidosa. Pode-se ver os operadores de curto-circuito em Java como uma aplicação muito limitada de avaliação preguiçosa.

No entanto, quando as linguagens funcionais exigem a semântica da avaliação lenta, geralmente é por uma razão bem diferente, a saber, a prevenção da recursion infinita (ou pelo menos, excessiva).

Bem, no que diz respeito à linguagem – sim, ambas as funções são chamadas.

Se você reescreveu a function para isso:

 public boolean isTrue() { return isBTrue() || isATrue(); } 

então a segunda function não será chamada, se a primeira for verdadeira.


Mas esta é uma avaliação de curto-circuito , não uma avaliação preguiçosa . Caso de avaliação preguiçosa seria algo como isto:

 public interface LazyBoolean { boolean eval(); } class CostlyComparison implements LazyBoolean { private int a, b; public CostlyComparison(int a, int b) { this.a=a; this.b=b; } @Override public boolean eval() { //lots of probably not-always-necessary computation here return a > b; } } public LazyBoolean isATrue() { return new CostlyComparison(10,30); //just an example } public boolean isTrue() { // so now we only pay for creation of 2 objects LazyBoolean a = isATrue(); // but the computation is not performed; LazyBoolean b = isBTrue(); // instead, it's encapsulated in a LazyBoolean return b.eval() || a.eval(); // and will be evaluated on demand; // this is the definition of lazy eval. } 

Não, o Java tem apenas uma avaliação rápida dos methods definidos pelo usuário. Algumas das construções de linguagem do Java implementam uma avaliação não estrita, como você observou. Outros incluem if , ?: while .

Certa vez eu aprendi [1] que existe alguma confusão em torno do que significa “ter avaliação preguiçosa”. Primeiro, avaliação preguiçosa significa avaliação chamada por necessidade . Java não tem nada assim. No entanto, há uma tendência comum (que pessoalmente desencorajo) a afrouxar a definição de avaliação preguiçosa para include também a avaliação chamada por nome . Funções como && não podem ser distinguidas em chamada por necessidade versus avaliação chamada por nome; seria o mesmo, independentemente do que obscurece o assunto.

Levando em conta esse afrouxamento, alguns contra-argumentam alegando que o Java tem uma avaliação preguiçosa do seguinte:

 interface Thunk { A value(); } 

Então, você pode escrever um && definido pelo usuário assim:

 boolean and(boolean p, Thunk q) { return p && q(); } 

A alegação é então apresentada que isso demonstra que o Java tem avaliação preguiçosa. No entanto, esta não é uma avaliação preguiçosa, mesmo no sentido frouxo. O ponto de distinção aqui é que o sistema de tipos do Java não unifica os tipos boolean / Boolean e Thunk . A tentativa de usar um como o outro resultará em um erro de tipo . Na ausência de um sistema de tipo estático, o código ainda falharia. É essa falta de unificação (tipagem estática ou não) que responde à questão; não, Java não possui avaliação lenta definida pelo usuário. É claro que pode ser emulado como acima, mas esta é uma observação desinteressante que decorre da equivalência de turing.

Uma linguagem como a Scala possui avaliação chamada por nome, que permite uma function definida pelo usuário and equivalente à function && regular (levando em consideração a terminação. Ver [1]).

 // note the => annotation on the second argument def and(p: Boolean, q: => Boolean) = p && q 

Isso permite:

 def z: Boolean = z val r: Boolean = and(false, z) 

Observe que esse breve snippet de programa termina fornecendo um valor. Também unifica os valores do tipo Boolean como chamada por nome. Portanto, se você concordar com a definição vaga de avaliação preguiçosa (e, novamente, desencoraje isso), você pode dizer que Scala tem avaliação preguiçosa. Eu ofereço Scala aqui como um bom contraste. Eu recomendo olhar para Haskell para uma avaliação verdadeiramente preguiçosa (chamada por necessidade).

Espero que isto ajude!

[1] http://blog.tmorris.net/posts/a-fling-with-lazy-evaluation/

O SE8 (JDK1.8) introduziu expressões do Lambda , que podem tornar as avaliações mais preguiçosas mais transparentes. Considere a declaração no método principal do seguinte código:

 @FunctionalInterface public interface Lazy { T value(); } class Test { private String veryLongMethod() { //Very long computation return ""; } public static  T coalesce(T primary, Lazy secondary) { return primary != null? primary : secondary.value(); } public static void main(String[] argv) { String result = coalesce(argv[0], ()->veryLongMethod()); } } 

A coalesce da function chamada retorna o primeiro valor não nulo dado (como no SQL). O segundo parâmetro em sua chamada é uma expressão do Lambda. O método veryLongMethod () será chamado somente quando argv [0] == null. A única carga útil nesse caso é inserir ()-> antes do valor a ser avaliado de forma preguiçosa sob demanda.

Para simplificar, você pode usar a interface do fornecedor do java 8 assim:

 Supplier someValSupplier = () -> getSomeValLazily(); 

Então no código depois você pode ter:

 if (iAmLazy) someVal = someValSupplier.get(); // lazy getting the value else someVal = getSomeVal(); // non lazy getting the value 

Só queria adicionar além do que foi mencionado neste tópico de questão, abaixo é da documentação do Oracle na JVM

uma implementação da Java Virtual Machine pode optar por resolver cada referência simbólica em uma class ou interface individualmente quando usada (resolução “tardia” ou “tardia”) ou resolvê-las todas de uma vez quando a class estiver sendo verificada (“ansioso”). ou resolução “estática”). Isso significa que o processo de resolução pode continuar, em algumas implementações, após uma class ou interface ter sido inicializada.

referência

e como um exemplo das classs que tem implementação lenta é o Stream, isso é da Documentação do Oracle no Stream

Riachos são preguiçosos; A computação nos dados de origem é executada apenas quando a operação do terminal é iniciada e os elementos de origem são consumidos apenas quando necessário.

referência

Dito isto, se você fizer o seguinte, nada será exibido. A menos que você adicione o iniciador.

 Steam.of(1, 2, 3, 4, 5).filter(number -> { System.out.println("This is not going to be logged"); return true; }); 

Is isATrue () é chamado se isBTrue () retorna true?

Sim, ambos são chamados.

Não, não tem. isBTrue() será invocado, independentemente do resultado de isATrue() . Você mesmo pode verificar isso escrevendo um programa desse tipo, com instruções de impressão em cada um dos methods.

Sim isATrue() será chamado porque você está chamando-o explicitamente na linha boolean a = isATrue();

Mas não será chamado no caso seguinte se isBTrue() retornar true :

 public boolean isTrue() { return isBTrue() || isATrue(); }