Home > Software design >  Outer Join on list of Objects
Outer Join on list of Objects

Time:12-13

I have the following objects:

class A {
   int id;
   String propA;
}

class B {
   int id;
   String propB;
}

class C {
   int id;
   String propA;
   String propB;
}

Given Lists of Objects A and B, I would like to compute a List of Objects C, which will be an object created from joining Object A with B by id; However, if the id is present in the list of objects A and not B or B and not A, I would like to include it in a list of objects C, but with missing property. In other words, doing join on following lists:

List<A> listA = new ArrayList<>();
listA.add(new A(1, "example1"));
listA.add(new A(2, "example2"));

List<B> listB = new ArrayList<>();
listB.add(new B(2, "another example2"));
listB.add(new B(3, "another example3"));

Should be equivalent to creating the following list:

List<C> listC = new ArrayList<>();
listC.add(new C(1, "example1", null));
listC.add(new C(2, "example2", "another example2"));
listC.add(new C(3, null, "another example3"));

I have seen similar issues solved with Stream API, but it was always a one-sided Outer Join; either way, I tried to adapt it to my needs.

My attempt is following:

//creating maps of ids to list elements, so I can access it from the final stream easily
Map<Integer, A> listAbyId = listA.stream().collect(Collectors.toMap(A::getId, Function.identity()));
Map<Integer, B> listBbyId = listB.stream().collect(Collectors.toMap(B::getId, Function.identity()));

//creating set of all ids that are in both lists
Set<Integer> set = listA.stream().map(a -> a.getId()).collect(Collectors.toSet());
Set<Integer> set2 = listB.stream().map(b -> b.getId()).collect(Collectors.toSet());
HashSet<Integer> setc = new HashSet<>();
setc.addAll(set);
setc.addAll(set2);

//final stream that creates list of C objects.
List<C> listC = setc.stream().map(c -> new C(c,
                                            listAbyId.get(c) == null ? null : listAbyId.get(c).getPropA(),
                                            listBbyId.get(c) == null ? null : listBbyId.get(c).getPropB()))
                            .collect(Collectors.toList());

It works as intended, but it kind of seems like overkill. Is there an easier way to do it?

CodePudding user response:

Your start is pretty good allready but you are right the MapOfA/B is a bit overkill


public static void main(String[] args) {

    List<A> listA = new ArrayList<>();
    listA.add(new A(1, "texta"));
    listA.add(new A(2, "textA"));

    List<B> listB = new ArrayList<>();
    listB.add(new B(2, "textb"));
    listB.add(new B(3, "textB"));
    
    Set<Integer> ids = listA.stream().map(a -> a.id).collect(Collectors.toSet());
    listB.forEach(b -> ids.add(b.id));
    List<C> listC = ids.stream()
            .map(id -> new C(id, listA.stream().filter(a -> a.id == id).findAny().orElse(new A(0, null)).propA,
                    listB.stream().filter(b -> b.id == id).findAny().orElse(new B(0, null)).propB))
            .toList();

    }

CodePudding user response:

Something like this:

Map<Integer, A> map = listA.stream().collect(Collectors.toMap(A::getId, item -> new C(item.getId(), item.getPropA(), null)));
listB.stream().forEach(item -> map.containsKey(item.getId()) ? map.get(item.getId()).setPropB(item.getPropB()) : map.put(item.getId(), item);

And then convert to list.

  • Related