Daftar Java. Memuat (Objek dengan nilai bidang sama dengan x)

199

Saya ingin memeriksa apakah suatu Listberisi objek yang memiliki bidang dengan nilai tertentu. Sekarang, saya bisa menggunakan loop untuk memeriksa dan memeriksa, tetapi saya ingin tahu apakah ada kode yang lebih efisien.

Sesuatu seperti;

if(list.contains(new Object().setName("John"))){
    //Do some stuff
}

Saya tahu kode di atas tidak melakukan apa-apa, hanya untuk menunjukkan secara kasar apa yang saya coba capai.

Juga, hanya untuk memperjelas, alasan saya tidak ingin menggunakan loop sederhana adalah karena kode ini saat ini akan masuk ke dalam loop yang ada di dalam loop yang berada di dalam loop. Agar mudah dibaca, saya tidak ingin terus menambahkan loop ke loop ini. Jadi saya bertanya-tanya apakah ada alternatif (ish) sederhana.

Rudi Kershaw
sumber
5
Karena ini adalah kesetaraan khusus, Anda harus menulis kode khusus.
Sotirios Delimanolis
Sasaran yang dinyatakan dan contoh kode Anda tampaknya tidak cocok. Apakah Anda ingin membandingkan objek hanya berdasarkan satu nilai bidang?
Duncan Jones
1
Mengganti equals(Object)metode objek kustom Anda?
Josh M
1
for(Person p:list) if (p.getName().equals("John") return true; return false;Saya tidak akan menemukan cara yang lebih ringkas di Jawa.
MikeFHay
@ Rajdeep maaf, saya tidak mengerti pertanyaan Anda. p.equals(p) harus selalu benar, jadi saya bingung apa yang ingin Anda capai. Semoga jika Anda mengajukan pertanyaan baru Anda bisa mendapatkan bantuan yang lebih baik.
MikeFHay

Jawaban:

267

Streaming

Jika Anda menggunakan Java 8, mungkin Anda bisa mencoba sesuatu seperti ini:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}

Atau sebagai alternatif, Anda dapat mencoba sesuatu seperti ini:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}

Metode ini akan kembali truejika List<MyObject>berisi MyObjectdengan nama name. Jika Anda ingin melakukan operasi pada setiap MyObjects itu getName().equals(name), maka Anda dapat mencoba sesuatu seperti ini:

public void perform(final List<MyObject> list, final String name){
    list.stream().filter(o -> o.getName().equals(name)).forEach(
            o -> {
                //...
            }
    );
}

Dimana omewakili sebuah MyObjectinstance.

Atau, seperti komentar yang disarankan (Terima kasih MK10), Anda dapat menggunakan Stream#anyMatchmetode ini:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}
Josh M
sumber
1
Maka, contoh kedua seharusnya public boolean. Juga, Anda mungkin ingin menggunakan Objects.equals()jika o.getName()bisa null.
Eric Jablow
1
Saya hanya seorang programmer paranoid. Saya sudah berurusan dengan proyek-proyek di mana orang-orang cepat dan longgar dengan nol, jadi saya cenderung bersikap defensif. Jauh lebih baik untuk memastikan barang tidak menjadi nol, selamanya.
Eric Jablow
5
@ EricJablow Mungkin Anda harus mengomentari SEMUA jawaban yang tidak melakukan nullpemeriksaan, bukan hanya satu (milik saya).
Josh M
55
Bahkan lebih pendek dan lebih mudah dimengerti: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10
3
Saya setuju dengan @ MK10 tetapi saya akan menggunakan java.util.Objectsuntuk perbandingan nullsafe. return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Jika Anda ingin memeriksa objek dalam aliran untuk null, Anda dapat menambahkan .filter(Objects::nonNull)sebelum anyMatch.
tobijdc
81

Anda punya dua pilihan.

1. Pilihan pertama, yang lebih disukai, adalah mengganti metode `equals ()` di kelas Object Anda.

Katakanlah, misalnya, Anda memiliki kelas Obyek ini:

public class MyObject {
    private String name;
    private String location;
    //getters and setters
}

Sekarang katakanlah Anda hanya peduli dengan nama MyObject, bahwa itu harus unik sehingga jika dua `MyObject` memiliki nama yang sama mereka harus dianggap sama. Dalam hal ini, Anda ingin mengganti metode `equals ()` (dan juga metode `hashcode ()`) sehingga membandingkan nama untuk menentukan kesetaraan.

Setelah melakukan ini, Anda dapat memeriksa untuk melihat apakah Koleksi berisi MyObject dengan nama "foo" dengan seperti:

MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);

Namun, ini mungkin bukan pilihan bagi Anda jika:

  • Anda menggunakan nama dan lokasi untuk memeriksa kesetaraan, tetapi Anda hanya ingin memeriksa apakah suatu Koleksi memiliki `MyObject` dengan lokasi tertentu. Dalam hal ini, Anda telah mengganti `equals ()`.
  • `MyObject` adalah bagian dari API yang tidak dapat Anda ubah.

Jika salah satu dari ini adalah masalahnya, Anda akan menginginkan opsi 2:

2. Tulis metode utilitas Anda sendiri:

public static boolean containsLocation(Collection<MyObject> c, String location) {
    for(MyObject o : c) {
        if(o != null && o.getLocation.equals(location)) {
            return true;
        }
    }
    return false;
}

Atau, Anda dapat memperluas ArrayList (atau koleksi lain) dan kemudian menambahkan metode Anda sendiri ke dalamnya:

public boolean containsLocation(String location) {
    for(MyObject o : this) {
        if(o != null && o.getLocation.equals(location)) {
                return true;
            }
        }
        return false;
    }

Sayangnya tidak ada cara yang lebih baik di sekitarnya.

James Dunn
sumber
Saya pikir Anda lupa tanda kurung untuk rajin jika (o! = Null && o.getLocation.equals (lokasi))
olszi
25

Google Jambu

Jika Anda menggunakan Jambu Biji , Anda dapat mengambil pendekatan fungsional dan melakukan hal berikut

FluentIterable.from(list).find(new Predicate<MyObject>() {
   public boolean apply(MyObject input) {
      return "John".equals(input.getName());
   }
}).Any();

yang terlihat sedikit bertele-tele. Namun predikat adalah objek dan Anda dapat memberikan varian berbeda untuk pencarian yang berbeda. Perhatikan bagaimana perpustakaan itu sendiri memisahkan iterasi koleksi dan fungsi yang ingin Anda terapkan. Anda tidak perlu mengganti equals()untuk perilaku tertentu.

Seperti disebutkan di bawah ini, kerangka java.util.Stream dibangun ke dalam Java 8 dan kemudian menyediakan sesuatu yang serupa.

Brian Agnew
sumber
1
Atau Anda dapat menggunakan Iterables.any(list, predicate), yang praktis sama, dan Anda mungkin atau mungkin tidak suka secara gaya.
MikeFHay
@EricJablow Yup. :) Anda bisa melihat solusi saya.
Josh M
25

Ini adalah bagaimana melakukannya menggunakan Java 8+:

boolean isJohnAlive = list.stream().anyMatch(o -> o.getName().equals("John"));
GabrielBB
sumber
20

Collection.contains()diimplementasikan dengan memanggil equals()setiap objek sampai satu kembali true.

Jadi salah satu cara untuk mengimplementasikan ini adalah dengan menimpanya equals()tetapi tentu saja, Anda hanya dapat memiliki satu yang sama.

Kerangka kerja seperti Jambu karena itu menggunakan predikat untuk ini. Dengan Iterables.find(list, predicate), Anda dapat mencari bidang yang berubah-ubah dengan memasukkan tes ke dalam predikat.

Bahasa lain yang dibangun di atas VM memiliki built in ini. Di Groovy , misalnya, Anda cukup menulis:

def result = list.find{ it.name == 'John' }

Java 8 membuat hidup kami lebih mudah, juga:

List<Foo> result = list.stream()
    .filter(it -> "John".equals(it.getName())
    .collect(Collectors.toList());

Jika Anda peduli dengan hal-hal seperti ini, saya sarankan buku "Beyond Java". Ini berisi banyak contoh untuk banyak kekurangan di Jawa dan bagaimana bahasa lain melakukan lebih baik.

Aaron Digulla
sumber
Jadi, jika saya mengganti metode equals dari objek kustom yang ditambahkan ke daftar ... bisakah saya mendapatkannya hanya mengembalikan true jika variabel kelas tertentu sama?
Rudi Kershaw
1
Tidak, ide di balik equals()ini sangat terbatas. Ini berarti memeriksa "identitas objek" - apa pun artinya bagi beberapa kelas objek. Jika Anda menambahkan bendera bidang mana yang harus dimasukkan, yang dapat menyebabkan semua jenis masalah. Saya sangat menyarankan untuk tidak melakukannya.
Aaron Digulla
Suka asyik, jadi Java 8 Lambda "baru" benar-benar tidak memberi kita ketenangan ini? def result = list.find{ it.name == 'John' }Oracle / JCP harus dituntut karena kejahatan perang terhadap programmer.
ken
19

Pencarian Biner

Anda dapat menggunakan Collections.binarySearch untuk mencari elemen dalam daftar Anda (dengan asumsi daftar diurutkan):

Collections.binarySearch(list, new YourObject("a1", "b",
                "c"), new Comparator<YourObject>() {

            @Override
            public int compare(YourObject o1, YourObject o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

yang akan mengembalikan angka negatif jika objek tidak ada dalam koleksi atau yang lain akan mengembalikan indexobjek. Dengan ini, Anda dapat mencari objek dengan strategi pencarian berbeda.

Debojit Saikia
sumber
Ini adalah solusi hebat untuk proyek kecil yang tidak ingin menggunakan jambu biji. Namun satu pertanyaan, dokumen pada binarySearch menyatakan bahwa: "Daftar harus diurutkan ke dalam urutan naik sesuai dengan pembanding yang ditentukan, sebelum melakukan panggilan ini. Jika tidak diurutkan, hasilnya tidak ditentukan". Tetapi dalam beberapa kasus, tergantung pada apa yang dibandingkan, ini mungkin tidak terjadi?
chrismarx
dan angka negatif menunjukkan di mana objek akan dimasukkan jika Anda menambahkannya dan mengurutkan ulang.
fiorentinoing
8

Peta

Anda bisa membuat Hashmap<String, Object>menggunakan salah satu nilai sebagai kunci, dan kemudian melihat apakah yourHashMap.keySet().contains(yourValue)mengembalikan true.


sumber
+1 Meskipun itu berarti sedikit overhead dalam menempatkan rincian ke dalam peta di tempat pertama Anda kemudian mendapatkan pencarian waktu yang konstan. Saya terkejut tidak ada yang menyarankan ini sampai sekarang.
Rudi Kershaw
6

Koleksi Eclipse

Jika Anda menggunakan Eclipse Collections , Anda dapat menggunakan anySatisfy()metode ini. Baik menyesuaikan Listdalam Anda ListAdapteratau mengubah Anda Listmenjadi ListIterablejika mungkin.

ListIterable<MyObject> list = ...;

boolean result =
    list.anySatisfy(myObject -> myObject.getName().equals("John"));

Jika Anda akan sering melakukan operasi seperti ini, lebih baik mengekstrak metode yang menjawab apakah tipe tersebut memiliki atribut.

public class MyObject
{
    private final String name;

    public MyObject(String name)
    {
        this.name = name;
    }

    public boolean named(String name)
    {
        return Objects.equals(this.name, name);
    }
}

Anda dapat menggunakan formulir alternatif anySatisfyWith()bersama-sama dengan referensi metode.

boolean result = list.anySatisfyWith(MyObject::named, "John");

Jika Anda tidak dapat mengubah Anda Listmenjadi a ListIterable, inilah cara Anda akan menggunakannya ListAdapter.

boolean result = 
    ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");

Catatan: Saya pengendara untuk Eclipse ollections.

Craig P. Motlin
sumber
5

Predicate

Jika Anda tidak menggunakan Java 8, atau pustaka yang memberi Anda lebih banyak fungsi untuk menangani koleksi, Anda bisa mengimplementasikan sesuatu yang bisa lebih dapat digunakan kembali daripada solusi Anda.

interface Predicate<T>{
        boolean contains(T item);
    }

    static class CollectionUtil{

        public static <T> T find(final Collection<T> collection,final  Predicate<T> predicate){
            for (T item : collection){
                if (predicate.contains(item)){
                    return item;
                }
            }
            return null;
        }
    // and many more methods to deal with collection    
    }

Saya menggunakan sesuatu seperti itu, saya memiliki antarmuka predikat, dan saya meneruskan implementasinya ke kelas util saya.

Apa keuntungan melakukan ini dengan cara saya? Anda memiliki satu metode yang berhubungan dengan pencarian dalam koleksi jenis apa pun. dan Anda tidak perlu membuat metode terpisah jika Anda ingin mencari berdasarkan bidang yang berbeda. Yang perlu Anda lakukan adalah memberikan predikat berbeda yang dapat dihancurkan segera setelah tidak lagi berguna /

jika Anda ingin menggunakannya, yang perlu Anda lakukan hanyalah memanggil metode dan menentukan predikat Anda

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});
pengguna902383
sumber
4

Berikut ini solusi menggunakan Jambu Biji

private boolean checkUserListContainName(List<User> userList, final String targetName){

    return FluentIterable.from(userList).anyMatch(new Predicate<User>() {
        @Override
        public boolean apply(@Nullable User input) {
            return input.getName().equals(targetName);
        }
    });
}
Phan Van Linh
sumber
3

containsMetode menggunakan equalsinternal. Jadi Anda perlu mengganti equalsmetode untuk kelas Anda sesuai kebutuhan Anda.

Tapi ini tidak benar secara sintaksis:

new Object().setName("John")
Juned Ahsan
sumber
3
Kecuali jika penyetel kembali this.
Sotirios Delimanolis
Jadi, jika saya mengganti metode equals dari objek kustom yang ditambahkan ke daftar ... bisakah saya mendapatkannya hanya mengembalikan true jika variabel kelas tertentu sama?
Rudi Kershaw
@RudiKershaw Ya itu idenya, Anda dapat mengembalikan true berdasarkan satu / dua / semua bidang. Jadi, jika Anda yakin secara logis benar untuk mengatakan bahwa dua objek dengan nama yang sama adalah objek yang sama maka kembalikan benar dengan hanya membuat nama-nama tersebut.
Juned Ahsan
1
Benar-benar tidak disarankan untuk mengganti equalskasus penggunaan tunggal, kecuali jika masuk akal untuk kelas secara umum. Anda akan jauh lebih baik hanya menulis perulangan.
MikeFHay
@MikeFHay setuju, itu sebabnya saya sebutkan di komentar "Jadi, jika Anda percaya secara logis benar mengatakan bahwa dua objek dengan nama yang sama adalah objek yang sama, maka kembalikan benar"
Juned Ahsan
1

Jika Anda perlu melakukan ini List.contains(Object with field value equal to x)berulang kali, solusi sederhana dan efisien adalah:

List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
    fieldOfInterestValues.add(obj.getFieldOfInterest());
}

Maka List.contains(Object with field value equal to x)hasilnya akan sama denganfieldOfInterestValues.contains(x);

Magda
sumber
1

Anda juga dapat anyMatch()menggunakan:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().contains(name));
}
akshaymittal143
sumber
0

Meskipun ada JAVA 8 SDK, ada banyak alat koleksi yang dapat membantu perpustakaan Anda bekerja, misalnya: http://commons.apache.org/proper/commons-collections/

Predicate condition = new Predicate() {
   boolean evaluate(Object obj) {
        return ((Sample)obj).myField.equals("myVal");
   }
};
List result = CollectionUtils.select( list, condition );
Pasha GR
sumber