Java List.contains (Objeto con valor de campo igual a x)


199

Quiero verificar si a Listcontiene un objeto que tiene un campo con un cierto valor. Ahora, podría usar un bucle para pasar y verificar, pero tenía curiosidad por saber si había algo más eficiente en el código.

Algo como;

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

Sé que el código anterior no hace nada, es solo para demostrar aproximadamente lo que estoy tratando de lograr.

Además, solo para aclarar, la razón por la que no quiero usar un bucle simple es porque este código irá actualmente dentro de un bucle que está dentro de un bucle que está dentro de un bucle. Para facilitar la lectura, no quiero seguir agregando bucles a estos bucles. Entonces me pregunté si había alternativas simples (ish).


55
Como se trata de igualdad personalizada, tendrá que escribir un código personalizado.
Sotirios Delimanolis

Su objetivo declarado y su ejemplo de código no parecen coincidir. ¿Desea comparar los objetos solo en función de un valor de campo?
Duncan Jones

1
¿Anular el equals(Object)método de su objeto personalizado?
Josh M

1
for(Person p:list) if (p.getName().equals("John") return true; return false;Me temo que no encontrarás una manera más concisa en Java.
MikeFHay

@ Rajdeep lo siento, no entiendo tu pregunta. siempre p.equals(p) debe ser cierto, así que estoy confundido con lo que estás tratando de lograr. Espero que si hace una nueva pregunta pueda obtener una mejor ayuda.
MikeFHay

Respuestas:


267

Corrientes

Si está utilizando Java 8, quizás podría intentar algo como esto:

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

O, alternativamente, podría intentar algo como esto:

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

Este método devolverá truesi List<MyObject>contiene un MyObjectcon el nombre name. Si desea realizar una operación en cada uno de los MyObjects getName().equals(name), puede intentar algo como esto:

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

Donde orepresenta una MyObjectinstancia.

Alternativamente, como sugieren los comentarios (Gracias MK10), puede usar el Stream#anyMatchmétodo:

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

1
Entonces, el segundo ejemplo debería ser public boolean. Además, es posible que desee utilizar Objects.equals()en caso de que o.getName()pueda ser null.
Eric Jablow

1
Solo soy un programador paranoico. He tratado con proyectos donde la gente era rápida y suelta con nulos, así que tiendo a estar a la defensiva. Mucho mejor para asegurarse de que los artículos no sean nulos, nunca.
Eric Jablow

55
@EricJablow Quizás deberías comentar TODAS las respuestas que no realizan nullverificaciones, en lugar de solo una (la mía).
Josh M

55
Aún más corto y fácil de entender: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10

3
Estoy de acuerdo con @ MK10 pero lo usaría java.util.Objectspara la comparación nula-segura. return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Si desea verificar los objetos en la secuencia para nulo, puede agregar un .filter(Objects::nonNull)antes de anyMatch.
tobijdc

81

Tienes dos opciones.

1. La primera opción, que es preferible, es anular el método `equals ()` en su clase Object.

Digamos, por ejemplo, que tiene esta clase de objeto:

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

Ahora digamos que solo le importa el nombre de MyObject, que debería ser único, por lo que si dos `MyObject`s tienen el mismo nombre, deberían considerarse iguales. En ese caso, desearía anular el método `equals ()` (y también el método `hashcode ()`) para que compare los nombres para determinar la igualdad.

Una vez que haya hecho esto, puede verificar si una Colección contiene un MyObject con el nombre "foo" de la siguiente manera:

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

Sin embargo, esto podría no ser una opción para usted si:

  • Está utilizando tanto el nombre como la ubicación para verificar la igualdad, pero solo desea verificar si una Colección tiene algún `MyObject`s con una determinada ubicación. En este caso, ya ha anulado `equals ()`.
  • `MyObject` es parte de una API que no tiene libertad para cambiar.

Si cualquiera de estos es el caso, querrá la opción 2:

2. Escriba su propio método de utilidad:

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

Alternativamente, puede extender ArrayList (o alguna otra colección) y luego agregarle su propio método:

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

Desafortunadamente no hay una mejor manera de evitarlo.


Creo que olvidó los corchetes para getter in if (o! = Null && o.getLocation.equals (location))
olszi

25

Google guayaba

Si usa Guava , puede adoptar un enfoque funcional y hacer lo siguiente

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

que se ve un poco detallado. Sin embargo, el predicado es un objeto y puede proporcionar diferentes variantes para diferentes búsquedas. Observe cómo la biblioteca misma separa la iteración de la colección y la función que desea aplicar. No tiene que anular equals()un comportamiento particular.

Como se indica a continuación, el marco java.util.Stream integrado en Java 8 y posterior proporciona algo similar.


1
Alternativamente, podría usar Iterables.any(list, predicate), que es prácticamente lo mismo, y puede o no preferirlo estilísticamente.
MikeFHay

@EricJablow Sí. :) Podrías mirar mi solución.
Josh M

25

Así es como se hace usando Java 8+:

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

20

Collection.contains()se implementa llamando equals()a cada objeto hasta que uno regrese true.

Entonces, una forma de implementar esto es anular, equals()pero por supuesto, solo puede tener uno igual.

Los marcos como la guayaba, por lo tanto, utilizan predicados para esto. Con Iterables.find(list, predicate), puede buscar campos arbitrarios poniendo la prueba en el predicado.

Otros lenguajes construidos sobre la VM tienen esto incorporado. En Groovy , por ejemplo, simplemente escribe:

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

Java 8 también nos facilitó la vida:

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

Si te importan cosas como esta, te sugiero el libro "Más allá de Java". Contiene muchos ejemplos de las numerosas deficiencias de Java y de cómo otros idiomas funcionan mejor.


Entonces, si anulo el método igual de los Objetos personalizados que se agregan a la lista ... ¿puedo hacer que devuelva verdadero si ciertas variables de clase son las mismas?
Rudi Kershaw

1
No, la idea detrás equals()es muy limitada. Significa verificar la "identidad del objeto", lo que sea que eso signifique para alguna clase de objetos. Si agregaste una marca que campos deberían incluirse, eso podría causar todo tipo de problemas. Recomiendo encarecidamente que no lo haga.
Aaron Digulla

Amor maravilloso, ¿entonces el "nuevo" Java 8 Lambda realmente no nos da aún esta genialidad? def result = list.find{ it.name == 'John' }Oracle / JCP debe ser demandado por crímenes de guerra contra programadores.
Ken

19

Búsqueda binaria

Puede usar Collections.binarySearch para buscar un elemento en su lista (suponiendo que la lista esté ordenada):

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

que devolverá un número negativo si el objeto no está presente en la colección o devolverá el indexdel objeto. Con esto puede buscar objetos con diferentes estrategias de búsqueda.


Esta es una gran solución para pequeños proyectos que no quieren usar guayaba. Sin embargo, una pregunta es que los documentos en binarySearch dicen que: "La lista se debe ordenar en orden ascendente de acuerdo con el comparador especificado, antes de realizar esta llamada. Si no se ordena, los resultados no están definidos". Pero parece que en algunos casos, dependiendo de lo que se compara, ¿podría no ser así?
chrismarx

y el número negativo representa dónde se insertará el objeto si lo agrega y vuelve a ordenar.
fiorentinoing

8

Mapa

Puede crear un Hashmap<String, Object>utilizando uno de los valores como clave y luego ver si yourHashMap.keySet().contains(yourValue)devuelve verdadero.


+1 Aunque en primer lugar significa un poco de sobrecarga al poner los detalles en el mapa, entonces obtienes una búsqueda de tiempo constante. Me sorprende que nadie haya sugerido esto hasta ahora.
Rudi Kershaw

6

Colecciones Eclipse

Si está usando Eclipse Collections , puede usar el anySatisfy()método. Adapta tu Listen ListAdaptero cambia tu Lista ListIterablesi es posible.

ListIterable<MyObject> list = ...;

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

Si realiza operaciones como esta con frecuencia, es mejor extraer un método que responda si el tipo tiene el atributo.

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

Puede usar la forma alternativa anySatisfyWith()junto con una referencia de método.

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

Si no puede cambiarlo Listpor uno ListIterable, así es como lo usaría ListAdapter.

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

Nota: Soy un committer para las selecciones de Eclipse.


5

Predicate

Si no utiliza Java 8, o una biblioteca que le brinda más funcionalidad para manejar colecciones, podría implementar algo que sea más reutilizable que su solució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    
    }

Estoy usando algo así, tengo una interfaz de predicado y la paso a mi clase de utilidad.

¿Cuál es la ventaja de hacer esto a mi manera? tiene un método que se ocupa de buscar en cualquier tipo de colección. y no tiene que crear métodos separados si desea buscar por un campo diferente. Todo lo que necesita hacer es proporcionar un predicado diferente que se pueda destruir tan pronto como ya no sea útil.

si desea usarlo, todo lo que necesita hacer es llamar al método y definir su predicado

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

4

Aquí hay una solución usando guayaba

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

containsEl método se utiliza equalsinternamente. Por lo tanto, debe anular elequals método para su clase según su necesidad.

Por cierto, esto no parece sintácticamente correcto:

new Object().setName("John")

3
A menos que el setter regrese this.
Sotirios Delimanolis

Entonces, si anulo el método igual de los Objetos personalizados que se agregan a la lista ... ¿puedo hacer que devuelva verdadero si ciertas variables de clase son las mismas?
Rudi Kershaw

@RudiKershaw Sí, esa es la idea, puede devolver verdadero sobre la base de uno / dos / todos los campos. Entonces, si cree que es lógicamente correcto decir que dos objetos con el mismo nombre son objetos iguales, entonces devuelva verdadero solo con comapring de los nombres.
Juned Ahsan

1
Realmente no es recomendable anular equalspara un caso de uso único, a menos que tenga sentido para la clase en general. Sería mucho mejor escribir el bucle.
MikeFHay

@MikeFHay estuvo de acuerdo, por eso mencioné en el comentario "Entonces, si crees que es lógicamente correcto decir que dos objetos con el mismo nombre son iguales, entonces devuelve verdadero"
Juned Ahsan

1

Si necesita realizar esto List.contains(Object with field value equal to x)repetidamente, una solución simple y eficiente sería:

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

Entonces el List.contains(Object with field value equal to x)tendría el mismo resultado quefieldOfInterestValues.contains(x);


1

también puedes anyMatch()usar:

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

0

A pesar de JAVA 8 SDK, hay muchas herramientas de colección que las bibliotecas pueden ayudarlo a trabajar, por ejemplo: 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 );
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.