Pertanyaan ArrayList tidak menggunakan yang ditimpa sama


Saya mengalami masalah dengan mendapatkan ArrayList untuk benar menggunakan equals yang diganti. masalahnya adalah saya mencoba menggunakan persamaan untuk hanya menguji bidang satu kunci, dan menggunakan ArrayList.contains () untuk menguji keberadaan suatu objek dengan bidang yang benar. Berikut ini contohnya

public class TestClass  {
    private static class InnerClass{    
    private final String testKey;
    //data and such

    InnerClass(String testKey, int dataStuff) {
        this.testKey =testKey;
        //etc
    }
    @Override
    public boolean equals (Object in) {
        System.out.println("reached here");
        if(in == null) {
        return false;
        }else if( in instanceof String) {
        String inString = (String) in;
        return testKey == null ? false : testKey.equals(inString);
        }else {
        return false;
        }       
    }       
    }

    public static void main(String[] args) {    
    ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
    //add some entries
    objectList.add(new InnerClass("UNIQUE ID1", 42));
    System.out.println( objectList.contains("UNIQUE ID1")); 
    }    
}

Apa yang membuat saya khawatir adalah bahwa tidak hanya saya salah pada output, tetapi saya juga tidak mendapatkan output "mencapai sini".

Adakah yang punya ide mengapa pengabaian ini sepenuhnya diabaikan? Apakah ada kehalusan dengan override dan kelas batin yang tidak saya ketahui?

Edit: Memiliki masalah dengan situs jadi saya tidak bisa menandai yang dijawab. Terima kasih atas tanggapan cepat: ya kekeliruan di pihak saya bahwa itu adalah String. Sama dengan yang disebut, bukan yang khusus saya. Saya kira itu cek kuno untuk saat ini


10
2017-07-06 13:01


asal


Jawaban:


Jika Anda memeriksa sumber ArrayList, Anda akan melihat panggilannya equals dari objek lain. Dalam kasus Anda itu akan memanggil equals dari String "UNIQUE ID1" yang akan memeriksa bahwa objek lain bukan tipe String dan kembali begitu saja false:

public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

public int indexOf(Object o) {
    ...     
    for (int i = 0; i < size; i++)
    if (o.equals(elementData[i]))
        return i;
    ...
    return -1;
}

Untuk panggilan kasus Anda contains dengan InnerClass yang hanya mengandung id:

objectList.contains(new InnerClass("UNIQUE ID1"))

Jangan lupa untuk menerapkan equals untuk InnerClass yang membandingkan id hanya.


15
2017-07-06 13:05



Menurut JavaDoc dari List.contains(o), itu didefinisikan untuk kembali true 

jika dan hanya jika daftar ini mengandung setidaknya satu elemen e seperti yang (o==null ? e==null : o.equals(e)).

Perhatikan bahwa panggilan definisi ini equals di o, yang merupakan parameter dan tidak elemen yang ada di dalam List.

Karena itu String.equals() akan dipanggil dan tidak InnerClass.equals().

Juga perhatikan itu kontrak untuk Object.equals() menyatakan bahwa

ini simetris: untuk nilai referensi yang tidak nol x dan y, x.equals(y) harus kembali true jika dan hanya jika y.equals(x) kembali true.

Tetapi Anda melanggar batasan ini, karena new TestClass("foo", 1).equals("foo") kembali true tapi "foo".equals(new TestClass("foo", 1)) akan selalu kembali false.

Sayangnya ini berarti bahwa kasus penggunaan Anda (kelas khusus yang bisa sama dengan kelas standar lain) tidak dapat diimplementasikan dengan cara yang sepenuhnya sesuai.

Jika Anda masih ingin melakukan sesuatu seperti ini, Anda harus membaca spesifikasi (dan kadang-kadang implementasi) dari semua kelas koleksi Anda sangat hati-hati dan periksa perangkap seperti ini.


7
2017-07-06 13:06



Anda memohon contains dengan argumen itu adalah a String dan bukan sebuah InnerClass:

System.out.println( objectList.contains("UNIQUE ID1"))

Di JDK saya:

public class ArrayList {

    public boolean contains(Object o) {
    return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
    if (o == null) {
        // omitted for brevity - aix
    } else {
        for (int i = 0; i < size; i++)
        if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
            return i;
    }
    return -1;
    }
}

Perhatikan caranya indexOf panggilan o.equals(). Dalam kasusmu, o adalah String, jadi Anda objectList.contains akan digunakan String.equals dan tidak InnerClass.equals.


3
2017-07-06 13:05



Umumnya, Anda juga harus mengesampingkan hashCode() tetapi ini bukan masalah utama di sini. Anda mengalami asimetris equals(..) metode. Dokumen menjelaskan bahwa itu harus simetris:

Ini simetris: untuk setiap nilai referensi non-nol x dan y, x.equals (y) harus mengembalikan true jika dan hanya jika y.equals (x) mengembalikan nilai true.

Dan apa yang Anda amati adalah perilaku yang tidak terduga karena kontrak rusak.

Buat metode utilitas yang mengiterasi semua item dan verifikasi dengan equals(..) pada string:

public static boolean containsString(List<InnerClass> items, String str) {
    for (InnerClass item : items) {
        if (item.getTestKey().equals(str)) {
           return true;
        }
    }
    return false;
} 

Anda bisa melakukan hal yang sama dengan jambu biji Iterables.any(..) metode:

final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
   @Override
   public boolean apply(InnerClass input){ 
       return input.getTestKey().equals(str);
   }
}

2
2017-07-06 13:06



Implementasi Anda sama salah. Parameter Anda seharusnya tidak menjadi a String. Itu harus menjadi InnerClass.

public boolean equals(Object o) {
  if (this == o) return true;
  if (!(o instanceof InnerClass) return false;
  InnerClass that = (InnerClass)o;
  // check for null keys if you need to
  return this.testKey.equals(that.testKey);
}

(Perhatikan itu instanceof null mengembalikan false, jadi Anda tidak perlu memeriksa null terlebih dahulu).

Anda kemudian akan menguji keberadaan suatu setara objek dalam daftar Anda menggunakan:

objectList.contains(new InnerClass("UNIQUE ID1"));

Tetapi jika Anda benar-benar ingin memeriksa InnerClass dengan kunci String, mengapa tidak digunakan Map<String,InnerClass> sebagai gantinya?


1
2017-07-06 13:06



Meskipun tidak menjawab pertanyaan Anda, banyak Koleksi yang digunakan hashcode(). Anda harus mengesampingkan itu juga untuk "setuju" dengan equals().

Sebenarnya, kamu harus selalu terapkan keduanya equals dan hashcode bersama, dan mereka harus selalu konsisten satu sama lain. Sebagai javadoc untuk Object.equals() menyatakan:

Perhatikan bahwa biasanya diperlukan untuk   mengganti metode hashCode setiap kali   metode ini ditimpa, sehingga   mempertahankan kontrak umum untuk   metode hashCode, yang menyatakan itu   objek yang sama harus memiliki hash yang sama   kode.

Secara khusus, banyak Koleksi bergantung pada kontrak ini yang ditegakkan - perilaku tidak ditentukan sebaliknya.


0
2017-07-06 13:03



Ada beberapa masalah dengan kode Anda. Saran saya adalah untuk menghindari mengabaikan sama sekali jika Anda tidak terbiasa dengannya dan memperluasnya menjadi implementasi baru seperti itu ...

class MyCustomArrayList extends ArrayList<InnerClass>{

    public boolean containsString(String value){
        for(InnerClass item : this){
            if (item.getString().equals(value){
                return true;
            }
        }
        return false;
    }

}

Maka Anda dapat melakukan sesuatu seperti

List myList = new MyCustomArrayList()
myList.containsString("some string");

Saya menyarankan ini karena jika Anda mengesampingkan equals juga harus mengesampingkan hashCode dan sepertinya Anda kurang memiliki sedikit pengetahuan di bidang ini - jadi saya akan menghindarinya.

Juga, metode berisi memanggil metode yang sama itulah sebabnya Anda melihat "mencapai sini". Sekali lagi jika Anda tidak memahami alur panggilan saya hanya akan menghindarinya.


0
2017-07-06 13:15



dengan cara lain, metode yang sama dipanggil jika Anda mengubah kode Anda sebagai berikut. harap ini membersihkan konsepnya.

package com.test;

import java.util.ArrayList;    
import java.util.List;

public class TestClass  {
    private static class InnerClass{    
        private final String testKey;
        //data and such

        InnerClass(String testKey, int dataStuff) {
            this.testKey =testKey;
            //etc
        }

        @Override
        public boolean equals (Object in1) {
            System.out.println("reached here");
            if(in1 == null) {
                return false;
            }else if( in1 instanceof InnerClass) {
                return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
            }else {
                return false;
            }       
        }       
    }

    public static void main(String[] args) {    
        ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
        InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
        InnerClass in2 = new InnerClass("UNIQUE ID1", 42);

        //add some entries
        objectList.add(in1);
        System.out.println( objectList.contains(in2)); 
    }    
}

0
2018-05-23 06:13



Seperti banyak posting katakan, masalahnya adalah bahwa list.indexOf (obj) fungsi panggilan "sama dengan" dari obj, bukan item pada daftar.

Saya memiliki masalah yang sama dan "contains ()" tidak memuaskan saya, karena saya perlu tahu di mana elemennya !. Pendekatan saya adalah membuat elemen kosong dengan hanya parameter untuk membandingkan, dan kemudian memanggil indexOf.

Menerapkan fungsi seperti ini,

public static InnerClass empty(String testKey) {
    InnerClass in = new InnerClass();
    in.testKey =testKey;
    return in;
}

Dan kemudian, panggil indexOf seperti ini:

ind position = list.indexOf(InnerClass.empty(key));

0
2018-05-22 13:48