Bagaimana cara membandingkan dua bilangan bulat di Jawa dengan benar?

217

Saya tahu bahwa jika Anda membandingkan bilangan bulat primitif kotak dengan konstanta seperti:

Integer a = 4;
if (a < 5)

a secara otomatis akan dihapus dari kotak dan perbandingan akan berfungsi.

Namun, apa yang terjadi ketika Anda membandingkan dua kotak Integersdan ingin membandingkan kesetaraan atau kurang dari / lebih besar dari?

Integer a = 4;
Integer b = 5;

if (a == b)

Akankah kode di atas menghasilkan pengecekan untuk melihat apakah mereka adalah objek yang sama, atau akankah itu dihapus otomatis dalam kasus itu?

Bagaimana dengan:

Integer a = 4;
Integer b = 5;

if (a < b)

?

shmosel
sumber
16
Nah, apa yang terjadi ketika Anda mencoba? Apa yang kamu amati?
Bart Kiers
31
@ Bart Kiers: Eksperimen eksplisit hanya bisa membuktikan, tidak membuktikan bahwa unboxing terjadi. Jika menggunakan ==alih-alih equalsmenghasilkan hasil yang benar, itu mungkin karena nomor kotak sedang diinternir atau digunakan kembali (sebagai optimasi kompiler, mungkin). Alasan untuk mengajukan pertanyaan ini adalah untuk mencari tahu apa yang terjadi secara internal, bukan apa yang tampaknya terjadi. (Setidaknya, itulah sebabnya saya di sini.)
Jim Pivarski
Apa yang terjadi dengan akun Anda?

Jawaban:

303

Tidak, == antara Integer, Long dll akan memeriksa kesetaraan referensi - yaitu

Integer x = ...;
Integer y = ...;

System.out.println(x == y);

ini akan memeriksa apakah xdan ymerujuk ke objek yang sama daripada objek yang sama .

Begitu

Integer x = new Integer(10);
Integer y = new Integer(10);

System.out.println(x == y);

dijamin untuk mencetak false. Menginternir nilai "kecil" yang diautobox dapat menyebabkan hasil yang rumit:

Integer x = 10;
Integer y = 10;

System.out.println(x == y);

Ini akan dicetak true, karena aturan tinju ( JLS bagian 5.1.7 ). Itu masih referensi kesetaraan yang digunakan, tetapi referensi itu benar - benar sama.

Jika nilai p yang sedang kotak adalah bilangan bulat tipe int antara -128 dan 127 inklusif (§3.10.1), atau boolean literal benar atau salah (§3.10.3), atau karakter literal antara '\ u0000' dan '\ u007f' inklusif (§3.10.4), lalu biarkan a dan b menjadi hasil dari dua konversi tinju p. Itu selalu terjadi bahwa a == b.

Secara pribadi saya akan menggunakan:

if (x.intValue() == y.intValue())

atau

if (x.equals(y))

Seperti yang Anda katakan, untuk perbandingan apa pun antara jenis pembungkus ( Integer, Longdll) dan jenis numerik ( int, longdll) nilai jenis pembungkusnya adalah tanpa kotak dan tes diterapkan pada nilai-nilai primitif yang terlibat.

Ini terjadi sebagai bagian dari promosi numerik biner ( JLS bagian 5.6.2 ). Lihatlah dokumentasi masing-masing operator untuk melihat apakah itu diterapkan. Misalnya, dari dokumen untuk ==dan !=( JLS 15.21.1 ):

Jika operan dari operator kesetaraan keduanya bertipe numerik, atau satu bertipe numerik dan yang lainnya dapat dikonversi (§5.1.8) menjadi tipe numerik, promosi numerik biner dilakukan pada operan (§5.6.2).

dan untuk <, <=, >dan >=( JLS 15.20.1 )

Jenis dari masing-masing operan dari operator perbandingan numerik harus merupakan tipe yang dapat dikonversi (§5.1.8) ke tipe numerik primitif, atau kesalahan waktu kompilasi terjadi. Promosi numerik biner dilakukan pada operan (§5.6.2). Jika jenis operan yang dipromosikan adalah int atau panjang, maka perbandingan bilangan bulat yang ditandatangani dilakukan; jika tipe yang dipromosikan ini adalah float atau double, maka perbandingan floating-point dilakukan.

Perhatikan bagaimana semua ini dianggap sebagai bagian dari situasi di mana tidak ada tipe yang merupakan tipe numerik.

Jon Skeet
sumber
2
Apakah ada alasan mengapa seseorang ingin menulis x.compareTo(y) < 0alih-alih x < y?
Max Nanasy
1
@ MaxNanasy: Bukannya saya bisa langsung memikirkan.
Jon Skeet
2
Pada Java 1.6.27+ ada kelebihan yang sama dengan di kelas Integer, jadi harus seefisien memanggil .intValue (). Ini membandingkan nilai sebagai primitif int.
otterslide
Seperti @otterslide katakan, ini tidak perlu lagi di Java 8. Perbandingan Integer dengan Integer adalah berdasarkan nilai secara default.
Axel Prieto
1
@Axel: Penambahan kelebihan tidak akan mengubah perilaku operator ==, bukan? Saya tidak dalam posisi untuk menguji sekarang, tetapi saya akan sangat terkejut jika itu berubah.
Jon Skeet
44

==masih akan menguji kesetaraan objek. Namun, mudah untuk dibodohi:

Integer a = 10;
Integer b = 10;

System.out.println(a == b); //prints true

Integer c = new Integer(10);
Integer d = new Integer(10);

System.out.println(c == d); //prints false

Contoh Anda dengan ketidaksetaraan akan bekerja karena mereka tidak didefinisikan pada Objek. Namun, dengan ==perbandingan, kesetaraan objek masih akan diperiksa. Dalam hal ini, ketika Anda menginisialisasi objek dari primitif kotak, objek yang sama digunakan (untuk a dan b). Ini adalah pengoptimalan yang oke karena kelas kotak primitif tidak dapat diubah.

Adam Lewis
sumber
Saya pikir itu adalah kesetaraan objek yang sedang diuji. Saya mendapat beberapa hasil yang aneh. Haruskah saya menggantinya dengan .equals ()? Juga, apakah Anda merasa saya harus meninggalkan ketidaksetaraan sebagaimana adanya atau melakukannya dengan cara lain juga?
Ada beberapa kasus tepi yang tidak jelas dengan autoboxing. Saya memiliki IDE saya (Eclipse) yang diatur untuk mewarnai apa pun yang un-boxed dalam warna merah, ini telah menyelamatkan saya dari bug pada beberapa kesempatan. Jika Anda membandingkan dua bilangan bulat, gunakan .equals, jika Anda ingin memperjelas ketidaksetaraan Anda, tuliskan para pemerannya secara eksplisit: if ((int) c <(int) d) ...; Anda juga dapat melakukan: c.compareTo (d) <0 // === c <d
Adam Lewis
12
Dan jika Anda mengubah angka literal ke 200, kedua tes akan mencetak false.
Daniel Earwicker
2
... pada sebagian besar implementasi JVM, yaitu. Menurut spesifikasi bahasa hasilnya dapat bervariasi antara implementasi.
Daniel Earwicker
4
Saya pikir lebih jelas untuk menyebut ini "referensi kesetaraan" - dengan begitu jelas apa yang Anda maksud. Saya biasanya mengerti "objek kesetaraan" berarti "hasil equalsdipanggil".
Jon Skeet
28

Karena Java 1.7, Anda dapat menggunakan Objects.equals :

java.util.Objects.equals(oneInteger, anotherInteger);

Mengembalikan nilai true jika argumen sama satu sama lain dan salah jika sebaliknya. Akibatnya, jika kedua argumen adalah nol, true dikembalikan dan jika tepat satu argumen nol, false dikembalikan. Kalau tidak, kesetaraan ditentukan dengan menggunakan metode sama dengan argumen pertama.

Justas
sumber
Ini menangani null sehingga membuatnya sederhana. Terima kasih!
Darren Parker
10

== memeriksa persamaan referensi, namun ketika menulis kode seperti:

Integer a = 1;
Integer b = 1;

Java cukup pintar untuk menggunakan kembali berubah sama untuk adan b, jadi ini benar: a == b. Penasaran, saya menulis contoh kecil untuk menunjukkan di mana java berhenti mengoptimalkan dengan cara ini:

public class BoxingLol {
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            Integer a = i;
            Integer b = i;
            if (a != b) {
                System.out.println("Done: " + i);
                System.exit(0);
            }
        }
        System.out.println("Done, all values equal");
    }
}

Ketika saya mengkompilasi dan menjalankan ini (di mesin saya), saya mendapatkan:

Done: 128
Cory Kendall
sumber
1
tl; dr -1 untuk handwaving; stackoverflow.com/questions/15052216/... stackoverflow.com/questions/20897020/... stackoverflow.com/questions/3131136/integers-caching-in-java dll menjelaskan secara detail masalah yang Anda sebutkan; lebih baik membaca dokumen (atau sumber lib) daripada membuat pseudo-tes dengan risiko lokalitas yang tinggi dari hasil - tidak hanya Anda benar-benar lupa tentang batas bawah cache (yaitu -128 secara default), tidak hanya Anda memiliki satu per satu (
tetapi Anda sama sekali tidak memiliki jaminan untuk menerima hasil yang sama pada mesin apa pun - karena Anda dapat dengan mudah meningkatkan ukuran cache sendiri, YMMV. Juga, pertanyaan OP adalah bagaimana membandingkan dua Bilangan Bulat dengan benar - Anda belum menjawabnya sama sekali .
Saya menghargai pendapat dan persepsi Anda di sini. Saya pikir kami hanya memiliki pendekatan yang berbeda secara fundamental untuk CS.
Cory Kendall
1
ini bukan tentang opini atau persepsi - ini tentang fakta , yang dengan tulus Anda lewatkan. Melakukan pseudo-test tanpa membuktikan apa-apa, tanpa data dukungan keras (dokumen, sumber, dll.) Dan tanpa menjawab pertanyaan OP tidak layak disebut Q&A atau CS yang baik. Adapun "pendekatan yang berbeda" - CS, menurut definisi, adalah ilmu ; apa yang Anda lakukan bukan sains ; itu adalah hal yang menyesatkan (atau itu akan menjadi komentar yang menarik , jika dinyatakan dengan benar) - jika Anda menginginkannya sebagai sains , perbaiki kesalahan mendasar dalam jawaban Anda atau sanggah secara masuk akal , karena itu 'bekerja.
Tentu, saya akan mencoba mengatasi kekurangannya. Saya tidak melupakan batas bawah, saya tidak merasa itu menarik dan memilih untuk tidak memasukkannya. Saya tidak percaya saya memiliki kesalahan dengan satu kesalahan, saya menyatakan cara java (yang saya klarifikasi pada mesin saya, dalam situasi saya) berhenti mengoptimalkan ini, yaitu di 128. Jika saya telah menyatakan nilai maks yang dilakukannya ini untuk, daripada Anda benar jawabannya adalah 127.
Cory Kendall
8

tl; dr pendapat saya adalah menggunakan unary +untuk memicu unboxing pada salah satu operan ketika memeriksa kesetaraan nilai, dan cukup gunakan operator matematika sebaliknya. Dasar pemikirannya sebagai berikut:

Telah disebutkan bahwa ==perbandingan Integeradalah perbandingan identitas, yang biasanya bukan yang diinginkan oleh seorang programmer, dan tujuannya adalah untuk melakukan perbandingan nilai; tetap, saya telah melakukan sedikit ilmu tentang bagaimana melakukan perbandingan itu paling efisien, baik dalam hal kekompakan kode, kebenaran dan kecepatan.

Saya menggunakan banyak metode:

public boolean method1() {
    Integer i1 = 7, i2 = 5;
    return i1.equals( i2 );
}

public boolean method2() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2.intValue();
}

public boolean method3() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2;
}

public boolean method4() {
    Integer i1 = 7, i2 = 5;
    return i1 == +i2;
}

public boolean method5() { // obviously not what we want..
    Integer i1 = 7, i2 = 5;
    return i1 == i2;
}

dan mendapatkan kode ini setelah kompilasi dan dekompilasi:

public boolean method1() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    return var1.equals( var2 );
}

public boolean method2() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method3() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method4() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method5() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2 == var1 ) {
        return true;
    } else {
        return false;
    }
}

Seperti yang dapat Anda lihat dengan mudah, metode 1 memanggil Integer.equals()(jelas), metode 2-4 menghasilkan kode yang persis sama , membuka nilai dengan menggunakan .intValue()dan kemudian membandingkannya secara langsung, dan metode 5 hanya memicu perbandingan identitas, menjadi cara yang salah untuk membandingkan nilai.

Karena (seperti yang telah disebutkan oleh JS misalnya) equals()menimbulkan overhead (yang harus dilakukan instanceofdan pemain yang tidak dicentang), metode 2-4 akan bekerja dengan kecepatan yang sama persis, lebih baik dari metode 1 saat digunakan dalam loop ketat, karena HotSpot tidak kemungkinan untuk mengoptimalkan gips & instanceof.

Ini sangat mirip dengan operator perbandingan lainnya (misalnya </ >) - mereka akan memicu unboxing, sementara menggunakan compareTo()tidak - tetapi kali ini, operasi ini sangat dioptimalkan oleh HS karena intValue()hanya metode pengambil (kandidat utama untuk dioptimalkan keluar).

Menurut pendapat saya, versi 4 yang jarang digunakan adalah cara yang paling ringkas - setiap pengembang C / Java berpengalaman tahu bahwa plus unary dalam banyak kasus sama dengan dilemparkan ke int/ .intValue()- sementara itu mungkin sedikit momen WTF untuk beberapa (kebanyakan mereka yang tidak dapat menggunakan unary plus dalam masa hidup mereka), ini bisa menunjukkan maksud yang paling jelas dan paling singkat - ini menunjukkan bahwa kita menginginkan intnilai dari salah satu operan, memaksa nilai lainnya untuk unbox juga. Ini juga sangat mirip dengan i1 == i2perbandingan reguler yang digunakan untuk intnilai-nilai primitif .

Pilihan saya berlaku untuk i1 == +i2& i1 > i2gaya untuk Integerobjek, baik untuk alasan kinerja & konsistensi. Itu juga membuat kode portabel untuk primitif tanpa mengubah apa pun selain tipe deklarasi. Menggunakan metode bernama sepertinya memperkenalkan kebisingan semantik kepada saya, mirip dengan gaya yang banyak dikritik bigInt.add(10).multiply(-3).


sumber
Bisakah Anda jelaskan apa arti + dalam metode 4? Saya mencoba untuk google tetapi saya hanya mendapatkan penggunaan normal simbol itu (penambahan, penggabungan).
Alex Li
1
@AlexLi artinya persis apa yang saya tulis - unary +(unary plus), lihat misalnya stackoverflow.com/questions/2624410/...
8

Panggilan

if (a == b)

Akan bekerja sebagian besar waktu, tetapi tidak dijamin untuk selalu bekerja, jadi jangan menggunakannya.

Cara paling tepat untuk membandingkan dua kelas Integer untuk kesetaraan, dengan asumsi mereka diberi nama 'a' dan 'b' adalah dengan memanggil:

if(a != null && a.equals(b)) {
  System.out.println("They are equal");
}

Anda juga dapat menggunakan cara ini yang sedikit lebih cepat.

   if(a != null && b != null && (a.intValue() == b.intValue())) {
      System.out.println("They are equal");
    } 

Pada mesin saya 99 miliar operasi membutuhkan waktu 47 detik menggunakan metode pertama, dan 46 detik menggunakan metode kedua. Anda harus membandingkan miliaran nilai untuk melihat perbedaan apa pun.

Perhatikan bahwa 'a' mungkin nol karena ini adalah Objek. Membandingkan dengan cara ini tidak akan menyebabkan pengecualian pointer nol.

Untuk membandingkan lebih besar dan lebih sedikit daripada, gunakan

if (a != null && b!=null) {
    int compareValue = a.compareTo(b);
    if (compareValue > 0) {
        System.out.println("a is greater than b");
    } else if (compareValue < 0) {
        System.out.println("b is greater than a");
    } else {
            System.out.println("a and b are equal");
    }
} else {
    System.out.println("a or b is null, cannot compare");
}
otterslide
sumber
1
if (a==b)hanya berfungsi untuk nilai kecil dan tidak akan berfungsi sebagian besar waktu.
Tony
Ini berfungsi hingga 127 karena itu adalah cache Integer default Java, yang memastikan semua angka hingga 127 memiliki nilai referensi yang sama. Anda dapat mengatur cache agar lebih tinggi dari 127 jika Anda mau, tetapi jangan gunakan == agar aman.
otterslide
3

Kita harus selalu menggunakan metode equals () untuk perbandingan untuk dua bilangan bulat. Adalah praktik yang disarankan.

Jika kita membandingkan dua bilangan bulat menggunakan == yang akan bekerja untuk rentang nilai integer tertentu (Integer dari -128 ke 127) karena optimasi internal JVM.

Silakan lihat contoh:

Kasus 1:

Integer a = 100; Integer b = 100;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Dalam kasus di atas JVM menggunakan nilai a dan b dari kolam renang cache dan kembali objek contoh yang sama (karena alamat memori) dari objek integer dan kita mendapatkan keduanya equal.Its sebuah JVM optimasi dilakukan untuk nilai rentang tertentu.

Kasus 2: Dalam kasus ini, a dan b tidak sama karena tidak datang dengan kisaran -128 hingga 127.

Integer a = 220; Integer b = 220;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Cara yang tepat:

Integer a = 200;             
Integer b = 200;  
System.out.println("a == b? " + a.equals(b)); // true

Saya harap ini membantu.

Siyaram Malav
sumber
1

Dalam kasus saya, saya harus membandingkan dua Integeruntuk persamaan di mana keduanya bisa null. Mencari topik serupa, tidak menemukan sesuatu yang elegan untuk ini. Muncul dengan fungsi utilitas sederhana.

public static boolean integersEqual(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return true;
    }
    if (i1 == null && i2 != null) {
        return false;
    }
    if (i1 != null && i2 == null) {
        return false;
    }
    return i1.intValue() == i2.intValue();
}

//considering null is less than not-null
public static int integersCompare(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return 0;
    }
    if (i1 == null && i2 != null) {
        return -1;
    }
    return i1.compareTo(i2);
}
JackHammer
sumber
-1

Karena metode perbandingan harus dilakukan berdasarkan tipe int (x == y) atau kelas Integer (x.equals (y)) dengan operator yang tepat

public class Example {

    public static void main(String[] args) {
     int[] arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<arr.length-1; j++)
            if((arr[j-1]!=arr[j]) && (arr[j]!=arr[j+1])) 
                System.out.println("int>"+arr[j]);


    Integer[] I_arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<I_arr.length-1; j++)
            if((!I_arr[j-1].equals(I_arr[j])) && (!I_arr[j].equals(I_arr[j+1]))) 
                System.out.println("Interger>"+I_arr[j]);
    }
}
Chronoslog
sumber
-2

metode ini membandingkan dua Integer dengan cek kosong, lihat tes

public static boolean compare(Integer int1, Integer int2) {
    if(int1!=null) {
        return int1.equals(int2);
    } else {
        return int2==null;
    }
    //inline version:
    //return (int1!=null) ? int1.equals(int2) : int2==null;
}

//results:
System.out.println(compare(1,1));           //true
System.out.println(compare(0,1));           //false
System.out.println(compare(1,0));           //false
System.out.println(compare(null,0));        //false
System.out.println(compare(0,null));        //false
System.out.println(compare(null,null));     //true
Alex Torson
sumber
4
Untuk ini, saya pikir akan lebih baik menggunakan Objects.equals(x,y)metode daripada menggulung sendiri.
ryvantage