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

Eu quero verificar se uma List contém um object que tenha um campo com um determinado valor. Agora, eu poderia usar um loop para passar e verificar, mas eu estava curioso para saber se havia algo mais eficiente em código.

Algo como;

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

Eu sei que o código acima não faz nada, é apenas para demonstrar mais ou menos o que estou tentando alcançar.

Além disso, só para esclarecer, a razão pela qual eu não quero usar um loop simples é porque este código irá atualmente dentro de um loop que está dentro de um loop que está dentro de um loop. Para facilitar a leitura, não quero continuar adicionando loops a esses loops. Então eu me perguntei se havia alguma alternativa simples (ish).

Córregos

Se você estiver usando o Java 8, talvez possa tentar algo assim:

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

Ou, alternativamente, você poderia tentar algo assim:

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

Esse método retornará true se o List contiver um MyObject com o nome do name . Se você quer executar uma operação em cada um dos MyObject s que getName().equals(name) , então você pode tentar algo como isto:

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

Onde o representa uma instância MyObject .

Você tem duas escolhas.

1. A primeira escolha, que é preferível, é sobrescrever o método `equals ()` na sua class Object.

Digamos, por exemplo, que você tenha essa class Object:

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

Agora vamos dizer que você só se preocupa com o nome do MyObject, que ele deve ser único, então se dois `MyObject`s tiverem o mesmo nome, eles devem ser considerados iguais. Nesse caso, você deve sobrescrever o método `equals ()` (e também o método `hashcode ()`) para comparar os nomes para determinar a igualdade.

Depois de fazer isso, você pode verificar se uma coleção contém um MyObject com o nome “foo” por assim:

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

No entanto, isso pode não ser uma opção para você se:

  • Você está usando tanto o nome quanto a localização para verificar a igualdade, mas você só quer verificar se uma Coleção tem algum ‘MyObject`s com um determinado local. Neste caso, você já cancelou `equals ()`.
  • `MyObject` é parte de uma API que você não tem liberdade para alterar.

Se qualquer um desses for o caso, você desejará a opção 2:

2. Escreva seu próprio método de utilidade:

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

Alternativamente, você poderia estender ArrayList (ou alguma outra coleção) e então adicionar seu próprio método a ele:

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

Infelizmente não há uma maneira melhor de contornar isso.

Google Goiaba

Se você estiver usando Goiaba , você pode ter uma abordagem funcional e fazer o seguinte

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

que parece um pouco detalhado. No entanto, o predicado é um object e você pode fornecer variantes diferentes para pesquisas diferentes. Observe como a própria biblioteca separa a iteração da coleção e a function que você deseja aplicar. Você não precisa replace equals() para um comportamento específico.

Conforme observado abaixo, a estrutura java.util.Stream incorporada no Java 8 e posterior fornece algo semelhante.

Pesquisa binária

Você pode usar Collections.binarySearch para pesquisar um elemento na sua lista (supondo que a lista esteja classificada):

 Collections.binarySearch(list, new YourObject("a1", "b", "c"), new Comparator() { @Override public int compare(YourObject o1, YourObject o2) { return o1.getName().compareTo(o2.getName()); } }); 

que retornará um número negativo se o object não estiver presente na coleção ou então retornará o index do object. Com isso você pode procurar objects com diferentes estratégias de busca.

Collection.contains() é implementado chamando equals() em cada object até que um retorne true .

Portanto, uma maneira de implementar isso é sobrescrevendo equals() mas é claro, você pode ter apenas um igual.

Frameworks como o Guava, portanto, usam predicados para isso. Com Iterables.find(list, predicate) , você pode procurar por campos arbitrários colocando o teste no predicado.

Outras linguagens construídas sobre a VM têm isso embutido. No Groovy , por exemplo, você simplesmente escreve:

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

O Java 8 tornou toda a nossa vida mais fácil também:

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

Se você se preocupa com coisas como esta, sugiro o livro “Beyond Java”. Ele contém muitos exemplos para as inúmeras deficiências do Java e como outras linguagens se saem melhor.

Coleções do Eclipse

Se você estiver usando o Eclipse Collections , poderá usar o método anySatisfy() . Adapte sua List em um ListAdapter ou altere sua List para um ListIterable se possível.

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

Se você fizer operações como essa frequentemente, é melhor extrair um método que responda se o tipo tem o 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); } } 

Você pode usar o formulário alternativo anySatisfyWith() junto com uma referência de método.

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

Se você não pode alterar sua List em um ListIterable , aqui está como você usaria o ListAdapter .

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

Nota: Eu sou um committer para as collections do Eclipse.

Mapa

Você poderia criar um Hashmap usando um dos valores como uma chave e, em seguida, verificando se yourHashMap.keySet().contains(yourValue) retorna true.

Predicate

Se você não usa Java 8, ou biblioteca que lhe dá mais funcionalidade para lidar com collections, você poderia implementar algo que pode ser mais reutilizável do que sua solução.

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

Eu estou usando algo assim, eu tenho interface de predicado, e eu estou passando a implementação para minha class de utilitários.

Qual a vantagem de fazer isso do meu jeito? você tem um método que lida com a pesquisa em qualquer coleção de tipos. e você não precisa criar methods separados se quiser pesquisar por um campo diferente. alll o que você precisa fazer é fornecer um predicado diferente que pode ser destruído assim que ele não for mais útil

se você quiser usá-lo, tudo o que você precisa fazer é chamar o método e definir o predicado tyour

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

contains método usa equals internamente. Portanto, você precisa replace o método equals da sua turma de acordo com sua necessidade.

Btw isso não parece sintaticamente correto:

 new Object().setName("John") 

Se você precisar executar este List.contains(Object with field value equal to x) repetidamente, uma solução simples e eficiente seria:

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

Em seguida, o List.contains(Object with field value equal to x) teria o mesmo resultado que fieldOfInterestValues.contains(x);

Apesar do JAVA 8 SDK, há muitas bibliotecas de ferramentas de coleção que podem ajudá-lo a trabalhar, por exemplo: 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 ); 

Aqui está uma solução usando Goiaba

 private boolean checkUserListContainName(List userList, final String targetName){ return FluentIterable.from(userList).anyMatch(new Predicate() { @Override public boolean apply(@Nullable User input) { return input.getName().equals(targetName); } }); }