Dadas dos listas de objetos, me gustaría ser capaz de decir qué elementos no están en su intersecan basado en uno de sus atributos. Echemos un vistazo en el siguiente ejemplo:
Tengo una clase Foo
que tiene dos atributos: boo
yplaceholder
class Foo {
private int boo;
private int placeholder = 1;
public Foo(int boo) {
this.boo = boo;
}
public int getBoo() {
return boo;
}
}
Ahora estoy creando dos listas desde que (digamos que esta es mi entrada)
List<Foo> list1 = new ArrayList<Foo>();
list1.add(new Foo(1));
list1.add(new Foo(2));
list1.add(new Foo(3));
List<Foo> list2 = new ArrayList<Foo>();
list2.add(new Foo(0));
list2.add(new Foo(1));
list2.add(new Foo(2));
Y ahora me gustaría decir, que los artículos están en list1
y no en list2
o en list2
y no en list1
base a su atributo boo
. Así, en el ejemplo anterior quiero una List<Foo> notInIntersectList
que contiene uno Foo(0)
y uno Foo(3)
.
List<Foo> notInIntersectList = new ArrayList<Foo>();
list1.forEach(li1foo -> {
boolean inBothLists = false;
list2.forEach(li2foo -> {
if (li1foo.getBoo() == li2foo.getBoo()) {
inBothLists = true;
}
});
if (!inBothLists) {
notInIntersectList.add(li1foo);
}
});
//now I covered all items in list1 but not in list2. Now do this again with lists swapped, so I can also cover those.
//...
Por desgracia yo estoy haciendo Local variable inBothLists defined in an enclosing scope must be final or effectively final
como un error. ¿Cómo se resuelve este problema adecuadamente, ya que este no parece ser la solución "correcta"?
No se puede mutar las variables dentro de una expresión lambda (Ver: variable que se utiliza en la expresión lambda debe ser final o con eficacia definitiva )
He aquí una manera de arreglar su código (bien con las salas)
List<Foo> notInIntersectList = list1.stream()
.filter(fooElementFromList1 -> list2
.stream()
.noneMatch(fooElementFromList2 -> fooElementFromList2.getBoo() == fooElementFromList1.getBoo()))
.collect(Collectors.toCollection(ArrayList::new));
list2.stream()
.filter(fooElementFromList2 -> list1
.stream()
.noneMatch(fooElementFromList1 -> fooElementFromList1.getBoo() == fooElementFromList2.getBoo()))
.forEach(notInIntersectList::add);
La complejidad de este es O(n*m)
(donde n
y m
son el número de elementos de list1 y list2 respectivamente).
Para hacer esto en O(n+m)
, puede utilizar un conjunto. Para ello, se necesita una equals
y hashcode
método en la Foo
clase. Este considera dos Foo
casos como igual sólo se basa en el valor de la variable de instancia boo
.
class Foo {
....
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Foo other = (Foo) obj;
return boo == other.boo;
}
@Override
public int hashCode() {
return boo;
}
}
Y utilizar un Set
para esto
Set<Foo> fooSet1 = new HashSet<>(list1);
Set<Foo> fooSet2 = new HashSet<>(list2);
fooSet1.removeAll(list2);
fooSet2.removeAll(list1);
List<Foo> notInIntersectList = Stream.concat(fooSet1.stream(), fooSet2.stream())
.collect(Collectors.toList());