Search code examples
javajava-8code-readability

Java 8: How to elegantly remove redundant outer objects on base of inner object ID and date in Java 8?


There is ObjectOuter which contains another object called ObjectInner which is having an ID. We need to remove Redundant ObjectOuter having duplicate ObjectInner Ids. (We have another DateFinish object in picture)

public static List<ObjectOuter> removeRedundantObject(List<ObjectOuter> ObjectOuterOriginal){
      if(ObjectOuterOriginal.size() == 1) {
         return ObjectOuterOriginal;
      }
      List<ObjectInner> allObjectInner = ObjectOuterOriginal.stream().map(ObjectOuter::getObjectInner).collect(toList());
      List<Long> allObjectInnerIds = allObjectInner.stream().map(ObjectInner::getObjectInnerId).collect(toList());
      List<ObjectOuter> myFinalObjectOuter = new ArrayList<>();
      if(ObjectOuterOriginal.size() == allObjectInnerIds.stream().distinct().collect(Collectors.toList()).size()){
         return ObjectOuterOriginal;
      }
      Set<Long> duplicateObjectOuter = CommonUtils.getDuplicateNumbers(allObjectInnerIds); //Returns numbers which are duplicate in set
      if(SetUtils.emptyIfNull(duplicateObjectOuter).isEmpty()){
        return ObjectOuterOriginal;
      } else {
        duplicateObjectOuter.forEach((objectInnerId) -> {
              List<ObjectOuter> myOwnObjectOuter = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()==null).collect(Collectors.toList());
              if(ListUtils.emptyIfNull(myOwnObjectOuter).isEmpty()) {
                  LocalDate maxDate = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()!=null).map(u -> u.getDateFinish()).max(LocalDate::compareTo).get();
                  List<ObjectOuter> ownObjectOuter = ObjectOuterOriginal.stream().filter(d -> d.getObjectInner().getObjectInnerId().equals(objectInnerId) && d.getDateFinish()!=null).filter(d -> d.getDateFinish().compareTo(maxDate) == 0).collect(toList());
                  myFinalObjectOuter.addAll(ownObjectOuter);
              } else {
                  myFinalObjectOuter.addAll(myOwnObjectOuter);
              }
        });
        duplicateObjectOuter.forEach((objectInnerId) -> {
            ObjectOuterOriginal.removeIf(d -> d.getObjectInner().getObjectInnerId().compareTo(objectInnerId) == 0);
        });
        ObjectOuterOriginal.addAll(myFinalObjectOuter);
      }
      return ObjectOuterOriginal;
  }

Also we need to use filter on innerObject to select only those ids whose date is either NULL or having max date among duplicate elements; where date is located in outer object.

Above code is executing properly but it is suggested to handle it in more elegant way in Java 8. I can only think of omitting first if statement. but is there any scope of merging statements in Java-8 for above snippet?


Solution

  • If your id's were already unique, you could simply do:

    public static void main(String[] args) {
        Outer[] outers = {
                new Outer(new Inner("a")),
                new Outer(new Inner("b")),
                new Outer(new Inner("c")),
                new Outer(new Inner("a")),
                new Outer(new Inner("b")),
                new Outer(new Inner("c")),
        };
    
        Map<String, Outer> OutersById = Arrays.stream(outers).collect(Collectors.toMap(outer -> outer.inner.id, outer -> outer));
    
        OutersById.forEach((k,v)->System.out.println(k+", "+v));
    }
    

    But this will result in a Duplicate Key Exception.

    In order to manage your duplicates, and use your own disambiguation strategy,

    Map<String, Outer> OutersById = Arrays.stream(outers).collect(Collectors.toMap(outer -> outer.inner.id, outer -> outer));
    

    Can be changed to

    Map<String, Outer> OutersById = Arrays.stream(outers)
                    .collect(
                            Collectors.toMap(
                                    outer -> outer.inner.id, 
                                    outer -> outer,
                                    (a, b) -> a.hashCode() > b.hashCode() ? a : b
                                    ));
    

    Or whichever other strategy you want, maybe outer has a date on it that can be compared?

    Maybe a and b can be joined, into a new object ab?

    Update:

    public static void main(String[] args) {
            Instant now = Instant.now();
            Instant nxtWeek= now.plus(Duration.ofDays(7));
    
            Outer[] outers = {
                    new Outer(new Inner("a"), null),
                    new Outer(new Inner("b"), Date.from(now)),
                    new Outer(new Inner("c"), Date.from(nxtWeek)),
    
                    new Outer(new Inner("a"), Date.from(now)),
                    new Outer(new Inner("b"), Date.from(nxtWeek)),
                    new Outer(new Inner("c"), null),
    
            };
    
            Comparator<Outer> outerRanker = Comparator.comparing(Outer::getFinishDate, Comparator.nullsLast(Date::compareTo));
    
            Map<String, Outer> OutersById = Arrays.stream(outers)
                    .collect(
                            Collectors.toMap(
                                    outer -> outer.inner.id,
                                    outer -> outer,
                                    (a, b) -> outerRanker.compare(a,b) > 0 ? a : b
                                    ));
            System.out.println("today: "+Date.from(now));
            OutersById.forEach((k,v)->System.out.println(k+", "+v));
        }
    

    Results in

    today: Thu Jun 07 15:11:12 ACST 2018
    a, Outer{inner=Inner{id='a'}, FinishDate=null}
    b, Outer{inner=Inner{id='b'}, FinishDate=Thu Jun 14 15:11:12 ACST 2018}
    c, Outer{inner=Inner{id='c'}, FinishDate=null}
    
    Process finished with exit code 0