Pode (a == 1 && a == 2 && a == 3) avaliar como verdadeiro em Java?

176

Sabemos que pode em JavaScript .

Mas é possível imprimir a mensagem "Success" na condição fornecida abaixo em Java?

if (a==1 && a==2 && a==3) {
    System.out.println("Success");
}

Alguém sugeriu:

int _a = 1;
int a  = 2;
int a_ = 3;
if (_a == 1 && a == 2 && a_ == 3) {
    System.out.println("Success");
}

Mas, ao fazer isso, estamos alterando a variável real. Existe alguma outra maneira?

Taylor Sen
fonte
5
&&é um andoperador lógico , o que significa que adeve ter os valores 1, 2 e 3 ao mesmo tempo, o que é logicamente impossível. A resposta é NÃO, não é possível. Deseja escrever uma ifdeclaração que verifique se apossui um dos valores 1, 2 OU 3?
Boris Pavlović 22/01
16
Nota para todos: esta pergunta pode vir da mesma pergunta para Javascript: stackoverflow.com/questions/48270127/… ; nesse caso, a resposta éyes
28
@ArthurAttout Não é uma cópia duplicada, essa pergunta é para Javascript, não para Java.
Nos
13
O que usa espaço em branco nos nomes de variáveis ​​também funciona em Java. Mas é claro que isso é basicamente o mesmo que usar _, exceto que você não pode vê-lo.
tobias_k
8
@Seelenvirtuose True, mas if(a==1 && a==2 && a==3)não é necessariamente avaliado ao mesmo tempo. E isso pode ser usado para fazer isso funcionar sem ter que recorrer a truques Unicode.
Erwin Bolwidt 22/01

Respostas:

324

Sim, é muito fácil conseguir isso com vários threads, se você declarar variável acomo volátil.

Um segmento altera constantemente a variável a de 1 para 3, e outro segmento constantemente testa isso a == 1 && a == 2 && a == 3. Isso acontece com frequência suficiente para que um fluxo contínuo de "Sucesso" seja impresso no console.

(Observe que se você adicionar uma else {System.out.println("Failure");}cláusula, verá que o teste falha com muito mais frequência do que com êxito.)

Na prática, ele também funciona sem se declarar avolátil, mas apenas 21 vezes no meu MacBook. Sem volatile, o compilador ou o HotSpot pode armazenar em cache aou substituir a ifinstrução por if (false). Provavelmente, o HotSpot entra em ação depois de um tempo e o compila com instruções de montagem que armazenam o valor em cache a. Com volatile , ele continua imprimindo "Sucesso" para sempre.

public class VolatileRace {
    private volatile int a;

    public void start() {
        new Thread(this::test).start();
        new Thread(this::change).start();
    }

    public void test() {
        while (true) {
            if (a == 1 && a == 2 && a == 3) {
                System.out.println("Success");
            }
        }
    }

    public void change() {
        while (true) {
            for (int i = 1; i < 4; i++) {
                a = i;
            }
        }
    }

    public static void main(String[] args) {
        new VolatileRace().start();
    }
}
Erwin Bolwidt
fonte
83
Este é um exemplo brilhante! Acho que vou roubá-lo da próxima vez eu estou discutindo problemas de concorrência potencialmente perigosos com um dev júnior :)
Alex Pruss
6
Não é muito provável sem volatile, mas, em princípio, isso pode acontecer sem a volatilepalavra-chave e pode acontecer um número arbitrário de vezes, enquanto, por outro lado, não há garantia de que isso aconteça, mesmo com volatile. Mas é claro, tê-lo acontecendo na prática, é silenciosamente impressionante ...
Holger
5
@AlexPruss Eu apenas fiz em todos os devs, e não apenas juniores na minha empresa (eu sabia que poderia ser possível), 87% de sucesso de falhas nas respostas
Eugene
3
@ Holger True, também não há garantias. Em uma arquitetura típica de núcleo múltiplo ou multi-CPU, em que os dois threads são atribuídos a um núcleo separado, é bem provável que isso aconteça volatile. As barreiras de memória criadas para implementar volatilereduzem a velocidade dos encadeamentos e aumentam a probabilidade de eles operarem em sincronia por curtos períodos de tempo. Isso acontece com muito mais frequência do que eu esperava. É muito sensível ao tempo, mas vejo aproximadamente entre 0,2% e 0,8% das avaliações de (a == 1 && a == 2 && a == 3)retorno true.
Erwin Bolwidt 23/01/19
4
Esta é a única resposta que realmente faz com que o código pretendido retorne verdadeiro, em vez de apenas fazer pequenas variações do mesmo. +1
Alejandro
85

Usando conceitos (e código) de uma brilhante resposta de código de golfe ,Integer valores podem ser alterados.

Nesse caso, ele pode fazer intcom que Integers seja igual a s quando normalmente não seriam:

import java.lang.reflect.Field;

public class Test
{
    public static void main(String[] args) throws Exception
    {
        Class cache = Integer.class.getDeclaredClasses()[0];
        Field c = cache.getDeclaredField("cache");
        c.setAccessible(true);
        Integer[] array = (Integer[]) c.get(cache);
        // array[129] is 1
        array[130] = array[129]; // Set 2 to be 1
        array[131] = array[129]; // Set 3 to be 1

        Integer a = 1;
        if(a == (Integer)1 && a == (Integer)2 && a == (Integer)3)
            System.out.println("Success");
    }
}

Infelizmente, não é tão elegante quanto a resposta multithread de Erwin Bolwidt (como esta exige Integercasting) , mas ainda existem algumas travessuras divertidas.

phflack
fonte
Muito interessante. Eu estava brincando de abusar Integer. É uma pena o elenco, mas ainda é legal.
Michael
@ Michael Eu não consigo descobrir como se livrar do elenco. asendo definida como 1/2/3 irá satisfazer a == 1, mas não ir por outro caminho
phflack
4
Eu respondi a essa pergunta há alguns dias em JS / Ruby / Python e Java. A versão menos feia que eu poderia encontrar era usar a.equals(1) && a.equals(2) && a.equals(3), o que obriga 1, 2e 3para ser autoboxed como Integers.
Eric Duminil
@EricDuminil Isso pode tirar um pouco da diversão, porque e se você fizesse auma aula boolean equals(int i){return true;}?
Phdlack
1
Como eu disse, é o menos feio, mas ainda não é o ideal. Com Integer a = 1;a linha anterior, ainda está claro que arealmente é um número inteiro.
Eric Duminil
52

Em esta questão @aioobe sugere (e aconselhar contra a) o uso de C pré-processador para classes Java.

Embora seja extremamente barato, essa é a minha solução:

#define a evil++

public class Main {
    public static void main(String[] args) {
        int evil = 1;
        if (a==1 && a==2 && a==3)
            System.out.println("Success");
    }
}

Se executado usando os seguintes comandos, ele produzirá exatamente um Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main
Pado
fonte
6
Este tipo de sente como executar código através de um localizar e substituir antes de realmente compilá-lo, mas eu definitivamente poderia ver alguém fazer algo assim, como parte de seu fluxo de trabalho
phflack
1
@ phflack Eu gosto, sem dúvida esta pergunta é sobre mostrar os perigos de fazer suposições ao ler código. É fácil assumir que aé uma variável, mas pode ser qualquer parte arbitrária de código em idiomas com um pré-processador.
kevin
2
Não é Java. É a linguagem "Java depois de passar pelo pré-processador C". Brecha semelhante usada por esta resposta. (nota: dissimulados é off-topic para o Código Golf agora)
user202729
40

Como já sabemos que é possível fazer esse código ser avaliado como verdadeiro, graças às ótimas respostas de Erwin Bolwidt e phflack , eu queria mostrar que você precisa manter um alto nível de atenção ao lidar com uma condição semelhante à apresentada na pergunta, pois às vezes o que você vê pode não ser exatamente o que você pensa que é.

Esta é minha tentativa de mostrar que esse código é impresso Success!no console. Eu sei que trapacei um pouco , mas ainda acho que este é um bom lugar para apresentá-lo aqui.

Não importa quais sejam os objetivos de escrever um código como este - é melhor saber como lidar com a situação a seguir e como verificar se você não está errado com o que acha que vê.

Usei o cirílico 'a', que é um caractere distinto do latim 'a'. Você pode inspecionar os caracteres usados ​​na instrução if aqui .

Isso funciona porque os nomes das variáveis ​​são obtidos de diferentes alfabetos. Eles são identificadores distintos, criando duas variáveis ​​distintas com um valor diferente em cada uma.

Observe que, se você deseja que esse código funcione corretamente, a codificação de caracteres precisa ser alterada para uma que suporte os dois caracteres, por exemplo, todas as codificações Unicode (UTF-8, UTF-16 (em BE ou LE), UTF-32 e até UTF-7 ) ou Windows-1251, ISO 8859-5, KOI8-R (obrigado - Thomas Weller e Paŭlo Ebermann - por apontarem):

public class A {
    public static void main(String[] args) {
        int а = 0;
        int a = 1;
        if == 0 && a == 1) {
            System.out.println("Success!");
        }
    }
}

(Espero que você nunca precise lidar com esse tipo de problema em nenhum momento no futuro.)

Przemysław Moskal
fonte
@ Michael O que você quer dizer com não aparece bem? Eu escrevi no celular e aqui parece bom para mim, mesmo ao mudar para a visualização da área de trabalho. Se a resposta for melhor, sinta-se à vontade para editá-la, para deixar claro, já que acho um pouco difícil agora.
Przemysław Moskal
@ Michael Muito obrigado. Eu pensei que o que eu admiti no final da minha resposta é o suficiente :) #
001 Przemysław Moskal
@mohsenmadi Sim, não posso ver aqui no celular, mas tenho certeza de que antes da edição, a última frase era sobre o uso de idiomas diferentes no meu exemplo: P
Przemysław Moskal
Por que █ == 0 é semelhante a um == 1?
Thomas Weller
1
Mais detalhes: você não precisa necessariamente de UTF-8 (embora isso seja recomendado de qualquer maneira), qualquer codificação de caracteres que possua ambos аe afuncione, desde que você informe ao editor em que codificação está (e possivelmente também no compilador). Todas as codificações Unicode funcionam (UTF-8, UTF-16 (em BE ou LE), UTF-32 e até UTF-7), bem como, por exemplo, Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann 23/01
27

Há outra maneira de abordar isso (além da abordagem volátil de corrida de dados que publiquei anteriormente), usando o poder do PowerMock. O PowerMock permite que os métodos sejam substituídos por outras implementações. Quando isso é combinado com o desempacotamento automático, a expressão original (a == 1 && a == 2 && a == 3), sem modificação, pode ser verdadeira.

A resposta do @ phflack depende da modificação do processo de boxe automático em Java que usa a Integer.valueOf(...)chamada. A abordagem abaixo depende da modificação do desbloqueio automático, alterando a Integer.intValue()chamada.

A vantagem da abordagem abaixo é que a declaração if original dada pelo OP na pergunta é usada inalterada, o que eu acho que é a mais elegante.

import static org.powermock.api.support.membermodification.MemberMatcher.method;
import static org.powermock.api.support.membermodification.MemberModifier.replace;

import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@PrepareForTest(Integer.class)
@RunWith(PowerMockRunner.class)
public class Ais123 {
    @Before
    public void before() {
        // "value" is just a place to store an incrementing integer
        AtomicInteger value = new AtomicInteger(1);
        replace(method(Integer.class, "intValue"))
            .with((proxy, method, args) -> value.getAndIncrement());
    }

    @Test
    public void test() {
        Integer a = 1;

        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        } else {
            Assert.fail("(a == 1 && a == 2 && a == 3) != true, a = " + a.intValue());
        }
    }

}
Erwin Bolwidt
fonte
O Powermock pode substituir métodos sintéticos como os access$nnnusados ​​para ler privatecampos de classes internas / externas? Isso permitiria que algumas outras variantes interessantes (que até mesmo o trabalho com uma intvariável) ...
Holger
@ Holger Idéia interessante, entendo o que você quer dizer. Ainda não está funcionando - não está claro o que está impedindo o funcionamento.
Erwin Bolwidt 23/01
Muito bom, era o que eu estava tentando fazer, mas achei que alterar o método estático de classe imutável seria bastante difícil sem manipular o bytecode. Parece que eu estava errado, mas nunca ouvi falar do PowerMock, eu me pergunto como isso acontece. Como uma observação, a resposta do phflack não se baseia na caixa automática: ele altera os endereços do Número inteiro em cache (então os == estão realmente comparando endereços de objetos Número inteiro em vez de valores).
Asoub
2
@Asoub the casting ( (Integer)2) caixas o int. Olhando mais para a reflexão , parece que não é possível fazer isso com unboxing usando a reflexão, mas pode ser possível com Instrumentação vez (ou com PowerMock, como neste resposta)
phflack
@Holger provavelmente não intercepta o método sintético, embora me permita registrar uma substituição access$0e a existência de um método seja verificada no registro. Mas a substituição nunca é invocada.
Erwin Bolwidt
17

Como esse parece ser um acompanhamento dessa pergunta sobre JavaScript , vale a pena notar que esse truque e similares também funcionam em Java:

public class Q48383521 {
    public static void main(String[] args) {
        int a = 1;
        int 2 = 3;
        int a = 3;
        if(aᅠ==1 && a==ᅠ2 && a==3) {
            System.out.println("success");
        }
    }
}

Em Ideone


Mas observe que isso não é a pior coisa que você pode fazer com o Unicode. Usar espaços em branco ou caracteres de controle que sejam partes de identificadores válidos ou letras diferentes com a mesma aparência ainda cria identificadores diferentes e que podem ser identificados, por exemplo, ao fazer uma pesquisa de texto.

Mas esse programa

public class Q48383521 {
    public static void main(String[] args) {
        int ä = 1;
        int ä = 2;
        if(ä == 1 && ä == 2) {
            System.out.println("success");
        }
    }
}

usa dois identificadores iguais, pelo menos do ponto de vista Unicode. Eles apenas usam maneiras diferentes de codificar o mesmo caractere ä, usando U+00E4e U+0061 U+0308.

Em Ideone

Portanto, dependendo da ferramenta que você está usando, elas podem não apenas parecer iguais, as ferramentas de texto habilitadas para Unicode podem até não reportar nenhuma diferença, sempre encontrando as duas ao pesquisar. Você pode até ter o problema de que as diferentes representações se percam ao copiar o código-fonte para outra pessoa, talvez tentando obter ajuda para o "comportamento estranho", tornando-o não reproduzível para o ajudante.

Holger
fonte
3
Eh, essa resposta não cobre o abuso unicode bem o suficiente?
Michael
2
int ᅠ2 = 3;é intencional? Porque, eu estou vendo código muito estranho todo
Ravi
1
@ Michael não exatamente, pois a resposta é sobre o uso de duas letras diferentes (que os IDEs considerariam diferentes ao procurar a), enquanto isso é sobre espaço em branco e, além disso, está dando crédito às respostas ainda mais antigas sobre a questão relacionada ao JavaScript. Note-se que o meu comentário não é ainda mais antiga do que a questão Java (por vários dias) ...
Holger
2
Eu não vejo a distinção. Ambas as respostas estão fornecendo uma solução em que os três identificadores na instrução if são visualmente semelhantes, mas tecnicamente distintos devido ao uso de caracteres unicode incomuns. Seja espaço em branco ou cirílico, é irrelevante.
Michael
2
@ Michael, você pode ver dessa maneira, isso é com você. Como dito, eu queria afirmar que a resposta dada há cinco dias para JavaScript também se aplica ao Java, considerando apenas o histórico da pergunta. Sim, isso é um pouco redundante para outra resposta que não se refere à pergunta JavaScript vinculada. De qualquer forma, enquanto isso, atualizei minha resposta para adicionar um caso que não trata de caracteres visualmente semelhantes, mas de maneiras diferentes de codificar o mesmo caractere e nem mesmo é um “caractere unicode incomum”; é um personagem que estou usando todos os dias ...
Holger
4

Inspirado na excelente resposta do @ Erwin , escrevi um exemplo semelhante, mas usando a API Java Stream .

E uma coisa interessante é que minha solução funciona, mas em casos muito raros (porque o   just-in-timecompilador otimiza esse código).

O truque é desativar as JITotimizações usando a seguinte VMopção:

-Djava.compiler=NONE

Nessa situação, o número de casos de sucesso aumenta significativamente. Aqui está o código:

class Race {
    private static int a;

    public static void main(String[] args) {
        IntStream.range(0, 100_000).parallel().forEach(i -> {
            a = 1;
            a = 2;
            a = 3;
            testValue();
        });
    }

    private static void testValue() {
        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        }
    }
}

Os fluxos paralelos PS são usados ForkJoinPoolsob o capô, e a variável a é compartilhada entre vários threads sem nenhuma sincronização, por isso o resultado é não determinístico.

Oleksandr Pyrohov
fonte
1

Seguindo linhas semelhantes , forçando um flutuador (ou duplo) a estourar (ou estourar) através da divisão (ou multiplicação) por um grande número:

int a = 1;
if (a / Float.POSITIVE_INFINITY == 1 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 2 / Float.POSITIVE_INFINITY
        && a / Float.POSITIVE_INFINITY == 3 / Float.POSITIVE_INFINITY) {
    System.out.println("Success");
}
Aloke
fonte
2
@PatrickRoberts - esse comportamento específico é um fluxo insuficiente de ponto flutuante, conforme os documentos Java . Veja também isto , isto , isto e isto .
Aloke