Kann (a == 1 && a == 2 && a == 3) in Java als wahr ausgewertet werden?

176

Wir wissen, dass dies in JavaScript möglich ist .

Aber ist es möglich, die Meldung "Erfolg" unter der unten angegebenen Bedingung in Java zu drucken?

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

Jemand schlug vor:

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

Auf diese Weise ändern wir jedoch die tatsächliche Variable. Gibt es einen anderen Weg?

Taylor Sen.
quelle
5
&&ist ein logischer andOperator, was bedeutet, adass er gleichzeitig den Wert 1, 2 und 3 haben sollte, was logisch unmöglich ist. Die Antwort ist NEIN, nicht möglich. Möchten Sie eine ifAnweisung schreiben , die prüft, ob aeiner der Werte 1, 2 ODER 3 vorliegt?
Boris Pavlović
16
Hinweis für alle: Diese Frage stammt möglicherweise aus derselben Frage für Javascript: stackoverflow.com/questions/48270127/… . In diesem Fall lautet die Antwortyes
Nr.
28
@ArthurAttout Kein Duplikat, diese Frage bezieht sich auf Javascript, nicht auf Java.
Nr.
13
Die Verwendung von Leerzeichen in Variablennamen funktioniert auch in Java. Aber natürlich ist dies im Grunde das gleiche wie bei der Verwendung _, außer dass Sie es nicht sehen können.
tobias_k
8
@Seelenvirtuose True, wird aber if(a==1 && a==2 && a==3)nicht unbedingt gleichzeitig ausgewertet. Und das kann verwendet werden, damit dies funktioniert, ohne auf Unicode-Tricks zurückgreifen zu müssen.
Erwin Bolwidt

Antworten:

324

Ja, es ist ziemlich einfach, dies mit mehreren Threads zu erreichen, wenn Sie die Variable aals flüchtig deklarieren .

Ein Thread ändert ständig die Variable a von 1 auf 3, und ein anderer Thread testet dies ständig a == 1 && a == 2 && a == 3. Es kommt oft genug vor, dass ein kontinuierlicher Stream von "Success" auf der Konsole gedruckt wird.

(Wenn Sie eine else {System.out.println("Failure");}Klausel hinzufügen , werden Sie feststellen, dass der Test weitaus häufiger fehlschlägt als erfolgreich.)

In der Praxis funktioniert es auch, ohne es aals flüchtig zu deklarieren , aber nur 21 Mal auf meinem MacBook. Ohne volatiledarf der Compiler oder HotSpot adie ifAnweisung zwischenspeichern oder durch ersetzen if (false). Höchstwahrscheinlich wird HotSpot nach einer Weile aktiviert und zu Assembly-Anweisungen kompiliert, die den Wert von zwischenspeichern a. Mit volatile druckt es "Erfolg" für immer.

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
quelle
83
Dies ist ein brillantes Beispiel! Ich denke, ich werde es stehlen, wenn ich das nächste Mal potenziell gefährliche Parallelitätsprobleme mit einem Junior-Entwickler diskutiere :)
Alex Pruss
6
Es ist nicht sehr wahrscheinlich ohne volatile, aber im Prinzip kann dies sogar ohne das volatileSchlüsselwort geschehen und es kann beliebig oft vorkommen, während es andererseits keine Garantie gibt, dass es jemals passieren wird, selbst mit volatile. Aber natürlich ist es ziemlich beeindruckend, wenn es in der Praxis passiert…
Holger,
5
@AlexPruss Ich habe gerade bei allen Entwicklern gearbeitet, nicht nur bei Junioren in meiner Firma (ich wusste, dass es möglich sein könnte), 87% Erfolg bei fehlgeschlagenen Antworten
Eugene
3
@ Holger Richtig, auch keine Garantie. Bei einer typischen Architektur mit mehreren Vollkernen oder mehreren CPUs, bei der beide Threads einem separaten Kern zugewiesen werden, ist es jedoch sehr wahrscheinlich, dass dies der Fall ist volatile. Die zur Implementierung erstellten Speicherbarrieren volatileverlangsamen die Threads und erhöhen die Wahrscheinlichkeit, dass sie für kurze Zeit synchron arbeiten. Es passiert viel öfter als ich erwartet hatte. Es ist sehr zeitempfindlich, aber ich sehe ungefähr zwischen 0,2% und 0,8% der Bewertungen der (a == 1 && a == 2 && a == 3)Rendite true.
Erwin Bolwidt
4
Dies ist die einzige Antwort, die tatsächlich dazu führt, dass der beabsichtigte Code true zurückgibt, anstatt nur kleine Variationen desselben vorzunehmen. +1
Alejandro
85

Mit Konzepten (und Code) aus einer brillanten Code-Golf-Antwort können IntegerWerte durcheinander gebracht werden.

In diesem Fall kann ints gegossen werden, Integerum gleich zu sein, wenn dies normalerweise nicht der Fall wäre:

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

Leider ist es nicht ganz so elegant wie Erwin Bolwidts Multithread-Antwort (da diese ein IntegerCasting erfordert ) , aber es finden trotzdem einige lustige Spielereien statt.

phflack
quelle
Sehr interessant. Ich spielte mit Missbrauch Integer. Es ist eine Schande über das Casting, aber es ist immer noch cool.
Michael
@ Michael Ich kann nicht genau herausfinden, wie ich das Casting loswerden kann. aauf 1/2/3 gesetzt zu werden wird befriedigen a == 1, aber es geht nicht in die andere Richtung
phflack
4
Ich habe diese Frage vor einigen Tagen in JS / Ruby / Python und beantwortet Java. Die am wenigsten hässliche Version konnte ich war zu bedienen a.equals(1) && a.equals(2) && a.equals(3), die Kräfte 1, 2und 3als autoboxed werden Integers.
Eric Duminil
@EricDuminil Das könnte ein bisschen Spaß machen, denn was wäre, wenn du aeine Klasse mit machen würdest boolean equals(int i){return true;}?
Phflack
1
Wie gesagt, es ist am wenigsten hässlich, aber es ist immer noch nicht optimal. Mit Integer a = 1;in der vorherigen Zeile ist immer noch klar, dass es sich awirklich um eine Ganzzahl handelt.
Eric Duminil
52

In dieser Frage schlägt @aioobe die Verwendung des C-Präprozessors für Java-Klassen vor (und rät davon ab).

Obwohl es extrem betrügerisch ist, ist das meine Lösung:

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

Bei Ausführung mit den folgenden Befehlen wird genau einer ausgegeben Success:

cpp -P src/Main.java Main.java && javac Main.java && java Main
Pado
quelle
6
Diese Art von fühlt sich an, als würde man Code durch ein Suchen und Ersetzen laufen lassen, bevor man ihn tatsächlich kompiliert, aber ich konnte definitiv jemanden sehen, der so etwas als Teil seines Workflows
tut
1
@phflack Ich mag es, wohl geht es bei dieser Frage darum, die Gefahren aufzuzeigen, wenn beim Lesen von Code Annahmen getroffen werden. Es ist leicht anzunehmen a, dass es sich um eine Variable handelt, es kann sich jedoch um einen beliebigen Code in Sprachen mit einem Präprozessor handeln.
Kevin
2
Es ist nicht Java. Es ist die Sprache "Java nach Übergabe des C-Präprozessors". Ähnliche Lücke von dieser Antwort verwendet. (Hinweis: Underhanded ist für Code Golf jetzt nicht zum Thema )
user202729
40

Da wir bereits wissen, dass es dank der großartigen Antworten von Erwin Bolwidt und phflack möglich ist , diesen Code als wahr auszuwerten , wollte ich zeigen, dass Sie bei der Behandlung einer Bedingung, die wie die vorgestellte aussieht, ein hohes Maß an Aufmerksamkeit auf sich ziehen müssen in der Frage, da manchmal das, was Sie sehen, möglicherweise nicht genau das ist, was Sie denken, dass es ist.

Dies ist mein Versuch zu zeigen, dass dieser Code Success!auf der Konsole gedruckt wird. Ich weiß, dass ich ein bisschen geschummelt habe , aber ich denke immer noch, dass dies ein guter Ort ist, um es hier zu präsentieren.

Egal, wozu das Schreiben von Code wie diesen dient - es ist besser zu wissen, wie man mit der folgenden Situation umgeht und wie man prüft, ob man mit dem, was man zu sehen glaubt, nicht falsch liegt.

Ich habe das kyrillische 'a' verwendet, das sich vom lateinischen 'a' unterscheidet. Sie können die in der if-Anweisung verwendeten Zeichen hier überprüfen .

Dies funktioniert, weil die Namen der Variablen aus verschiedenen Alphabeten stammen. Sie sind unterschiedliche Bezeichner, wodurch zwei unterschiedliche Variablen mit jeweils unterschiedlichen Werten erstellt werden.

Beachten Sie, dass, wenn dieser Code ordnungsgemäß funktionieren soll, die Zeichenkodierung in eine geändert werden muss, die beide Zeichen unterstützt, z. B. alle Unicode-Kodierungen (UTF-8, UTF-16 (in BE oder LE), UTF-32, sogar UTF-7) ) oder Windows-1251, ISO 8859-5, KOI8-R (danke - Thomas Weller und Paŭlo Ebermann - für den Hinweis):

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

(Ich hoffe, Sie werden sich in Zukunft nie mehr mit solchen Problemen befassen müssen.)

Przemysław Moskal
quelle
@ Michael Was meinst du damit, dass es nicht gut auftaucht? Ich habe es auf dem Handy geschrieben und hier sieht es für mich in Ordnung aus, auch wenn ich zur Desktop-Ansicht wechsle. Wenn es meine Antwort verbessern würde, können Sie sie gerne bearbeiten, um es klar zu machen, da ich es jetzt etwas schwierig finde.
Przemysław Moskal
@ Michael Vielen Dank. Ich dachte, dass das, was ich am Ende meiner Antwort zugegeben habe, ausreicht :)
Przemysław Moskal
@mohsenmadi Ja, ich kann es hier auf dem Handy nicht überprüfen, aber ich bin mir ziemlich sicher, dass es im letzten Satz vor der Bearbeitung darum ging, verschiedene Sprachen in meinem Beispiel zu verwenden: P
Przemysław Moskal
Warum sollte █ == 0 a == 1 ähnlich sein?
Thomas Weller
1
Mehr Nitpicking: Sie benötigen nicht unbedingt UTF-8 (obwohl dies sowieso empfohlen wird), eine Zeichenkodierung, die beides hat аund afunktioniert, solange Sie Ihrem Editor mitteilen, in welcher Kodierung es sich befindet (und möglicherweise auch dem Compiler). Alle Unicode-Codierungen funktionieren (UTF-8, UTF-16 (in BE oder LE), UTF-32, sogar UTF-7) sowie z. B. Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann
27

Es gibt eine andere Möglichkeit, dies zu erreichen (zusätzlich zu dem zuvor veröffentlichten Ansatz für flüchtige Datenrennen), bei dem PowerMock verwendet wird. Mit PowerMock können Methoden durch andere Implementierungen ersetzt werden. Wenn dies mit dem automatischen Entpacken kombiniert wird, kann der ursprüngliche Ausdruck (a == 1 && a == 2 && a == 3)ohne Änderung wahr gemacht werden.

Die Antwort von @ phflack basiert auf der Änderung des Auto-Boxing-Prozesses in Java, der den Integer.valueOf(...)Aufruf verwendet. Der folgende Ansatz basiert auf dem Ändern des automatischen Entpackens durch Ändern des Integer.intValue()Anrufs.

Der Vorteil des folgenden Ansatzes besteht darin, dass die ursprüngliche if-Anweisung des OP in der Frage unverändert verwendet wird, was ich für die eleganteste halte.

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
quelle
Kann Powermock Synthesemethoden ersetzen, wie access$nnnsie zum Lesen von privateFeldern innerer / äußerer Klassen verwendet werden? Das würde einige andere interessante Varianten erlauben (die sogar mit einer intVariablen funktionieren )…
Holger
@ Holger Interessante Idee, ich verstehe was du meinst. Es funktioniert noch nicht - nicht klar, was es daran hindert, zu funktionieren.
Erwin Bolwidt
Wirklich schön, das habe ich versucht, aber ich fand, dass es ziemlich schwierig wäre, die statische Methode von einer unveränderlichen Klasse zu ändern, ohne den Bytecode zu manipulieren. Scheint, als hätte ich mich geirrt, aber noch nie von PowerMock gehört. Ich frage mich, wie das geht. Nebenbei bemerkt, die Antwort von phflack basiert nicht auf Autoboxing: Er ändert die Adressen der zwischengespeicherten Ganzzahl (also vergleichen die == tatsächlich Ganzzahl-Objektadressen anstelle von Werten).
Asoub
2
@Asoub das Casting ( (Integer)2) boxt das int. Suchen Sie mehr in Reflexion , es sieht aus wie es nicht möglich ist , dies zu tun mit Unboxing mit Reflexion, kann aber möglich sein , mit Instrumentation statt (oder mit PowerMock, wie es in dieser Antwort)
phflack
@Holger fängt wahrscheinlich keine synthetische Methode ab, obwohl ich damit einen Ersatz für registrieren kann access$0und die Existenz einer Methode bei der Registrierung überprüft wird. Der Ersatz wird jedoch niemals aufgerufen.
Erwin Bolwidt
17

Da dies eine Fortsetzung dieser JavaScript-Frage zu sein scheint , ist es erwähnenswert, dass dieser Trick und ähnliches auch in Java funktioniert:

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

Auf Ideone


Beachten Sie jedoch, dass dies nicht das Schlimmste ist, was Sie mit Unicode tun können. Wenn Sie Leerzeichen oder Steuerzeichen verwenden, die gültige Bezeichnerteile sind, oder verschiedene Buchstaben verwenden, die gleich aussehen, werden immer noch Bezeichner erstellt, die unterschiedlich sind und erkannt werden können, z. B. bei einer Textsuche.

Aber dieses Programm

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

verwendet zwei Bezeichner, die zumindest aus Unicode-Sicht identisch sind. Sie verwenden nur verschiedene Methoden, um dasselbe Zeichen ämit U+00E4und zu codieren U+0061 U+0308.

Auf Ideone

Je nachdem, welches Tool Sie verwenden, sehen sie möglicherweise nicht nur gleich aus. Unicode-fähige Textwerkzeuge melden möglicherweise nicht einmal einen Unterschied und finden bei der Suche immer beide. Möglicherweise haben Sie sogar das Problem, dass die verschiedenen Darstellungen beim Kopieren des Quellcodes auf eine andere Person verloren gehen und möglicherweise versuchen, Hilfe für das „seltsame Verhalten“ zu erhalten, wodurch es für den Helfer nicht reproduzierbar wird.

Holger
quelle
3
Eh, deckt diese Antwort den Unicode-Missbrauch nicht gut genug ab?
Michael
2
int ᅠ2 = 3;ist es beabsichtigt? Weil ich überall sehr seltsamen Code sehe
Ravi
1
@Michael nicht genau, da es bei dieser Antwort um die Verwendung von zwei verschiedenen Buchstaben geht (die IDEs bei der Suche als unterschiedlich betrachten würden a), während es sich um Leerzeichen handelt und die noch älteren Antworten auf die zugehörige JavaScript-Frage berücksichtigt werden . Beachten Sie, dass mein Kommentar dort sogar älter ist als die Java-Frage (um einige Tage)…
Holger
2
Ich sehe den Unterschied nicht. Beide Antworten bieten eine Lösung, bei der die drei Bezeichner in der if-Anweisung visuell ähnlich, aber aufgrund der Verwendung ungewöhnlicher Unicode-Zeichen technisch unterschiedlich sind. Ob es sich um Leerzeichen oder Kyrillisch handelt, spielt keine Rolle.
Michael
2
@Michael du siehst es vielleicht so, das liegt an dir. Wie gesagt, ich wollte sagen, dass die vor fünf Tagen für JavaScript gegebene Antwort auch für Java gilt, wenn man nur den Verlauf der Frage berücksichtigt. Ja, das ist etwas überflüssig für eine andere Antwort, die sich nicht auf die verknüpfte JavaScript-Frage bezieht. In der Zwischenzeit habe ich meine Antwort aktualisiert, um einen Fall hinzuzufügen, bei dem es nicht um visuell ähnliche Zeichen geht, sondern um verschiedene Arten, dasselbe Zeichen zu codieren , und es ist nicht einmal ein „ungewöhnliches Unicode-Zeichen“. Es ist ein Charakter, den ich jeden Tag benutze ...
Holger
4

Inspiriert von der hervorragenden Antwort von @ Erwin habe ich ein ähnliches Beispiel geschrieben, aber die Java Stream API verwendet .

Und eine interessante Sache ist, dass meine Lösung funktioniert, aber in sehr seltenen Fällen (weil der   just-in-timeCompiler einen solchen Code optimiert).

Der Trick besteht darin, JITOptimierungen mit der folgenden VMOption zu deaktivieren :

-Djava.compiler=NONE

In dieser Situation steigt die Anzahl der Erfolgsfälle erheblich an. Hier ist der Code:

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 Parallele Streams werden ForkJoinPoolunter der Haube verwendet, und die Variable a wird ohne Synchronisation von mehreren Threads gemeinsam genutzt. Deshalb ist das Ergebnis nicht deterministisch.

Oleksandr Pyrohov
quelle
1

In ähnlicher Weise , indem ein Float (oder Double) durch Division (oder Multiplikation) durch eine große Zahl zum Unterlauf (oder Überlauf) gezwungen wird:

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
quelle
2
@PatrickRoberts - Dieses spezielle Verhalten ist ein Gleitkomma-Unterlauf gemäß den Java-Dokumenten . Sehen Sie auch dies , dies , dies und das .
Aloke