Czy (a == 1 && a == 2 && a == 3) może mieć wartość true w Javie?


176

Wiemy, że w JavaScript może .

Ale czy można wydrukować komunikat „Sukces” pod warunkiem podanym poniżej w Javie?

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

Ktoś zasugerował:

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

Ale robiąc to, zmieniamy rzeczywistą zmienną. Czy jest inny sposób?


5
&&jest andoperatorem logicznym , co oznacza, że apowinien mieć jednocześnie wartość 1, 2 i 3, co jest logicznie niemożliwe. Odpowiedź brzmi NIE, nie jest możliwa. Czy chcesz napisać ifoświadczenie, które sprawdza, czy ama jedną z wartości 1, 2 LUB 3?
Boris Pavlović

16
Uwaga dla wszystkich: to pytanie może pochodzić z tego samego pytania dla Javascript: stackoverflow.com/questions/48270127/… , w takim przypadku odpowiedź brzmiyes
nr

28
@ArthurAttout To nie jest duplikat, to pytanie dotyczy Javascript, a nie Java.
nr

13
Ten, który używa białych znaków w nazwach zmiennych, działa również w Javie. Ale oczywiście jest to w zasadzie to samo, co używanie _, z wyjątkiem tego, że nie możesz tego zobaczyć.
tobias_k

8
@Seelenvirtuose Prawda, ale if(a==1 && a==2 && a==3)niekoniecznie jest oceniana w tym samym czasie. I to można wykorzystać, aby ta działała bez konieczności uciekania się do sztuczek Unicode.
Erwin Bolwidt

Odpowiedzi:


325

Tak, jest to dość łatwe do osiągnięcia w przypadku wielu wątków, jeśli zadeklarujesz zmienną ajako zmienną.

Jeden wątek stale zmienia zmienną a z 1 na 3, a inny wątek stale to testuje a == 1 && a == 2 && a == 3. Wystarczająco często zdarza się, że na konsoli jest wyświetlany ciągły strumień „Sukces”.

(Zauważ, że jeśli dodasz else {System.out.println("Failure");}klauzulę, zobaczysz, że test kończy się niepowodzeniem znacznie częściej niż sukcesem).

W praktyce działa również bez deklarowania ajako niestabilny, ale tylko 21 razy na moim MacBooku. Bez volatiletego kompilator lub HotSpot mogą buforować alub zamieniać ifinstrukcję na if (false). Najprawdopodobniej HotSpot włącza się po chwili i kompiluje go do instrukcji asemblera, które buforują wartość a. Dzięki volatile temu drukuje „Sukces” na zawsze.

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
To wspaniały przykład! Myślę, że będę go ukraść następnym razem mam omawianiu potencjalnie niebezpieczne problemy współbieżności z junior dev :)
Alex Pruss

6
Nie jest to bardzo prawdopodobne bez volatile, ale w zasadzie może się to zdarzyć nawet bez volatilesłowa kluczowego i może się zdarzyć dowolną liczbę razy, podczas gdy nie ma gwarancji, że kiedykolwiek się to stanie, nawet z volatile. Ale oczywiście, gdy dzieje się to w praktyce, robi wrażenie…
Holger

5
@AlexPruss Właśnie zrobiłem na wszystkich deweloperach, a nie tylko na juniorach w mojej firmie ( wiedziałem, że to możliwe), 87% sukcesu niepowodzeń w odpowiedziach
Eugene

3
@Holger True, żadnych gwarancji. W typowej architekturze wielordzeniowej lub wieloprocesorowej, w której oba wątki są przypisane do oddzielnego rdzenia, jest całkiem prawdopodobne, że tak się stanie volatile. Bariery pamięci stworzone w celu implementacji volatilespowalniają wątki i zwiększają prawdopodobieństwo, że działają one zsynchronizowane przez krótki czas. Zdarza się to znacznie częściej, niż się spodziewałem. Jest bardzo wrażliwy na czas, ale z grubsza widzę, że między 0,2% a 0,8% ocen (a == 1 && a == 2 && a == 3)zwrotu true.
Erwin Bolwidt

4
Jest to jedyna odpowiedź, która w rzeczywistości sprawia, że ​​zamierzony kod zwraca prawdę, zamiast robić tylko małe odmiany tego samego. +1
Alejandro

85

Używając pojęć (i kodu) z genialnej odpowiedzi w kodzie golfa , Integermożna zmylić wartości.

W tym przypadku może sprawić, że ints rzucone na Integers będą równe, podczas gdy normalnie nie byłyby:

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

Niestety nie jest tak elegancka, jak wielowątkowa odpowiedź Erwina Bolwidta (ponieważ ta wymaga Integerrzucania) , ale wciąż ma miejsce kilka zabawnych shenaniganów.


Bardzo interesujące. Bawiłem się znęcaniem Integer. Szkoda castingu, ale nadal jest fajny.
Michael

@Michael Nie wiem, jak pozbyć się castingu. austawienie 1/2/3 zadowoli a == 1, ale nie idzie w drugą stronę
phflack

4
Odpowiedziałem na to pytanie kilka dni temu w JS / Ruby / Python i Java. Najmniej brzydki wersja mogłem znaleźć było użyć a.equals(1) && a.equals(2) && a.equals(3), co zmusza 1, 2a 3być autoboxed jako Integers.
Eric Duminil

@EricDuminil To może trochę zaszkodzić, bo co by było, gdybyś azostał klasą boolean equals(int i){return true;}?
phflack

1
Jak powiedziałem, jest najmniej brzydki, ale nadal nie jest optymalny. Kiedy był Integer a = 1;na linii wcześniej, nadal jest jasne, aże naprawdę jest to liczba całkowita.
Eric Duminil

52

W tym pytaniu @aioobe sugeruje (i odradza) użycie preprocesora C dla klas Java.

Chociaż jest to wyjątkowo oszukańcze, to moje rozwiązanie:

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

Jeśli zostanie wykonany przy użyciu następujących poleceń, wyświetli dokładnie jedno Success:

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

6
Wydaje mi się, że to jak przepuszczanie kodu przez znajdź i zamień przed faktycznym skompilowaniem go, ale zdecydowanie widziałem, jak ktoś robi coś takiego w ramach ich przepływu pracy
phflack

1
@phflack Podoba mi się, prawdopodobnie to pytanie dotyczy pokazania niebezpieczeństw związanych z robieniem założeń podczas czytania kodu. Łatwo założyć, że ajest to zmienna, ale może to być dowolny fragment kodu w językach z preprocesorem.
kevin

2
To nie jest Java. Jest to język „Java po przejściu przez preprocesor C”. Podobna luka używana w tej odpowiedzi. (uwaga: Underhanded jest teraz poza tematem dla Code Golf)
user202729

40

Ponieważ już wiemy, że dzięki świetnym odpowiedziom Erwina Bolwidta i phflacka można sprawić, by ten kod był prawdziwy , chciałem pokazać, że należy zachować wysoki poziom uwagi, gdy mamy do czynienia ze stanem, który wygląda jak ten przedstawiony w pytaniu, ponieważ czasami to, co widzisz, może nie być dokładnie tym, o czym myślisz.

To jest moja próba pokazania, że ​​ten kod jest drukowany Success!na konsoli. Wiem, że trochę oszukiwałem , ale nadal uważam, że to dobre miejsce, aby to tutaj zaprezentować.

Bez względu na cel pisania takiego kodu - lepiej wiedzieć, jak postępować w następującej sytuacji i jak sprawdzić, czy nie mylisz się w tym, co myślisz, że widzisz.

Użyłem cyrylicy „a”, która jest znakiem odrębnym od łacińskiego „a”. Można badać znaki użyte w instrukcji if tutaj .

To działa, ponieważ nazwy zmiennych są pobierane z różnych alfabetów. Są to różne identyfikatory, tworzące dwie odrębne zmienne o innej wartości w każdej.

Zwróć uwagę, że jeśli chcesz, aby ten kod działał poprawnie, kodowanie znaków należy zmienić na obsługujące oba znaki, np. Wszystkie kodowania Unicode (UTF-8, UTF-16 (w BE lub LE), UTF-32, a nawet UTF-7 ) lub Windows-1251, ISO 8859-5, KOI8-R (dziękuję - Thomasowi Wellerowi i Paŭlo Ebermannowi - za wskazanie):

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

(Mam nadzieję, że nigdy nie będziesz musiał zajmować się tego rodzaju problemem w przyszłości.)


@Michael Co przez to rozumiesz? Napisałem to na telefonie komórkowym i tutaj wygląda mi dobrze, nawet po przełączeniu na widok pulpitu. Jeśli poprawiłoby to moją odpowiedź, nie krępuj się ją edytować, aby było jasne, ponieważ teraz wydaje mi się to trochę trudne.
Przemysław Moskal

@Michael Dziękuję bardzo. Myślałem, że to co przyznałem na końcu mojej odpowiedzi wystarczy :)
Przemysław Moskal

@mohsenmadi Tak, nie mogę tego sprawdzić na telefonie komórkowym, ale jestem prawie pewien, że przed edycją ostatnie zdanie dotyczyło używania różnych języków w moim przykładzie: P
Przemysław Moskal

Dlaczego █ == 0 miałoby być podobne do a == 1?
Thomas Weller

1
Więcej czepiania się: niekoniecznie potrzebujesz UTF-8 (chociaż jest to zalecane), dowolnego kodowania znaków, które ma oba te elementy аi adziała, o ile powiesz swojemu edytorowi, w jakim kodowaniu jest (i być może także kompilator). Działają wszystkie kodowania Unicode (UTF-8, UTF-16 (w BE lub LE), UTF-32, nawet UTF-7), a także np. Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann

27

Istnieje inny sposób podejścia do tego (oprócz podejścia opartego na lotnych danych, które opublikowałem wcześniej), wykorzystując moc PowerMock. PowerMock umożliwia zastąpienie metod innymi implementacjami. W połączeniu z automatycznym rozpakowywaniem oryginalne wyrażenie (a == 1 && a == 2 && a == 3), bez modyfikacji, może być prawdziwe.

Odpowiedź @ phflack polega na zmodyfikowaniu procesu auto-boxingu w Javie, który używa Integer.valueOf(...)wywołania. Poniższe podejście polega na zmodyfikowaniu automatycznego rozpakowywania poprzez zmianę Integer.intValue()połączenia.

Zaletą poniższego podejścia jest to, że oryginalne stwierdzenie if podane przez PO w pytaniu jest używane bez zmian, co uważam za najbardziej eleganckie.

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

}

Czy Powermock może zastąpić metody syntetyczne, takie jak access$nnnmetody używane do odczytywania privatepól klas wewnętrznych / zewnętrznych? Pozwoliłoby to na inne interesujące warianty (które działają nawet ze intzmienną)…
Holger

@Holger Ciekawy pomysł, rozumiem, co masz na myśli. Jeszcze nie działa - nie jest jasne, co go powstrzymuje.
Erwin Bolwidt

Naprawdę fajnie, właśnie to próbowałem zrobić, ale stwierdziłem, że zmiana metody statycznej z klasy niezmiennej byłaby dość trudna bez manipulowania kodem bajtowym. Wygląda na to, że się myliłem, chociaż nigdy nie słyszałem o PowerMocku, zastanawiam się, jak to robi. Na marginesie, odpowiedź phflacka nie opiera się na autoboxingu: zmienia adresy cache z pamięci podręcznej Integer (więc == faktycznie porównują adresy obiektów typu Integer zamiast wartości).
Asoub

2
@Asoub the casting ( (Integer)2) boxy int. Patrząc bardziej do refleksji , wygląda na to, że nie jest możliwe, aby to zrobić z unboxing użyciu odbicia, ale może być możliwe z Instrumentation zamiast (lub z PowerMock, jak w tym odpowiedzi)
phflack

@Holger prawdopodobnie nie przechwytuje metody syntetycznej, chociaż pozwala mi zarejestrować zamiennik, access$0a istnienie metody jest sprawdzane przy rejestracji. Ale zamiana nigdy nie jest wywoływana.
Erwin Bolwidt

17

Ponieważ wydaje się, że jest to kontynuacja tego pytania JavaScript , warto zauważyć, że ta sztuczka i podobne działają również w Javie:

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

Na Ideone


Ale pamiętaj, że nie jest to najgorsza rzecz, jaką możesz zrobić z Unicode. Użycie białych znaków lub znaków sterujących, które są prawidłowymi częściami identyfikatora lub użycie różnych liter, które wyglądają tak samo, nadal tworzy identyfikatory, które są różne i można je zauważyć, np. Podczas wyszukiwania tekstu.

Ale ten program

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

używa dwóch identycznych identyfikatorów, przynajmniej z punktu widzenia Unicode. Po prostu używają różnych sposobów kodowania tego samego znaku ä, używając U+00E4i U+0061 U+0308.

Na Ideone

W zależności od używanego narzędzia mogą nie tylko wyglądać tak samo, narzędzia tekstowe z obsługą Unicode mogą nawet nie zgłaszać żadnej różnicy, zawsze znajdując oba podczas wyszukiwania. Możesz nawet mieć problem z tym, że różne reprezentacje giną podczas kopiowania kodu źródłowego komuś innemu, być może próbując uzyskać pomoc dotyczącą „dziwnego zachowania”, uniemożliwiając jego odtworzenie dla osoby pomagającej.


3
Ech, czy ta odpowiedź nie obejmuje wystarczająco dobrze nadużyć Unicode?
Michael

2
int ᅠ2 = 3;czy to jest zamierzone? Ponieważ wszędzie widzę bardzo dziwny kod
Ravi

1
@Michael niezupełnie, ponieważ ta odpowiedź dotyczy użycia dwóch różnych liter (które IDE uznałyby za różne podczas wyszukiwania a), podczas gdy chodzi o białe znaki , i cóż, jest to zasługa nawet starszych odpowiedzi na powiązane pytanie JavaScript. Zauważ, że mój komentarz jest nawet starszy niż pytanie o Javę (o kilka dni)…
Holger

2
Nie widzę różnicy. Obie odpowiedzi dostarczają rozwiązania, w którym trzy identyfikatory w instrukcji if są wizualnie podobne, ale technicznie różne dzięki zastosowaniu nietypowych znaków Unicode. Nie ma znaczenia, czy jest to spacja, czy cyrylica.
Michael

2
@Michael, możesz to postrzegać w ten sposób, to zależy od Ciebie. Jak już powiedziałem, chciałem stwierdzić, że odpowiedź udzielona pięć dni temu na temat JavaScript dotyczy również języka Java, biorąc pod uwagę tylko historię pytania. Tak, to trochę zbędne w stosunku do innej odpowiedzi, która nie odnosi się do pytania JavaScript, do którego prowadzi link. W każdym razie, w międzyczasie zaktualizowałem swoją odpowiedź, dodając przypadek, który nie dotyczy wizualnie podobnych postaci, ale różnych sposobów kodowania tej samej postaci i nie jest to nawet „niezwykły znak Unicode”; to postać, której używam codziennie…
Holger

4

Zainspirowany doskonałą odpowiedzią @ Erwin , napisałem podobny przykład, ale używając Java Stream API .

Ciekawostką jest to, że moje rozwiązanie działa, ale w bardzo rzadkich przypadkach (bo   just-in-timekompilator optymalizuje taki kod).

Sztuczka polega na wyłączeniu wszelkich JIToptymalizacji za pomocą następującej VMopcji:

-Djava.compiler=NONE

W tej sytuacji liczba przypadków sukcesu znacznie wzrasta. Oto kod:

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 Równoległe strumienie są używane ForkJoinPoolpod maską, a zmienna a jest współdzielona między wieloma wątkami bez żadnej synchronizacji, dlatego wynik jest niedeterministyczny.


1

Wzdłuż podobnych linii , zmuszając pływak (lub podwójny) do niedomiaru (lub przepełnienia) przez dzielenie (lub mnożenie) przez dużą liczbę:

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 - to specyficzne zachowanie to niedomiar zmiennoprzecinkowy zgodnie z dokumentacją Java . Zobacz także to , to , to i to .
Aloke
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.