it-swarm-pt.tech

Loops infinitos em Java

Veja o seguinte loop infinito while em Java. Isso causa um erro em tempo de compilação para a instrução abaixo.

while(true) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //Unreachable statement - compiler-error.

O mesmo loop infinito a seguir while, no entanto, funciona bem e não emite erros nos quais substituí a condição por uma variável booleana.

boolean b=true;

while(b) {
    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

No segundo caso, também, a instrução após o loop é obviamente inacessível porque a variável booleana b é verdadeira, mesmo assim o compilador não reclama. Por quê?


Edit: A versão a seguir de while fica presa em um loop infinito como óbvio, mas não emite erros de compilador para a instrução abaixo dela, mesmo que a condição if dentro do loop é sempre false e, conseqüentemente, o loop nunca pode retornar e pode ser determinado pelo compilador no próprio tempo de compilação.

while(true) {

    if(false) {
        break;
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //No error here.

while(true) {

    if(false)  { //if true then also
        return;  //Replacing return with break fixes the following error.
    }

    System.out.println("inside while");
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

while(true) {

    if(true) {
        System.out.println("inside if");
        return;
    }

    System.out.println("inside while"); //No error here.
}

System.out.println("while terminated"); //Compiler-error - unreachable statement.

Edit: A mesma coisa com if e while.

if(false) {
    System.out.println("inside if"); //No error here.
}

while(false) {
    System.out.println("inside while");
    // Compiler's complain - unreachable statement.
}

while(true) {

    if(true) {
        System.out.println("inside if");
        break;
    }

    System.out.println("inside while"); //No error here.
}      

A seguinte versão de while também fica presa em um loop infinito.

while(true) {

    try {
        System.out.println("inside while");
        return;   //Replacing return with break makes no difference here.
    } finally {
        continue;
    }
}

Isso ocorre porque o bloco finally é sempre executado, mesmo que a instrução return seja encontrada antes no bloco try.

82
Lion

O compilador pode provar fácil e inequivocamente que a primeira expressão sempre resulta em um loop infinito, mas não é tão fácil para o segundo. No seu exemplo de brinquedo, é simples, mas e se:

  • o conteúdo da variável foi lido de um arquivo?
  • a variável não era local e pode ser modificada por outro segmento?
  • a variável contou com alguma entrada do usuário?

O compilador claramente não está verificando seu caso mais simples, porque está abandonando completamente esse caminho. Por quê? Porque é muito mais dificil proibido pelas especificações. Consulte seção 14.21 :

(A propósito, meu compilador reclama quando a variável é declarada final.)

105
Wayne Burkett

De acordo com especificações , o seguinte é dito sobre as declarações while.

Uma instrução while pode ser concluída normalmente se pelo menos um dos seguintes for verdadeiro:

  • A instrução while é alcançável e a expressão da condição não é uma expressão constante com o valor true.
  • Há uma instrução de quebra acessível que sai da instrução while. \

Portanto, o compilador apenas dirá que o código após uma instrução while não pode ser alcançado se a condição while for uma constante com um valor verdadeiro ou se houver uma instrução break dentro do tempo. No segundo caso, como o valor de b não é uma constante, ele não considera o código a seguir inacessível. Há muito mais informações por trás desse link para fornecer mais detalhes sobre o que é e o que não é considerado inacessível.

55
Kibbee

Porque true é constante eb pode ser alterado no loop.

14
hope_is_grim

Como a análise do estado da variável é difícil, o compilador acabou de desistir e permite fazer o que deseja. Além disso, a Especificação da linguagem Java possui regras claras sobre como o compilador tem permissão para detectar código inacessível) .

Existem muitas maneiras de enganar o compilador - outro exemplo comum é

public void test()
{
    return;
    System.out.println("Hello");
}

o que não funcionaria, pois o compilador perceberia que a área era inacessível. Em vez disso, você poderia fazer

public void test()
{
    if (2 > 1) return;
    System.out.println("Hello");
}

Isso funcionaria, pois o compilador é incapaz de perceber que a expressão nunca será falsa.

10
kba

O último não é inacessível. O booleano b ainda tem a possibilidade de ser alterado para falso em algum lugar dentro do loop, causando uma condição final.

6
Cheesegraterr

Meu palpite é que a variável "b" tem a possibilidade de alterar seu valor, de modo que o compilador acha que System.out.println("while terminated"); pode ser alcançado.

4
xuanyuanzhiyuan

Compiladores não são perfeitos - nem deveriam ser

A responsabilidade do compilador é confirmar a sintaxe - não confirmar a execução. Em última análise, os compiladores podem capturar e impedir muitos problemas de tempo de execução em uma linguagem fortemente tipada - mas não podem capturar todos esses erros.

A solução prática é ter baterias de testes de unidade para complementar as verificações de seus compiladores OR) usam componentes orientados a objetos para implementar lógicas conhecidas por serem robustas, em vez de depender de variáveis ​​primitivas e condições de parada.

Digitação forte e OO: aumentando a eficácia do compilador

Alguns erros são de natureza sintática - e em Java, a digitação forte faz com que muitas exceções de tempo de execução possam ser capturadas. Mas, usando tipos melhores, você pode ajudar seu compilador a aplicar uma lógica melhor.

Se você deseja que o compilador imponha a lógica de maneira mais eficiente, em Java, a solução é criar objetos robustos e necessários que possam impor essa lógica e usar esses objetos para construir seu aplicativo, em vez de primitivos.

Um exemplo clássico disso é o uso do padrão de iterador, combinado com o loop foreach de Java, essa construção é menos vulnerável ao tipo de bug que você ilustra do que um loop while simplista.

4
jayunit100

Na verdade, acho que ninguém acertou em cheio (pelo menos não no sentido original do questionador). O OQ continua mencionando:

Correto, mas irrelevante, pois b NÃO está sendo alterado no loop

Mas isso não importa, porque a última linha IS alcançável. Se você pegou esse código, compilou-o em um arquivo de classe e entregou-o a outra pessoa (por exemplo, como uma biblioteca)) poderia vincular a classe compilada ao código que modifica "b" através da reflexão, saindo do loop e causando a execução da última linha.

Isso é verdade para qualquer variável que não seja uma constante (ou final que se compila em uma constante no local em que é usada - às vezes causando erros bizarros se você recompilar a classe com a final e não com uma classe que a referencie, a referência (a classe ainda manterá o valor antigo sem erros)

Eu usei a capacidade de reflexão para modificar variáveis ​​privadas não finais de outra classe para corrigir uma classe em uma biblioteca comprada - corrigindo um bug para que pudéssemos continuar desenvolvendo enquanto esperávamos por correções oficiais do fornecedor.

A propósito, isso pode não funcionar atualmente - embora eu já tenha feito isso antes, há uma chance de que um loop tão pequeno seja armazenado em cache no cache da CPU e, como a variável não está marcada como volátil, o código em cache pode nunca pegue o novo valor. Eu nunca vi isso em ação, mas acredito que é teoricamente verdade.

3
Bill K

O compilador não é sofisticado o suficiente para executar os valores que b podem conter (embora você o atribua apenas uma vez). O primeiro exemplo é fácil para o compilador ver que será um loop infinito porque a condição não é variável.

3
Jonathan M

Estou surpreso que seu compilador se recusou a compilar o primeiro caso. Isso me parece estranho.

Mas o segundo caso não é otimizado para o primeiro caso porque (a) outro encadeamento pode atualizar o valor de b (b) a função chamada pode modificar o valor de b como efeito colateral .

3
sarnold

É simplesmente porque o compilador não faz muito trabalho de babá, embora seja possível.

O exemplo mostrado é simples e razoável para o compilador detectar o loop infinito. Mas que tal inserir 1000 linhas de código sem qualquer relação com a variável b? E essas declarações são todas b = true;? Definitivamente, o compilador pode avaliar o resultado e dizer que é verdade, eventualmente, no loop while, mas quão lento será a compilação de um projeto real?

PS, ferramenta de cotão definitivamente deve fazer isso por você.

3
pinxue

Da perspectiva do compilador, o b em while(b) pode mudar para false em algum lugar. O compilador simplesmente não se incomoda em verificar.

Por diversão, tente while(1 < 2), for(int i = 0; i < 1; i--) etc.

2
SundayMonday

Se o compilador puder determinar conclusivamente que o booleano será avaliado para true no tempo de execução, lançará esse erro. O compilador assume que a variável que você declarou pode seja alterada (embora a gente saiba aqui como humanos, não).

Para enfatizar esse fato, se as variáveis ​​forem declaradas como final em Java, a maioria dos compiladores lançará o mesmo erro como se você substituísse o valor. Isso ocorre porque a variável é definida no tempo de compilação (e não pode ser alterada no tempo de execução) e, portanto, o compilador pode determinar conclusivamente que a expressão é avaliada como true no tempo de execução.

2
Cameron S

A primeira instrução sempre resulta em um loop infinito, porque especificamos uma constante na condição do loop while, onde, como no segundo caso, o compilador assume que existe a possibilidade de alteração do valor do loop interno b.

2
Sheo

As expressões são avaliadas no tempo de execução, portanto, ao substituir o valor escalar "true" por algo como uma variável booleana, você altera um valor escalar para uma expressão booleana e, portanto, o compilador não tem como conhecê-lo no momento da compilação.

2
Wilmer