Java: Proper way of creating a list containing all not-in-intersect elements from two given lists based on a specific attribute?

  • A+
Category:Languages

Given two Lists of Objects, I'd be able to tell which items are not in their intersect based on one of their attributes. Let's look at the following example:

I have a class Foo that has two attributes: boo and placeholder

class Foo {     private int boo;     private int placeholder = 1;      public Foo(int boo) {         this.boo = boo;     }      public int getBoo() {         return boo;     } } 

Now I am creating two Lists from that (let's say this is my input)

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

And now I'd like to say which Items are in list1 and not in list2 or in list2 and not in list1 based on their attribute boo. So in the above example I want a List<Foo> notInIntersectList that contains one Foo(0) and one 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.     //... 

Sadly I am getting Local variable inBothLists defined in an enclosing scope must be final or effectively final as an error. How is this issue solved properly, since this seems not to be the "right" solution?

 


You cannot mutate variables inside a lambda expression (See: Variable used in lambda expression should be final or effectively final)

Here's a way to fix your code (fun with Streams)

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

The complexity of this is O(n*m) (where n and m are the number of elements in list1 and list2 respectively).

To do this in O(n+m), you can use a Set. For this, you need a equals and hashcode method on the Foo class. This considers two Foo instances as equal only based on the value of the instance variable 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;     } } 

And use a Set for this as

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

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: