(A == 1 && a == 2 && a == 3) può essere valutato come vero in Java?


176

Sappiamo che può in JavaScript .

Ma è possibile stampare il messaggio "Success" sulla condizione indicata di seguito in Java?

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

Qualcuno ha suggerito:

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

Ma facendo questo stiamo cambiando la variabile reale. C'è un altro modo?


5
&&è un andoperatore logico , il che significa che adovrebbe avere contemporaneamente i valori 1, 2 e 3, il che è logicamente impossibile. La risposta è NO, impossibile. Vuoi scrivere ifun'istruzione che controlli se aha uno dei valori 1, 2 O 3?
Boris Pavlović il

16
Nota a tutti: questa domanda potrebbe provenire dalla stessa domanda per Javascript: stackoverflow.com/questions/48270127/… , nel qual caso la risposta èyes
nn.

29
@ArthurAttout Non è un duplicato, questa domanda è per Javascript, non per Java.
nn.

13
Quello che usa gli spazi bianchi nei nomi delle variabili funziona anche in Java. Ma ovviamente questo è fondamentalmente lo stesso dell'uso _, tranne che non puoi vederlo.
tobias_k,

8
@Seelenvirtuose True, ma if(a==1 && a==2 && a==3)non è necessariamente valutato allo stesso tempo. E quello può essere usato per fare questo lavoro senza dover ricorrere ai trucchi Unicode.
Erwin Bolwidt,

Risposte:


325

Sì, è abbastanza facile ottenerlo con più thread, se si dichiara variabile acome volatile.

Un thread cambia costantemente la variabile a da 1 a 3 e un altro thread lo verifica costantemente a == 1 && a == 2 && a == 3. Capita spesso di avere un flusso continuo di "Success" stampato sulla console.

(Nota se aggiungi una else {System.out.println("Failure");}clausola, vedrai che il test fallisce molto più spesso di quanto non riesca.)

In pratica, funziona anche senza dichiararsi avolatile, ma solo 21 volte sul mio MacBook. Senza volatile, al compilatore o HotSpot è consentito memorizzare nella cache ao sostituire l' ifistruzione if (false). Molto probabilmente, HotSpot entra in funzione dopo un po 'e lo compila in istruzioni di assemblaggio che memorizzano nella cache il valore di a. Con volatile , continua a stampare "Successo" per 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();
    }
}

83
Questo è un esempio geniale! Penso che lo ruberò la prossima volta che discuterò di problemi di concorrenza potenzialmente pericolosi con uno sviluppatore junior :)
Alex Pruss

6
Non è molto probabile che senza volatile, ma in linea di principio, ciò può accadere anche senza la volatileparola chiave e potrebbe accadere un numero arbitrario di volte, mentre, a portata di mano, non vi è alcuna garanzia che accadrà mai, anche con volatile. Ma ovviamente, accadendo in pratica, è abbastanza impressionante ...
Holger,

5
@AlexPruss L'ho fatto solo con tutti gli sviluppatori, non solo con i giovani della mia compagnia ( sapevo che sarebbe stato possibile), 87% di successo di fallimenti nelle risposte
Eugene,

3
@Holger Vero, nessuna garanzia di nessuno dei due. Su una tipica architettura multi-core o multi-cpu, in cui entrambi i thread vengono assegnati a un core separato, è molto probabile che accada volatile. Le barriere di memoria create per implementare volatilerallentano i thread e rendono più probabile che funzionino in sincronia per brevi periodi di tempo. Succede molto più spesso di quanto mi aspettassi. È molto sensibile ai tempi, ma vedo all'incirca tra lo 0,2% e lo 0,8% delle valutazioni del (a == 1 && a == 2 && a == 3)rendimento true.
Erwin Bolwidt,

4
Questa è l'unica risposta che rende il codice previsto restituito vero invece di apportare solo piccole variazioni allo stesso. +1
Alejandro,

85

Utilizzando i concetti (e il codice) di una brillante risposta di golf del codice , i Integervalori possono essere confusi.

In questo caso, può fare in modo che ints lanciate Integersiano uguali quando normalmente non sarebbero:

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");
    }
}

Sfortunatamente non è così elegante come la risposta multithread di Erwin Bolwidt (poiché questa richiede il Integercasting) , ma si svolgono comunque alcuni divertenti shenanigans.


Molto interessante. Stavo giocando con l'abuso Integer. È un peccato per il casting, ma è comunque bello.
Michael,

@Michael Non riesco proprio a capire come liberarmi del casting. aessere impostato su 1/2/3 soddisferà a == 1, ma non va diversamente
phflack il

4
Ho risposto a questa domanda qualche giorno fa in JS / Ruby / Python e Java. La versione meno brutta che potessi trovare era quella di usare a.equals(1) && a.equals(2) && a.equals(3), che forza 1, 2e 3di essere autoboxata come Integers.
Eric Duminil,

@EricDuminil Questo potrebbe toglierti un po 'di divertimento, perché se facessi auna lezione boolean equals(int i){return true;}?
phflack,

1
Come ho già detto, è il meno brutto ma non è ancora ottimale. Con Integer a = 1;sulla linea prima, è ancora chiaro ache è davvero un numero intero.
Eric Duminil,

52

In questa domanda @aioobe suggerisce (e sconsiglia) l'uso del preprocessore C per le classi Java.

Sebbene sia estremamente economico, questa è la mia soluzione:

#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 eseguito utilizzando i seguenti comandi, ne verrà generato esattamente uno Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main

6
Questo tipo di sensazione sembra eseguire il codice attraverso una ricerca e sostituzione prima di compilarlo, ma potrei sicuramente vedere qualcuno fare qualcosa del genere come parte del loro flusso di lavoro
phflack

1
@phflack Mi piace, probabilmente questa domanda riguarda il mostrare i pericoli di fare ipotesi durante la lettura del codice. È facile supporre che asia una variabile, ma può essere qualsiasi pezzo di codice arbitrario in lingue con un preprocessore.
Kevin

2
Non è Java. È il linguaggio "Java dopo aver passato il preprocessore C". Scappatoia simile usata da questa risposta. (nota: il subdolo è fuori tema per Code Golf ora)
user202729

40

Poiché sappiamo già che è possibile fare in modo che questo codice valuti il ​​vero grazie alle grandi risposte di Erwin Bolwidt e phflack , ho voluto dimostrare che è necessario mantenere un alto livello di attenzione quando si ha a che fare con una condizione che assomiglia a quella presentata nella domanda, come a volte ciò che vedi potrebbe non essere esattamente quello che pensi che sia.

Questo è il mio tentativo di dimostrare che questo codice viene stampato Success!sulla console. So di aver tradito un po ' , ma penso ancora che sia un buon posto per presentarlo proprio qui.

Non importa quali siano gli scopi di scrivere codice come questo: meglio sapere come affrontare la seguente situazione e come verificare se non ti sbagli con ciò che pensi di vedere.

Ho usato il cirillico "a", che è un carattere distinto dal latino "a". Puoi controllare i caratteri usati nell'istruzione if qui .

Questo funziona perché i nomi delle variabili sono presi da diversi alfabeti. Sono identificatori distinti, creando due variabili distinte con un valore diverso in ciascuna.

Nota che se vuoi che questo codice funzioni correttamente, la codifica dei caratteri deve essere cambiata in una che supporti entrambi i caratteri, ad es. Tutte le codifiche Unicode (UTF-8, UTF-16 (in BE o LE), UTF-32, persino UTF-7 ) o Windows-1251, ISO 8859-5, KOI8-R (grazie - Thomas Weller e Paŭlo Ebermann - per averlo sottolineato):

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

(Spero che in futuro non dovrai mai affrontare questo tipo di problema.)


@Michael Cosa vuoi dire che non si presenta bene? L'ho scritto sul cellulare e qui mi sembra ok, anche quando si passa alla visualizzazione desktop. Se renderebbe migliore la mia risposta, sentiti libero di modificarlo, rendilo chiaro perché lo trovo un po 'difficile ora.
Przemysław Moskal,

@Michael Grazie mille. Ho pensato che ciò che ho ammesso alla fine della mia risposta è abbastanza :)
Przemysław Moskal

@mohsenmadi Sì, non posso controllarlo qui sul cellulare, ma sono abbastanza sicuro che prima della modifica, l'ultima frase riguardava l'uso di lingue diverse nel mio esempio: P
Przemysław Moskal

Perché █ == 0 dovrebbe essere simile a a == 1?
Thomas Weller,

1
Più nitpicking: non hai necessariamente bisogno di UTF-8 (sebbene sia comunque raccomandato), qualsiasi codifica dei caratteri che ha entrambi аe afunziona, purché tu dica al tuo editor in che codifica si trova (e forse anche il compilatore). Funzionano tutte le codifiche Unicode (UTF-8, UTF-16 (in BE o LE), UTF-32, anche UTF-7), ad esempio Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann,

27

Esiste un altro modo per affrontarlo (oltre all'approccio volatile di data racing che ho pubblicato in precedenza), utilizzando la potenza di PowerMock. PowerMock consente di sostituire i metodi con altre implementazioni. Se combinato con l'auto-unboxing, l'espressione originale (a == 1 && a == 2 && a == 3), senza modifiche, può essere resa vera.

La risposta di @ phflack si basa sulla modifica del processo di auto-boxing in Java che utilizza la Integer.valueOf(...)chiamata. L'approccio seguente si basa sulla modifica del disinserimento automatico mediante modifica della Integer.intValue()chiamata.

Il vantaggio dell'approccio di seguito è che l'istruzione if originale fornita dall'OP nella domanda viene utilizzata invariata, che ritengo sia la più 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());
        }
    }

}

Powermock può sostituire metodi sintetici come i access$nnnmetodi utilizzati per leggere privatecampi di classi interne / esterne? Ciò consentirebbe alcune altre varianti interessanti (che funzionano anche con una intvariabile) ...
Holger,

@Holger Idea interessante, capisco cosa intendi. Non funziona ancora, non è chiaro cosa gli impedisce di funzionare.
Erwin Bolwidt,

Davvero bello, è quello che stavo cercando di fare, ma ho scoperto che cambiare il metodo statico dalla classe immutabile sarebbe abbastanza difficile senza manipolare il bytecode. Sembra che mi sbagliassi, ma non ho mai sentito parlare di PowerMock, mi chiedo come lo faccia. Come nota a margine, la risposta di phflack non si basa sull'autoboxing: cambia gli indirizzi dell'intero memorizzato nella cache (quindi == in realtà stanno confrontando gli indirizzi degli oggetti interi anziché i valori).
Asoub,

2
@Asoub the casting ( (Integer)2) inscatola l'int. Guardando più in riflessione , sembra che non è possibile fare questo con unboxing utilizzando la riflessione, ma può essere possibile con Strumentazione invece (o con PowerMock, come in questa risposta)
phflack

@Holger probabilmente non intercetta il metodo sintetico, anche se mi permette di registrare un sostituto access$0e l'esistenza di un metodo viene verificata al momento della registrazione. Ma la sostituzione non viene mai invocata.
Erwin Bolwidt,

17

Poiché questo sembra essere un seguito di questa domanda JavaScript , vale la pena notare che questo trucco e simili funzionano anche in 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");
        }
    }
}

Su Ideone


Ma nota che questa non è la cosa peggiore che potresti fare con Unicode. Uso di spazi vuoti o caratteri di controllo che sono parti di identificatori validi o lettere diverse che sembrano uguali crea ancora identificatori diversi e che possono essere individuati, ad esempio quando si esegue una ricerca di testo.

Ma questo programma

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

utilizza due identificatori uguali, almeno dal punto di vista Unicode. Usano solo modi diversi per codificare lo stesso personaggio ä, usando U+00E4e U+0061 U+0308.

Su Ideone

Quindi, a seconda dello strumento che stai utilizzando, potrebbero non solo avere lo stesso aspetto, ma gli strumenti di testo abilitati Unicode potrebbero non segnalare alcuna differenza, trovando sempre entrambi durante la ricerca. Potresti anche avere il problema che le diverse rappresentazioni si perdono durante la copia del codice sorgente a qualcun altro, forse cercando di ottenere aiuto per il "comportamento strano", rendendolo non riproducibile per l'helper.


3
Eh, questa risposta non copre abbastanza bene l'abuso di unicode?
Michael,

2
int ᅠ2 = 3;è intenzionale? Perché vedo un codice molto strano dappertutto
Ravi il

1
@Michael non esattamente, poiché quella risposta riguarda l'uso di due lettere diverse (che gli IDE considererebbero diversi nella ricerca a), mentre si tratta di spazi bianchi, e beh, sta dando credito alle risposte ancora più vecchie sulla relativa domanda JavaScript. Nota che il mio commento è ancora più antico della domanda di Java (di diversi giorni) ...
Holger,

2
Non vedo la distinzione. Entrambe le risposte stanno fornendo una soluzione in cui i tre identificatori nell'istruzione if sono visivamente simili ma tecnicamente distinti in virtù dell'uso di caratteri unicode insoliti. Che si tratti di spazi bianchi o cirillici è irrilevante.
Michael,

2
@Michael potresti vederlo in quel modo, dipende da te. Come detto, volevo affermare che la risposta data cinque giorni fa per JavaScript si applica anche a Java, solo considerando la storia della domanda. Sì, è in qualche modo ridondante per un'altra risposta che non fa riferimento alla domanda JavaScript collegata. Comunque, nel frattempo ho aggiornato la mia risposta per aggiungere un caso che non riguarda personaggi visivamente simili, ma modi diversi di codificare lo stesso personaggio e non è nemmeno un "carattere unicode insolito"; è un personaggio che sto usando ogni giorno ...
Holger,

4

Ispirato dall'ottima risposta di @Erwin , ho scritto un esempio simile, ma utilizzando l' API Java Stream .

E una cosa interessante è che la mia soluzione funziona, ma in casi molto rari (perché il   just-in-timecompilatore ottimizza tale codice).

Il trucco è disabilitare tutte le JITottimizzazioni usando la seguente VMopzione:

-Djava.compiler=NONE

In questa situazione, il numero di casi di successo aumenta in modo significativo. Ecco il codice:

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");
        }
    }
}

PS I flussi paralleli vengono utilizzati ForkJoinPoolsotto il cofano e la variabile a viene condivisa tra più thread senza alcuna sincronizzazione, ecco perché il risultato non è deterministico.


1

Lungo linee simili , forzando un float (o double) a underflow (o overflow) attraverso la divisione (o moltiplicazione) per un numero elevato:

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");
}

2
@PatrickRoberts: questo comportamento specifico è underflow in virgola mobile secondo i documenti Java . Vedi anche questo , questo , questo e questo .
Aloke,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.