List.contains Java (Đối tượng có giá trị trường bằng x)


199

Tôi muốn kiểm tra xem List đối tượng có chứa một trường có một giá trị nhất định hay không. Bây giờ, tôi có thể sử dụng một vòng lặp để đi qua và kiểm tra, nhưng tôi tò mò liệu có bất kỳ mã nào hiệu quả hơn không.

Cái gì đó như;

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

Tôi biết đoạn mã trên không làm gì cả, nó chỉ để chứng minh đại khái những gì tôi đang cố gắng đạt được.

Ngoài ra, để làm rõ, lý do tôi không muốn sử dụng một vòng lặp đơn giản là vì mã này hiện sẽ đi vào một vòng lặp nằm trong một vòng lặp nằm trong một vòng lặp. Để dễ đọc, tôi không muốn tiếp tục thêm các vòng lặp vào các vòng lặp này. Vì vậy, tôi tự hỏi nếu có bất kỳ thay thế (ish) đơn giản.


5
Vì đây là đẳng thức tùy chỉnh, bạn sẽ phải viết mã tùy chỉnh.
Sotirios Delimanolis

Mục tiêu đã nêu và ví dụ mã của bạn dường như không khớp. Bạn có muốn so sánh các đối tượng chỉ dựa trên một giá trị trường không?
Duncan Jones

1
Ghi đè equals(Object)phương thức của đối tượng tùy chỉnh của bạn?
Josh M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;Bạn sẽ không tìm thấy một cách ngắn gọn hơn trong Java Tôi sợ.
MikeFHay

@Rajdeep xin lỗi, tôi không hiểu câu hỏi của bạn. p.equals(p) phải luôn luôn đúng, vì vậy tôi bối rối những gì bạn đang cố gắng đạt được. Hy vọng nếu bạn hỏi một câu hỏi mới, bạn có thể nhận được sự giúp đỡ tốt hơn.
MikeFHay

Câu trả lời:


267

Dòng suối

Nếu bạn đang sử dụng Java 8, có lẽ bạn có thể thử một cái gì đó như thế này:

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

Hoặc cách khác, bạn có thể thử một cái gì đó như thế này:

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

Phương thức này sẽ trả về truenếu List<MyObject>chứa a MyObjectvới tên name. Nếu bạn muốn thực hiện một thao tác trên mỗi MyObjectđó getName().equals(name), thì bạn có thể thử một cái gì đó như thế này:

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

Trong trường hợp ođại diện cho mộtMyObject ví dụ.

Ngoài ra, như các ý kiến ​​đề xuất (Cảm ơn MK10), bạn có thể sử dụng Stream#anyMatchphương pháp:

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

1
Sau đó, ví dụ thứ hai nên được public boolean. Ngoài ra, bạn có thể muốn sử dụng Objects.equals()trong trường hợp o.getName()có thể null.
Eric Jablow

1
Tôi chỉ là một lập trình viên hoang tưởng. Tôi đã xử lý các dự án nơi mọi người nhanh chóng và lỏng lẻo với null, vì vậy tôi có xu hướng phòng thủ. Tốt hơn nhiều để đảm bảo các mặt hàng không phải là null, bao giờ hết.
Eric Jablow

5
@EricJablow Có lẽ bạn nên nhận xét về TẤT CẢ các câu trả lời không thực hiện nullkiểm tra, trái ngược với chỉ một (của tôi).
Josh M

55
Thậm chí ngắn hơn và dễ hiểu hơn: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
Tôi đồng ý với @ MK10 nhưng tôi sẽ sử dụng java.util.Objectsđể so sánh nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Nếu bạn muốn kiểm tra các đối tượng trong luồng cho null, bạn có thể thêm một .filter(Objects::nonNull)trước anyMatch.
tobijdc

81

Bạn có hai lựa chọn.

1. Lựa chọn đầu tiên, tốt hơn là, ghi đè phương thức `equals ()` trong lớp Object của bạn.

Ví dụ, giả sử bạn có lớp Object này:

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

Bây giờ, hãy nói rằng bạn chỉ quan tâm đến tên của MyObject, rằng nó phải là duy nhất vì vậy nếu hai `MyObject` có cùng tên thì chúng nên được coi là bằng nhau. Trong trường hợp đó, bạn sẽ muốn ghi đè phương thức `equals ()` (và cả phương thức `hashcode ()`) để nó so sánh các tên để xác định đẳng thức.

Khi bạn đã hoàn thành việc này, bạn có thể kiểm tra xem Bộ sưu tập có chứa MyObject với tên "foo" hay không:

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

Tuy nhiên, điều này có thể không phải là một lựa chọn cho bạn nếu:

  • Bạn đang sử dụng cả tên và vị trí để kiểm tra sự bằng nhau, nhưng bạn chỉ muốn kiểm tra xem Bộ sưu tập có bất kỳ `MyObject` nào với một vị trí nhất định không. Trong trường hợp này, bạn đã ghi đè `bằng ()`.
  • `MyObject` là một phần của API mà bạn không có quyền tự do thay đổi.

Nếu một trong hai trường hợp này, bạn sẽ muốn tùy chọn 2:

2. Viết phương thức tiện ích của riêng bạn:

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

Ngoài ra, bạn có thể mở rộng ArrayList (hoặc một số bộ sưu tập khác) và sau đó thêm phương thức của riêng bạn vào nó:

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

Thật không may, không có cách nào tốt hơn xung quanh nó.


Tôi nghĩ rằng bạn đã quên dấu ngoặc cho getter trong if (o! = Null && o.getLocation.equals (vị trí))
olszi

25

Quả ổi

Nếu bạn đang sử dụng Guava , bạn có thể thực hiện một cách tiếp cận chức năng và làm như sau

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

Có vẻ hơi dài dòng. Tuy nhiên vị ngữ là một đối tượng và bạn có thể cung cấp các biến thể khác nhau cho các tìm kiếm khác nhau. Lưu ý cách thư viện tự phân tách lần lặp của bộ sưu tập và chức năng bạn muốn áp dụng. Bạn không phải ghi đèequals() cho một hành vi cụ thể.

Như đã lưu ý dưới đây, khung java.util.Stream được tích hợp trong Java 8 và sau đó cung cấp một cái gì đó tương tự.


1
Ngoài ra, bạn có thể sử dụng Iterables.any(list, predicate), thực tế là giống nhau, và bạn có thể hoặc không thể thích nó theo phong cách.
MikeFHay

@EricJablow Yup. :) Bạn có thể nhìn vào giải pháp của tôi.
Josh M

25

Đây là cách thực hiện bằng Java 8+:

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

20

Collection.contains()được thực hiện bằng cách gọi equals()trên mỗi đối tượng cho đến khi một trở về true.

Vì vậy, một cách để thực hiện điều này là ghi đè equals()nhưng tất nhiên, bạn chỉ có thể có một bằng.

Các khung như Guava do đó sử dụng các vị từ cho việc này. VớiIterables.find(list, predicate) , bạn có thể tìm kiếm các trường tùy ý bằng cách đặt bài kiểm tra vào vị ngữ.

Các ngôn ngữ khác được xây dựng trên VM có tích hợp sẵn. Trong Groovy , ví dụ, bạn chỉ cần viết:

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

Java 8 cũng làm cho cuộc sống của chúng ta dễ dàng hơn:

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

Nếu bạn quan tâm đến những thứ như thế này, tôi đề nghị cuốn sách "Beyond Java". Nó chứa nhiều ví dụ cho nhiều thiếu sót của Java và cách các ngôn ngữ khác làm tốt hơn.


Vì vậy, nếu tôi ghi đè phương thức bằng của các Đối tượng tùy chỉnh được thêm vào danh sách ... tôi có thể lấy nó để trả về true nếu các biến lớp nhất định giống nhau không?
Rudi Kershaw

1
Không, ý tưởng đằng sau equals()là rất hạn chế. Nó có nghĩa là kiểm tra "danh tính đối tượng" - bất cứ điều gì có thể có nghĩa đối với một số loại đối tượng. Nếu bạn đã thêm một cờ nên bao gồm các trường, điều đó có thể gây ra tất cả các loại rắc rối. Tôi mạnh mẽ khuyên chống lại nó.
Aaron Digulla

Yêu Groovy, vậy Lambda "mới" thực sự không mang lại cho chúng ta sự mát mẻ này? def result = list.find{ it.name == 'John' }Oracle / JCP nên bị kiện vì tội ác chiến tranh chống lại các lập trình viên.
ken

19

Tìm kiếm nhị phân

Bạn có thể sử dụng Collections.binarySearch để tìm kiếm một phần tử trong danh sách của bạn (giả sử danh sách đã được sắp xếp):

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

sẽ trả về một số âm nếu đối tượng không có trong bộ sưu tập nếu không nó sẽ trả về indexđối tượng. Với điều này, bạn có thể tìm kiếm các đối tượng với các chiến lược tìm kiếm khác nhau.


Đây là một giải pháp tuyệt vời cho các dự án nhỏ không muốn sử dụng ổi. Mặc dù vậy, có một câu hỏi, các tài liệu về trạng thái nhị phân Tìm kiếm: "Danh sách phải được sắp xếp theo thứ tự tăng dần theo bộ so sánh đã chỉ định, trước khi thực hiện cuộc gọi này. Nếu không được sắp xếp, kết quả sẽ không được xác định". Nhưng dường như trong một số trường hợp, tùy thuộc vào những gì được so sánh, điều này có thể không phải là trường hợp?
chrismarx

và số âm biểu thị nơi đối tượng sẽ được chèn nếu bạn thêm nó và sắp xếp lại.
fiorentinoing

8

Bản đồ

Bạn có thể tạo một Hashmap<String, Object>bằng cách sử dụng một trong các giá trị làm khóa và sau đó xem nếu yourHashMap.keySet().contains(yourValue)trả về đúng.


+1 Mặc dù điều đó có nghĩa là một chút chi phí trong việc đưa các chi tiết vào bản đồ ở vị trí đầu tiên sau đó bạn sẽ được tra cứu thời gian liên tục. Tôi ngạc nhiên không ai đề nghị điều này cho đến bây giờ.
Rudi Kershaw

6

Bộ sưu tập Eclipse

Nếu bạn đang sử dụng Bộ sưu tập Eclipse , bạn có thể sử dụng anySatisfy()phương thức này. Hoặc điều chỉnh của bạn Listtrong một ListAdapterhoặc thay đổi của bạn Listthành một ListIterablenếu có thể.

ListIterable<MyObject> list = ...;

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

Nếu bạn thường xuyên thực hiện các thao tác như thế này, tốt hơn là trích xuất một phương thức trả lời xem loại đó có thuộc tính hay không.

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

Bạn có thể sử dụng biểu mẫu thay thế anySatisfyWith()cùng với tham chiếu phương thức.

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

Nếu bạn không thể thay đổi Listthành một ListIterable, đây là cách bạn sử dụng ListAdapter.

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

Lưu ý: Tôi là người đi làm cho các bộ sưu tập Eclipse.


5

Predicate

Nếu bạn không sử dụng Java 8 hoặc thư viện cung cấp cho bạn nhiều chức năng hơn để xử lý các bộ sưu tập, bạn có thể triển khai thứ gì đó có thể tái sử dụng nhiều hơn giải pháp của bạn.

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    
    }

Tôi đang sử dụng một cái gì đó như thế, tôi có giao diện vị ngữ và tôi đang chuyển nó cho lớp tiện dụng của tôi.

Lợi thế của việc này theo cách của tôi là gì? bạn có một phương pháp liên quan đến tìm kiếm trong bất kỳ bộ sưu tập loại nào. và bạn không phải tạo các phương thức riêng biệt nếu bạn muốn tìm kiếm theo trường khác nhau. alll những gì bạn cần làm là cung cấp các vị từ khác nhau có thể bị phá hủy ngay khi nó không còn hữu ích /

nếu bạn muốn sử dụng nó, tất cả những gì bạn cần làm là gọi phương thức và xác định vị từ tyour

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

4

Đây là một giải pháp sử dụng ổi

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

containsphương pháp sử dụng equalsnội bộ. Vì vậy, bạn cần ghi đè equalsphương thức cho lớp theo nhu cầu của bạn.

Btw điều này có vẻ không đúng về mặt cú pháp:

new Object().setName("John")

3
Trừ khi setter trả về this.
Sotirios Delimanolis

Vì vậy, nếu tôi ghi đè phương thức bằng của các Đối tượng tùy chỉnh được thêm vào danh sách ... tôi có thể lấy nó để trả về true nếu các biến lớp nhất định giống nhau không?
Rudi Kershaw

@RudiKershaw Vâng, đó là ý tưởng, bạn có thể trả về đúng trên cơ sở một / hai / tất cả các trường. Vì vậy, nếu bạn tin rằng nó là chính xác về mặt logic để nói rằng hai đối tượng có cùng tên là các đối tượng bằng nhau thì trả về true bằng cách chỉ viết lại tên.
Juned Ahsan

1
Thật sự không nên ghi đè equalscho một trường hợp sử dụng, trừ khi nó có ý nghĩa đối với lớp nói chung. Bạn sẽ tốt hơn nhiều nếu chỉ viết vòng lặp.
MikeFHay

@MikeFHay đồng ý, đó là lý do tại sao tôi đã đề cập trong bình luận "Vì vậy, nếu bạn tin rằng nó đúng về mặt logic để nói rằng hai đối tượng có cùng tên là các đối tượng bằng nhau thì trả về đúng"
Juned Ahsan

1

Nếu bạn cần thực hiện việc này List.contains(Object with field value equal to x)nhiều lần, một cách giải quyết đơn giản và hiệu quả sẽ là:

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

Sau đó, List.contains(Object with field value equal to x)sẽ có kết quả tương tự nhưfieldOfInterestValues.contains(x);


1

bạn cũng có thể anyMatch()sử dụng:

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

0

Mặc dù SDK JAVA 8, có rất nhiều thư viện công cụ thu thập có thể giúp bạn làm việc với, ví dụ: 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 );
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.