Bisakah (a == 1 && a == 2 && a == 3) dievaluasi menjadi true di Java?


176

Kami tahu itu bisa di JavaScript .

Tetapi apakah mungkin untuk mencetak pesan "Sukses" pada kondisi yang diberikan di bawah ini di Jawa?

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

Seseorang menyarankan:

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

Tetapi dengan melakukan ini kita mengubah variabel aktual. Apakah ada cara lain?


5
&&adalah andoperator logis , yang berarti aharus memiliki nilai 1, 2 dan 3 pada saat yang sama yang secara logika tidak mungkin. Jawabannya adalah TIDAK, tidak mungkin. Apakah Anda ingin menulis ifpernyataan yang memeriksa apakah amemiliki salah satu dari nilai 1, 2 atau 3?
Boris Pavlovic

16
Catatan untuk semua orang: Pertanyaan ini mungkin berasal dari pertanyaan yang sama untuk Javascript: stackoverflow.com/questions/48270127/… , dalam hal ini jawabannya adalahyes
no.

28
@ArthurAttout Bukan duplikat, pertanyaan itu untuk Javascript, bukan Java.
no.

13
Yang menggunakan spasi putih dalam nama variabel juga berfungsi di Jawa. Tapi tentu saja ini pada dasarnya sama dengan menggunakan _, kecuali Anda tidak bisa melihatnya.
tobias_k

8
@Seelenvirtuose Benar, tetapi if(a==1 && a==2 && a==3)belum tentu dievaluasi pada saat yang sama. Dan itu dapat digunakan untuk membuat pekerjaan ini tanpa harus menggunakan trik Unicode.
Erwin Bolwidt

Jawaban:


324

Ya, sangat mudah untuk mencapai ini dengan banyak utas, jika Anda mendeklarasikan variabel asebagai volatile.

Satu utas secara konstan mengubah variabel a dari 1 menjadi 3, dan utas lainnya terus-menerus menguji itu a == 1 && a == 2 && a == 3. Itu terjadi cukup sering untuk memiliki aliran "Sukses" yang dicetak pada konsol.

(Catatan jika Anda menambahkan else {System.out.println("Failure");}klausa, Anda akan melihat bahwa tes gagal jauh lebih sering daripada berhasil.)

Dalam praktiknya, ini juga berfungsi tanpa menyatakan avolatile, tetapi hanya 21 kali di MacBook saya. Tanpa volatile, kompiler atau HotSpot diizinkan untuk melakukan cache aatau mengganti ifpernyataan dengan if (false). Kemungkinan besar, HotSpot menendang setelah beberapa saat dan mengkompilasinya untuk instruksi perakitan yang melakukan cache nilai a. Dengan volatile , itu terus mencetak "Sukses" selamanya.

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
Ini adalah contoh cemerlang! Saya pikir saya akan mencurinya lain kali saya membahas masalah konkurensi yang berpotensi berbahaya dengan seorang yunior :)
Alex Pruss

6
Sangat tidak mungkin tanpa volatile, tetapi pada prinsipnya, ini bahkan dapat terjadi tanpa volatilekata kunci dan itu bisa terjadi beberapa kali secara sewenang-wenang, sementara, di tangan, tidak ada jaminan bahwa hal itu akan pernah terjadi, bahkan dengan volatile. Tapi tentu saja, setelah itu terjadi dalam praktik, cukup mengesankan ...
Holger

5
@AlexPruss Saya baru saja melakukan pada semua devs, bukan hanya junior di perusahaan saya (saya tahu itu mungkin), 87% keberhasilan kegagalan dalam jawaban
Eugene

3
@ Holger Benar, tidak ada jaminan. Pada arsitektur multi-core atau multi-cpu pada umumnya, di mana kedua utas ditugaskan ke inti yang terpisah, kemungkinan besar hal itu akan terjadi volatile. Hambatan memori yang dibuat untuk mengimplementasikan volatilememperlambat utas dan membuatnya lebih mungkin beroperasi dalam sinkronisasi untuk waktu yang singkat. Itu terjadi jauh lebih sering daripada yang saya harapkan. Ini sangat sensitif terhadap waktu, tetapi saya melihat kira-kira antara 0,2% dan 0,8% dari evaluasi (a == 1 && a == 2 && a == 3)pengembalian true.
Erwin Bolwidt

4
Ini adalah satu-satunya jawaban yang benar-benar membuat kode yang dimaksudkan untuk mengembalikan true, bukan hanya membuat variasi kecil yang sama. +1
Alejandro

85

Menggunakan konsep (dan kode) dari jawaban golf kode yang brilian , Integernilai-nilai dapat dikacaukan.

Dalam hal ini, itu dapat membuat ints dicor Integermenjadi sama ketika mereka tidak biasanya:

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

Sayangnya itu tidak seanggun jawaban multithreaded Erwin Bolwidt (karena yang satu ini membutuhkan Integercasting) , tetapi masih ada beberapa kesenangan yang terjadi.


Sangat menarik. Saya mempermainkan pelecehan Integer. Ini memalukan tentang casting tetapi masih keren.
Michael

@Michael Saya tidak tahu bagaimana cara menyingkirkan casting. adiatur ke 1/2/3 akan memuaskan a == 1, tetapi tidak pergi ke arah lain
phflack

4
Saya menjawab pertanyaan ini beberapa hari yang lalu di JS / Ruby / Python dan Java. Versi paling jelek yang bisa saya temukan adalah menggunakan a.equals(1) && a.equals(2) && a.equals(3), yang memaksa 1, 2dan 3untuk diautobox sebagai Integers.
Eric Duminil

@EricDuminil Itu mungkin membuat beberapa kesenangan, karena bagaimana jika Anda dibuat amenjadi kelas dengan boolean equals(int i){return true;}?
phflack

1
Seperti yang saya katakan, itu yang paling jelek tapi masih belum optimal. Dengan Integer a = 1;di telepon sebelumnya, masih jelas abenar-benar Integer.
Eric Duminil

52

Dalam pertanyaan ini @aioobe menyarankan (dan menyarankan agar) penggunaan preprocessor C untuk kelas Java.

Meskipun sangat curang, itu solusi saya:

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

Jika dijalankan menggunakan perintah berikut ini akan menampilkan persis satu Success:

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

6
Jenis ini terasa seperti menjalankan kode melalui penemuan dan penggantian sebelum benar-benar mengompilasinya, tapi saya pasti bisa melihat seseorang melakukan sesuatu seperti itu sebagai bagian dari alur kerja mereka
phflack

1
@ phflack Saya menyukainya, bisa dibilang pertanyaan ini adalah tentang menunjukkan bahaya membuat asumsi ketika membaca kode. Mudah diasumsikan asebagai variabel, tetapi bisa berupa sembarang kode dalam bahasa dengan preprosesor.
kevin

2
Itu bukan Jawa. Ini bahasa "Java setelah melewati C preprocessor". Celah serupa digunakan oleh jawaban ini . (catatan: curang adalah di luar topik untuk Code Golf sekarang)
user202729

40

Seperti yang sudah kita ketahui bahwa itu mungkin untuk membuat kode ini mengevaluasi untuk benar berkat jawaban yang besar dari Erwin Bolwidt dan phflack , saya ingin menunjukkan bahwa Anda perlu menjaga tingkat tinggi perhatian ketika berhadapan dengan kondisi yang terlihat seperti yang disajikan dalam pertanyaan, karena kadang-kadang apa yang Anda lihat mungkin tidak persis seperti yang Anda pikirkan.

Ini adalah usaha saya untuk menunjukkan bahwa kode ini dicetak Success! ke konsol. Saya tahu saya sedikit curang , tetapi saya masih berpikir ini adalah tempat yang bagus untuk menyajikannya di sini.

Tidak masalah apa tujuan penulisan kode seperti ini - lebih baik untuk mengetahui bagaimana menghadapi situasi berikut dan bagaimana memeriksa apakah Anda tidak salah dengan apa yang Anda pikir Anda lihat.

Saya menggunakan Cyrillic 'a' yang merupakan karakter berbeda dari bahasa latin 'a'. Anda dapat memeriksa karakter yang digunakan dalam pernyataan if sini .

Ini berfungsi karena nama-nama variabel diambil dari huruf yang berbeda. Mereka adalah pengidentifikasi yang berbeda, menciptakan dua variabel yang berbeda dengan nilai yang berbeda di masing-masing.

Perhatikan bahwa jika Anda ingin kode ini berfungsi dengan baik, pengkodean karakter perlu diubah menjadi satu yang mendukung kedua karakter, misalnya semua pengkodean Unicode (UTF-8, UTF-16 (dalam BE atau LE), UTF-32, bahkan UTF-7 ), atau Windows-1251, ISO 8859-5, KOI8-R (terima kasih - Thomas Weller dan Paŭlo Ebermann - untuk menunjukkannya):

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

(Saya harap Anda tidak akan pernah harus berurusan dengan masalah semacam itu kapan saja di masa depan.)


@Michael Apa maksudmu itu tidak muncul dengan baik? Saya telah menulisnya di ponsel dan di sini terlihat oke bagi saya, bahkan ketika beralih ke tampilan desktop. Jika itu akan membuat jawaban saya lebih baik, silakan mengeditnya untuk membuatnya jelas karena saya merasa agak sulit sekarang.
Przemysław Moskal

@Michael Terima kasih banyak. Saya pikir apa yang saya akui di akhir jawaban saya sudah cukup :)
Przemysław Moskal

@mohsenmadi Ya, saya tidak bisa memeriksanya di sini di ponsel, tapi saya cukup yakin bahwa sebelum mengedit, kalimat terakhir adalah tentang menggunakan bahasa yang berbeda dalam contoh saya: P
Przemysław Moskal

Mengapa █ == 0 mirip dengan a == 1?
Thomas Weller

1
Lebih banyak nitpicking: Anda tidak perlu UTF-8 (meskipun itu disarankan), penyandian karakter apa pun yang berfungsi аdan aberfungsi, selama Anda memberi tahu editor Anda tentang penyandiannya (dan mungkin juga kompiler). Semua penyandian Unicode berfungsi (UTF-8, UTF-16 (dalam BE atau LE), UTF-32, bahkan UTF-7), serta misalnya Windows-1251, ISO 8859-5, KOI8-R.
Paŭlo Ebermann

27

Ada cara lain untuk melakukan pendekatan ini (selain pendekatan balap data yang mudah menguap yang saya posting sebelumnya), menggunakan kekuatan PowerMock. PowerMock memungkinkan metode diganti dengan implementasi lain. Ketika itu dikombinasikan dengan auto-unboxing, ekspresi asli (a == 1 && a == 2 && a == 3), tanpa modifikasi, dapat dibuat benar.

Jawaban @ phflack bergantung pada memodifikasi proses tinju otomatis di Jawa yang menggunakan Integer.valueOf(...)panggilan. Pendekatan di bawah ini bergantung pada modifikasi auto-unboxing dengan mengubah Integer.intValue()panggilan.

Keuntungan dari pendekatan di bawah ini adalah bahwa pernyataan if-asli yang diberikan oleh OP dalam pertanyaan tersebut digunakan tidak berubah, yang menurut saya paling elegan.

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

}

Dapatkah Powermock mengganti metode sintetis seperti access$nnnmetode yang digunakan untuk membaca privatebidang kelas dalam / luar? Itu akan memungkinkan beberapa varian menarik lainnya (yang bahkan bekerja dengan intvariabel) ...
Holger

@ Holger Ide yang menarik, saya mengerti maksud Anda. Ini belum berfungsi - tidak jelas apa yang menghentikannya bekerja.
Erwin Bolwidt

Sangat bagus, itulah yang saya coba lakukan, tetapi saya menemukan bahwa mengubah metode statis dari kelas yang tidak dapat diubah akan sangat sulit tanpa memanipulasi bytecode. Sepertinya saya salah, tidak pernah mendengar tentang PowerMock, saya bertanya-tanya bagaimana cara melakukannya. Sebagai catatan, jawaban phflack tidak bergantung pada autoboxing: ia mengubah alamat dari Integer yang di-cache (jadi == sebenarnya membandingkan objek Integer yang menekan alamat daripada nilai).
Asoub

2
@Asoub the casting ( (Integer)2) mengotak int. Melihat lebih ke refleksi , sepertinya itu tidak mungkin untuk melakukan hal ini dengan pembukaan kemasan menggunakan refleksi, tetapi mungkin mungkin dengan Instrumentasi bukan (atau dengan PowerMock, seperti dalam jawaban ini)
phflack

@ Holger itu mungkin tidak mencegat metode sintetis, meskipun memungkinkan saya untuk mendaftarkan pengganti access$0dan keberadaan metode diperiksa saat pendaftaran. Tetapi penggantian tidak pernah diminta.
Erwin Bolwidt

17

Karena ini tampaknya merupakan tindak lanjut dari pertanyaan JavaScript ini , perlu dicatat bahwa trik ini dan yang serupa juga berfungsi di Jawa:

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

Tentang Ideone


Tetapi perhatikan bahwa ini bukan hal terburuk yang dapat Anda lakukan dengan Unicode. Menggunakan spasi putih atau karakter kontrol yang merupakan bagian pengidentifikasi yang valid atau menggunakan huruf berbeda yang terlihat sama masih membuat pengidentifikasi yang berbeda dan dapat terlihat, misalnya saat melakukan pencarian teks.

Tapi program ini

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

menggunakan dua pengidentifikasi yang sama, setidaknya dari sudut pandang Unicode. Mereka hanya menggunakan berbagai cara untuk menyandikan karakter yang sama ä, menggunakan U+00E4dan U+0061 U+0308.

Tentang Ideone

Jadi tergantung pada alat yang Anda gunakan, mereka mungkin tidak hanya terlihat sama, alat teks yang diaktifkan Unicode bahkan tidak dapat melaporkan perbedaan, selalu menemukan keduanya saat mencari. Anda bahkan mungkin memiliki masalah bahwa representasi yang berbeda hilang ketika menyalin kode sumber ke orang lain, mungkin mencoba untuk mendapatkan bantuan untuk "perilaku aneh", membuatnya tidak dapat direproduksi untuk helper.


3
Eh, bukankah jawaban ini mencakup penyalahgunaan unicode dengan cukup baik?
Michael

2
int ᅠ2 = 3;apakah itu disengaja? Karena, saya melihat kode yang sangat aneh di sekitar
Ravi

1
@Michael tidak persis, karena jawaban itu adalah tentang menggunakan dua huruf yang berbeda (yang mana IDE akan anggap berbeda ketika mencari a), sedangkan ini adalah tentang ruang putih, dan yah, itu memberi pujian untuk jawaban yang bahkan lebih lama pada pertanyaan JavaScript terkait. Perhatikan bahwa komentar saya bahkan lebih tua dari pertanyaan Java (beberapa hari) ...
Holger

2
Saya tidak melihat perbedaannya. Kedua jawaban tersebut memberikan solusi di mana ketiga pengidentifikasi dalam pernyataan if secara visual serupa tetapi secara teknis berbeda berdasarkan penggunaan karakter unicode yang tidak biasa. Apakah itu spasi putih atau Cyrillic tidak relevan.
Michael

2
@Michael Anda mungkin melihatnya seperti itu, itu terserah Anda. Seperti yang saya katakan, saya ingin menyatakan bahwa jawaban yang diberikan lima hari yang lalu untuk JavaScript juga berlaku untuk Java, hanya mempertimbangkan sejarah pertanyaannya. Ya, itu agak berlebihan untuk jawaban lain yang tidak merujuk pada pertanyaan JavaScript yang ditautkan. Bagaimanapun, sementara itu saya memperbarui jawaban saya untuk menambahkan sebuah kasus yang bukan tentang karakter yang secara visual mirip, tetapi berbagai cara untuk menyandikan karakter yang sama dan itu bahkan bukan "karakter unicode yang tidak biasa"; itu karakter yang saya gunakan setiap hari ...
Holger

4

Terinspirasi oleh jawaban luar biasa dari Erwin , saya menulis contoh serupa, tetapi menggunakan Java Stream API .

Dan hal yang menarik adalah solusi saya berfungsi, tetapi dalam kasus yang sangat jarang (karena   just-in-timekompiler mengoptimalkan kode seperti itu).

Caranya adalah dengan menonaktifkan semua JIToptimasi menggunakan VMopsi berikut :

-Djava.compiler=NONE

Dalam situasi ini, jumlah kasus sukses meningkat secara signifikan. Ini kodenya:

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 Aliran paralel digunakan di ForkJoinPoolbawah tenda, dan variabel a dibagi di antara beberapa utas tanpa sinkronisasi apa pun, itu sebabnya hasilnya non-deterministik.


1

Sepanjang garis yang sama , dengan memaksa float (atau dobel) ke underflow (atau overflow) melalui pembagian (atau multiplikasi) dengan sejumlah besar:

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 - perilaku spesifik ini adalah floating point underflow sesuai dengan dokumen Java . Juga lihat ini , ini , ini & ini .
Aloke
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.