Java List.contain (วัตถุที่มีค่าฟิลด์เท่ากับ x)


199

ฉันต้องการตรวจสอบว่าListมีวัตถุที่มีเขตข้อมูลที่มีค่าที่แน่นอน ตอนนี้ฉันสามารถใช้การวนซ้ำเพื่อตรวจสอบ แต่ฉันอยากรู้ว่ามีโค้ดอะไรที่มีประสิทธิภาพมากกว่านี้

สิ่งที่ต้องการ;

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

ฉันรู้ว่าโค้ดข้างต้นไม่ได้ทำอะไรเลยมันแค่แสดงให้เห็นอย่างคร่าวๆว่าฉันพยายามทำอะไร

นอกจากนี้เพื่อชี้แจงเหตุผลที่ฉันไม่ต้องการใช้การวนรอบแบบง่าย ๆ ได้เนื่องจากรหัสนี้จะไปในวงที่อยู่ภายในวงซึ่งอยู่ภายในวง เพื่อความสะดวกในการอ่านฉันไม่ต้องการเพิ่มลูปในลูปเหล่านี้ต่อไป ดังนั้นฉันสงสัยว่ามีทางเลือก (ish) เรียบง่ายใด ๆ


5
เนื่องจากนี่คือความเท่าเทียมกันที่กำหนดเองคุณจะต้องเขียนโค้ดที่กำหนดเอง
Sotirios Delimanolis

เป้าหมายที่ระบุไว้ของคุณและตัวอย่างรหัสของคุณดูเหมือนจะไม่ตรงกัน คุณต้องการเปรียบเทียบวัตถุบนพื้นฐานของค่าเขตข้อมูลเดียวหรือไม่
Duncan Jones

1
แทนที่equals(Object)วิธีการของวัตถุที่กำหนดเองของคุณหรือไม่
Josh M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;คุณจะไม่พบวิธีที่กระชับมากขึ้นใน Java ฉันกลัว
MikeFHay

@ Rajdeep ขออภัยฉันไม่เข้าใจคำถามของคุณ p.equals(p) ควรเป็นจริงเสมอดังนั้นฉันสับสนในสิ่งที่คุณพยายามจะทำ หวังว่าถ้าคุณถามคำถามใหม่คุณจะได้รับความช่วยเหลือที่ดีขึ้น
MikeFHay

คำตอบ:


267

ลำธาร

หากคุณใช้ Java 8 คุณอาจลองทำสิ่งนี้:

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

หรืออีกวิธีหนึ่งคุณสามารถลองสิ่งนี้:

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

วิธีการนี้จะกลับมาtrueถ้าList<MyObject>มีที่มีชื่อMyObject nameหากคุณต้องการที่จะดำเนินการในแต่ละMyObjectที่getName().equals(name)นั้นคุณสามารถลองสิ่งนี้:

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

ในกรณีที่oแสดงให้เห็นถึงMyObjectตัวอย่างเช่น

อีกทางหนึ่งตามความคิดเห็นที่แนะนำ (ขอบคุณ MK10) คุณสามารถใช้Stream#anyMatchวิธีการ:

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

1
public booleanจากนั้นตัวอย่างที่สองที่ควรจะเป็น นอกจากนี้คุณอาจต้องการที่จะใช้Objects.equals()ในกรณีที่สามารถo.getName() null
Eric Jablow

1
ฉันแค่เป็นโปรแกรมเมอร์หวาดระแวง ฉันจัดการกับโปรเจ็กต์ที่ผู้คนรวดเร็วและหลวมด้วยโมฆะดังนั้นฉันจึงมีแนวโน้มที่จะป้องกัน ดียิ่งกว่านั้นเพื่อให้แน่ใจว่ารายการจะไม่เป็นโมฆะ
Eric Jablow

5
@EricJablow บางทีคุณควรแสดงความคิดเห็นในทุกคำตอบที่ไม่ได้ดำเนินการnullตรวจสอบเมื่อเทียบกับเพียงหนึ่ง (เหมือง)
Josh M

55
ยิ่งสั้นและเข้าใจง่าย: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
ฉันเห็นด้วยกับ @ MK10 แต่ฉันจะใช้java.util.Objectsสำหรับการเปรียบเทียบ nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);หากคุณต้องการตรวจสอบวัตถุในสตรีมเป็นโมฆะคุณสามารถเพิ่ม a .filter(Objects::nonNull)ก่อนตรงไหนก็ได้
tobijdc

81

คุณมีสองทางเลือก

1. ตัวเลือกแรกที่เป็นที่นิยมมากกว่าคือแทนที่เมธอด `equals () 'ในคลาสวัตถุของคุณ

ตัวอย่างเช่นคุณมีคลาส Object:

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

ทีนี้สมมติว่าคุณสนใจชื่อ MyObject เพียงว่ามันควรจะไม่ซ้ำกันดังนั้นถ้าสอง `MyObject`s มีชื่อเดียวกันพวกเขาควรได้รับการพิจารณาเท่ากัน ในกรณีนั้นคุณต้องการแทนที่เมธอด `equals ()` (และเมธอด `hashcode ()`) เพื่อให้มันเปรียบเทียบชื่อเพื่อกำหนดความเท่าเทียมกัน

เมื่อคุณทำสิ่งนี้แล้วคุณสามารถตรวจสอบว่าคอลเล็กชันมี MyObject ที่ชื่อ "foo" หรือไม่โดยทำดังนี้:

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

อย่างไรก็ตามนี่อาจไม่ใช่ตัวเลือกสำหรับคุณหาก:

  • คุณกำลังใช้ทั้งชื่อและที่ตั้งเพื่อตรวจสอบความเท่าเทียมกัน แต่คุณต้องการตรวจสอบว่าคอลเล็กชันมี `MyObject`s ที่มีที่ตั้งที่แน่นอนหรือไม่ ในกรณีนี้คุณได้แทนที่ `equals () 'แล้ว
  • `MyObject 'เป็นส่วนหนึ่งของ API ที่คุณไม่มีอิสระในการเปลี่ยนแปลง

หากสิ่งเหล่านี้เป็นอย่างนั้นคุณจะต้องการตัวเลือก 2:

2. เขียนวิธีการอรรถประโยชน์ของคุณเอง:

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

หรือคุณสามารถขยาย ArrayList (หรือคอลเล็กชันอื่น ๆ ) แล้วเพิ่มวิธีการของคุณเอง:

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

น่าเสียดายที่ไม่มีวิธีที่ดีกว่าในเรื่องนี้


ฉันคิดว่าคุณลืมวงเล็บสำหรับ getter ในถ้า (o! = null && o.getLocation.equals (ตำแหน่ง))
olszi

25

Google Guava

หากคุณใช้Guavaคุณสามารถใช้วิธีการทำงานและทำสิ่งต่อไปนี้

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

ซึ่งดู verbose เล็กน้อย อย่างไรก็ตามเพรดิเคตเป็นวัตถุและคุณสามารถจัดให้มีชุดตัวเลือกต่าง ๆ สำหรับการค้นหาที่แตกต่างกัน โปรดสังเกตว่าห้องสมุดจะแยกการวนซ้ำของคอลเล็กชันและฟังก์ชันที่คุณต้องการนำไปใช้อย่างไร คุณไม่ต้องลบล้างequals()พฤติกรรมที่เฉพาะเจาะจง

ดังที่ระบุไว้ด้านล่าง, เฟรมเวิร์กjava.util.Stream ที่สร้างขึ้นใน Java 8 และใหม่กว่ามีบางสิ่งที่คล้ายกัน


1
หรือคุณอาจใช้Iterables.any(list, predicate)ซึ่งก็เหมือนกันและคุณอาจชอบหรือไม่ชอบโวหาร
MikeFHay

@EricJablow Yup :) คุณสามารถดูวิธีแก้ปัญหาของฉัน
Josh M


20

Collection.contains()จะดำเนินการโดยการเรียกใช้equals()กับแต่ละวัตถุจนกว่าจะส่งคืนtrueในแต่ละวัตถุจนผลตอบแทน

ดังนั้นวิธีหนึ่งที่จะใช้สิ่งนี้คือการแทนที่equals()แต่แน่นอนว่าคุณสามารถมีได้เพียงหนึ่งเท่ากับเท่านั้น

กรอบเช่นGuavaจึงใช้ predicates สำหรับสิ่งนี้ ด้วยIterables.find(list, predicate)คุณสามารถค้นหาฟิลด์ใด ๆ ก็ได้โดยใส่การทดสอบลงในเพรดิเคต

ภาษาอื่นที่อยู่ด้านบนของ VM มีสิ่งนี้อยู่ภายในในGroovyตัวอย่างเช่นคุณเพียงแค่เขียน:

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

Java 8 ทำให้ชีวิตของเราง่ายขึ้นเช่นกัน:

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

หากคุณสนใจสิ่งต่าง ๆ เช่นนี้ฉันแนะนำหนังสือ "Beyond Java" มันมีตัวอย่างมากมายสำหรับข้อบกพร่องจำนวนมากของ Java และภาษาอื่น ๆ ทำได้ดีกว่า


ดังนั้นถ้าฉันแทนที่วิธีการเท่ากับของวัตถุที่กำหนดเองที่ถูกเพิ่มลงในรายการ ... ฉันจะได้รับมันเพียงแค่ส่งกลับจริงถ้าตัวแปรคลาสบางอย่างเหมือนกันหรือไม่
Rudi Kershaw

1
ไม่แนวคิดเบื้องหลังequals()มี จำกัด มาก มันหมายถึงการตรวจสอบ "ตัวตนของวัตถุ" - สิ่งที่อาจหมายถึงคลาสของวัตถุบางอย่าง หากคุณเพิ่มการตั้งค่าสถานะซึ่งควรรวมเขตข้อมูลนั่นอาจทำให้เกิดปัญหาทุกประเภท ฉันขอแนะนำอย่างยิ่งกับมัน
Aaron Digulla

ชอบความแรงดังนั้น Java 8 Lambda "ใหม่" จริงๆไม่ให้ความเย็นนี้กับเราเหรอ? def result = list.find{ it.name == 'John' }Oracle / JCP ควรถูกฟ้องร้องข้อหาก่ออาชญากรรมสงครามกับโปรแกรมเมอร์
เคน

19

ค้นหาแบบทวิภาค

คุณสามารถใช้Collections.binarySearchเพื่อค้นหาองค์ประกอบในรายการของคุณ (สมมติว่ามีการจัดเรียงรายการ):

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

ซึ่งจะส่งกลับจำนวนลบหากวัตถุไม่ได้อยู่ในคอลเลกชันมิฉะนั้นมันจะกลับมาindexของวัตถุ ด้วยวิธีนี้คุณสามารถค้นหาวัตถุด้วยกลยุทธ์การค้นหาที่แตกต่างกัน


นี่เป็นทางออกที่ยอดเยี่ยมสำหรับโครงการขนาดเล็กที่ไม่ต้องการใช้ฝรั่ง แม้ว่าหนึ่งคำถาม docs ใน binarySearch ระบุว่า: "รายการจะต้องเรียงลำดับจากน้อยไปหามากตามตัวเปรียบเทียบที่ระบุก่อนที่จะทำการโทรนี้หากไม่ได้เรียงลำดับผลลัพธ์จะไม่ถูกกำหนด" แต่ในบางกรณีดูเหมือนว่าขึ้นอยู่กับสิ่งที่ถูกเปรียบเทียบนี่อาจไม่เป็นเช่นนั้น
chrismarx

และจำนวนลบหมายถึงตำแหน่งที่วัตถุจะถูกแทรกหากคุณเพิ่มและเรียงลำดับใหม่
fiorentinoing

8

แผนที่

คุณสามารถสร้างการHashmap<String, Object>ใช้หนึ่งในค่าเป็นกุญแจสำคัญและจากนั้นดูว่าyourHashMap.keySet().contains(yourValue)ผลตอบแทนจริง


+1 แม้ว่าจะหมายถึงค่าใช้จ่ายเล็กน้อยในการใส่รายละเอียดลงในแผนที่ในสถานที่แรกที่คุณจะได้รับการค้นหาเวลาคงที่ ฉันประหลาดใจที่ไม่มีใครแนะนำเรื่องนี้มาจนถึงตอนนี้
ฤดี Kershaw

6

Eclipse Collections

หากคุณใช้Eclipse Collectionsคุณสามารถใช้anySatisfy()วิธีนี้ได้ ทั้งปรับของคุณListในListAdapterหรือเปลี่ยนListเป็นListIterableถ้าเป็นไปได้

ListIterable<MyObject> list = ...;

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

หากคุณดำเนินการเช่นนี้บ่อยครั้งควรแยกวิธีที่ตอบว่าประเภทนั้นมีแอตทริบิวต์หรือไม่

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

คุณสามารถใช้แบบฟอร์มสำรองanySatisfyWith()พร้อมกับการอ้างอิงวิธีการ

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

ถ้าคุณไม่สามารถเปลี่ยนListเป็นนี่คือวิธีการที่คุณต้องการใช้ListIterableListAdapter

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

หมายเหตุ: ฉันเป็นคอมมิชชันสำหรับ Eclipse ollections


5

Predicate

หากคุณไม่ใช้ Java 8 หรือไลบรารีที่ให้ฟังก์ชันการทำงานเพิ่มเติมสำหรับการจัดการกับคอลเลกชันคุณสามารถใช้สิ่งที่สามารถใช้ซ้ำได้มากกว่าโซลูชันของคุณ

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    
    }

ฉันกำลังใช้บางอย่างเช่นนั้นฉันมีอินเทอร์เฟซแบบเพรดิเคตและฉันผ่านการใช้งานไปยังคลาส util ของฉัน

อะไรคือข้อดีของการทำสิ่งนี้ในทางของฉัน? คุณมีวิธีการหนึ่งที่เกี่ยวข้องกับการค้นหาในคอลเลกชันประเภทใด ๆ และคุณไม่จำเป็นต้องสร้างวิธีการแยกต่างหากหากคุณต้องการค้นหาด้วยฟิลด์อื่น alll สิ่งที่คุณต้องทำคือให้ภาคแสดงต่าง ๆ ซึ่งสามารถทำลายได้ทันทีที่ไม่มีประโยชน์ /

หากคุณต้องการใช้สิ่งที่คุณต้องทำก็คือวิธีการโทรและกำหนด tyour predicate

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

4

นี่คือวิธีแก้ปัญหาโดยใช้Guava

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

3

containsวิธีการใช้equalsภายใน ดังนั้นคุณต้องแทนที่equalsวิธีการเรียนของคุณตามความต้องการของคุณ

Btw สิ่งนี้ดูไม่ถูกต้องตามหลักไวยากรณ์:

new Object().setName("John")

3
thisเว้นแต่จะได้ผลตอบแทนหมา
Sotirios Delimanolis

ดังนั้นถ้าฉันแทนที่วิธีการเท่ากับของวัตถุที่กำหนดเองที่ถูกเพิ่มลงในรายการ ... ฉันจะได้รับมันเพียงแค่ส่งกลับจริงถ้าตัวแปรคลาสบางอย่างเหมือนกันหรือไม่
Rudi Kershaw

@RudiKershaw ใช่นั่นคือความคิดที่คุณสามารถส่งกลับจริงบนพื้นฐานของหนึ่ง / สอง / ทุกสาขา ดังนั้นหากคุณเชื่อว่าถูกต้องตามหลักเหตุผลในการบอกว่าวัตถุสองชิ้นที่มีชื่อเหมือนกันเป็นวัตถุเท่ากันให้คืนค่าเป็นจริงโดยเพียงแค่ทำการย่อชื่อ
Juned Ahsan

1
ไม่แนะนำให้เขียนทับequalsกรณีใช้งานเพียงครั้งเดียวเว้นแต่ว่าเหมาะสมสำหรับชั้นเรียนทั่วไป คุณน่าจะดีกว่ามากแค่เขียนบ่วง
MikeFHay

@MikeFHay ตกลง thats ทำไมฉันกล่าวถึงในการแสดงความคิดเห็น "ดังนั้นหากคุณเชื่อว่ามันเป็นที่ถูกต้องมีเหตุผลที่จะบอกว่าวัตถุทั้งสองที่มีชื่อเดียวกันเป็นวัตถุที่เท่าเทียมกันแล้วกลับจริง"
Juned Ahsan

1

หากคุณจำเป็นต้องดำเนินการนี้List.contains(Object with field value equal to x)ซ้ำ ๆ การแก้ปัญหาที่ง่ายและมีประสิทธิภาพจะเป็น:

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

จากนั้นList.contains(Object with field value equal to x)จะมีผลเช่นเดียวกับfieldOfInterestValues.contains(x);



0

แม้จะมี JAVA 8 SDK แต่ก็มีไลบรารีเครื่องมือการเก็บรวบรวมจำนวนมากที่สามารถช่วยคุณทำงานได้เช่น: 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 );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.