Je veux vérifier si une List
contient un objet qui a un champ avec une certaine valeur. Maintenant, je pourrais utiliser une boucle pour passer et vérifier, mais j'étais curieux de savoir s'il y avait quelque chose de plus efficace en code.
Quelque chose comme;
if(list.contains(new Object().setName("John"))){
//Do some stuff
}
Je sais que le code ci-dessus ne fait rien, c'est juste pour démontrer approximativement ce que j'essaie de réaliser.
Aussi, juste pour clarifier, la raison pour laquelle je ne veux pas utiliser une boucle simple est parce que ce code va actuellement dans une boucle qui est dans une boucle qui est dans une boucle. Pour des raisons de lisibilité, je ne veux pas continuer à ajouter des boucles à ces boucles. Alors je me suis demandé s'il y avait des alternatives simples (ish).
Si vous utilisez Java 8, vous pourriez peut-être essayer quelque chose comme ceci:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}
Ou bien, vous pouvez essayer quelque chose comme ceci:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}
Cette méthode renverra true
si le List<MyObject>
contient une MyObject
avec le nom name
. Si vous souhaitez effectuer une opération sur chacune des MyObject
s que getName().equals(name)
, alors vous pouvez essayer quelque chose comme ceci:
public void perform(final List<MyObject> list, final String name){
return list.stream().filter(o -> o.getName().equals(name)).forEach(
o -> {
//...
}
);
}
Où o
représente une instance MyObject
.
Vous avez deux choix.
1. Le premier choix, qui est préférable, consiste à remplacer la méthode `equals ()` dans votre classe Object.
Disons, par exemple, que vous avez cette classe d'objet:
public class MyObject {
private String name;
private String location;
//getters and setters
}
Maintenant, disons que vous ne vous souciez que du nom de MyObject, qu’il doit être unique, donc si deux `MyObject` ont le même nom, ils doivent être considérés comme égaux. Dans ce cas, vous voudriez remplacer la méthode `equals ()` (ainsi que la méthode `hashcode ()`) afin qu'elle compare les noms afin de déterminer l'égalité.
Une fois que vous avez fait cela, vous pouvez vérifier si une collection contient un MyObject avec le nom "foo" de la manière suivante:
MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);
Cependant, cela pourrait ne pas être une option pour vous si:
Si l'un ou l'autre est le cas, vous voudrez l'option 2:
2. Écrivez votre propre méthode d’utilité:
public static boolean containsLocation(Collection<MyObject> c, String location) {
for(MyObject o : c) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Vous pouvez également étendre ArrayList (ou une autre collection) et y ajouter votre propre méthode:
public boolean containsLocation(String location) {
for(MyObject o : this) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Malheureusement, il n'y a pas de meilleure solution.
Si vous utilisez Guava , vous pouvez adopter une approche fonctionnelle et procéder comme suit.
FluentIterable.from(list).find(new Predicate<MyObject>() {
public boolean apply(MyObject input) {
return "John".equals(input.getName());
}
}).Any();
qui a l'air un peu prolixe. Cependant, le prédicat est un objet et vous pouvez fournir différentes variantes pour différentes recherches. Notez comment la bibliothèque elle-même sépare l'itération de la collection et la fonction que vous souhaitez appliquer. Il n'est pas nécessaire de remplacer equals()
pour un comportement particulier.
Comme indiqué ci-dessous, la structure Java.util.Stream intégrée à Java 8 et versions ultérieures fournit quelque chose de similaire.
Collection.contains()
est implémenté en appelant equals()
sur chaque objet jusqu'à ce que l'on retourne true
.
Donc, une façon d'implémenter ceci est de remplacer equals()
mais bien sûr, vous ne pouvez avoir qu'un égal.
Les cadres tels que Guava utilisent donc des prédicats pour cela. Avec Iterables.find(list, predicate)
, vous pouvez rechercher des champs arbitraires en plaçant le test dans le prédicat.
Ceci est intégré dans les autres langues construites au-dessus de VM. Dans Groovy , par exemple, vous écrivez simplement:
def result = list.find{ it.name == 'John' }
Java 8 nous a également rendu la vie plus facile:
List<Foo> result = list.stream()
.filter(it -> "John".equals(it.getName())
.collect(Collectors.toList());
Si cela vous intéresse, je vous suggère le livre "Au-delà de Java". Il contient de nombreux exemples des nombreuses faiblesses de Java et de l'amélioration des autres langages.
Vous pouvez utiliser Collections.binarySearch pour rechercher un élément dans votre liste (en supposant que la liste est triée):
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());
}
});
qui retournera un nombre négatif si l'objet n'est pas présent dans la collection ou bien il retournera la index
de l'objet. Avec cela, vous pouvez rechercher des objets avec différentes stratégies de recherche.
Vous pouvez créer un Hashmap<String, Object>
en utilisant l'une des valeurs comme clé, puis voir si yourHashMap.keySet().contains(yourValue)
renvoie true.
Voici comment faire avec Java 8+:
boolean johnExistance = list.stream().anyMatch(o -> o.getName().equals("John"));
Si vous utilisez Collections Eclipse , vous pouvez utiliser la méthode anySatisfy()
. Adaptez votre List
dans une ListAdapter
ou modifiez votre List
en ListIterable
si possible.
ListIterable<MyObject> list = ...;
boolean result =
list.anySatisfy(myObject -> myObject.getName().equals("John"));
Si vous effectuez fréquemment des opérations de ce type, il est préférable d'extraire une méthode qui indique si le type a l'attribut.
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);
}
}
Vous pouvez utiliser la forme alternative anySatisfyWith()
avec une référence à une méthode.
boolean result = list.anySatisfyWith(MyObject::named, "John");
Si vous ne pouvez pas changer votre List
en ListIterable
, voici comment vous utiliseriez ListAdapter
.
boolean result =
ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");
Remarque: je suis un partisan des ollections Eclipse.
Predicate
Si vous n'utilisez pas Java 8, ou une bibliothèque qui vous donne plus de fonctionnalités pour gérer les collections, vous pouvez implémenter quelque chose qui peut être plus réutilisable que votre solution.
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
}
j'utilise quelque chose comme ça, j'ai l'interface de prédicat, et je la passe à la classe util.
Quel est l'avantage de le faire à ma manière? vous avez une méthode qui consiste à chercher dans n'importe quelle collection de types. et vous n'avez pas à créer des méthodes séparées si vous souhaitez effectuer une recherche par champ différent. tout ce que vous devez faire est de fournir un prédicat différent qui peut être détruit dès qu’il n’est plus utile /
si vous voulez l'utiliser, il vous suffit d'appeler la méthode et de définir votre prédicat
CollectionUtil.find(list, new Predicate<MyObject>{
public boolean contains(T item){
return "John".equals(item.getName());
}
});
La méthode contains
utilise equals
en interne. Vous devez donc redéfinir la méthode equals
pour votre classe selon vos besoins.
Btw, cela ne semble pas correct du point de vue de la synthèse:
new Object().setName("John")
Voici une solution utilisant 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);
}
});
}
Si vous devez exécuter cette List.contains(Object with field value equal to x)
à plusieurs reprises, une solution simple et efficace serait la suivante:
List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
fieldOfInterestValues.add(obj.getFieldOfInterest());
}
Alors le List.contains(Object with field value equal to x)
aurait le même résultat que fieldOfInterestValues.contains(x);
Malgré Java 8 SDK, de nombreuses bibliothèques d'outils de collection peuvent vous aider à travailler, par exemple: 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 );