Объединить 2 списка объектов с различными значениями полей

У меня есть 2 списка, в которых лежат объекты одного класса, их нужно объединить в один список. Класс:

public class Stat {

   private String name;

   private String date;

   private Long work1;

   private Long work2;

   @Override
   public boolean equals(Object o) {
     if (o == null || getClass() != o.getClass()) {
         return false;
     }
     Stat that = (Stat) o;
     return getName().equals(that.getName()) && getDate().equals(that.getDate());
     }

  //getters, setters, hashCode 
}

В обоих списках могут совпадают имена и дата, но в первом лежит зн-е work1, во втором work2, количество элементов может отличаться. Например:

List1<Stat> list1 = {1. name = name1, date = date1, work1 = 4, work2 = null
                     2. name = name2, date = date2, work1 = 5, work2 = null}

List<Stat> list2 = {1. name = name1, date = date1, work1 = null, work2 = 3
                    2. name = name2, date = date2, work1 = null, work2 = 4
                    3. name = name3, date = date3, work1 = null, work2 = 5}

List<Stat> result = {1. name = name1, date = date1, work1 = 4, work2 = 3
                     2. name = name2, date = date2, work1 = 5, work2 = 4
                     3. name = name3, date = date3, work1 = null, work2 = 5}

Я сделал способом, который показан ниже, но хотел бы узнать у более опытных коллег, можно ли это сделать более изящно, потому что я не нашёл способа

private Set<Stat> method() {
   Set<Stat> result = new HashSet<>();
   addResult(list1, result);
   addResult(list2, result);
   return result;
}

private void addResult(List<Stat> stats, Set<Stat> result) {
    for (Stat stat : stats) {
        for (Stat res : result) {
            if (stat.equals(res)) {
                Long work1 = stat.getWork1();
                if (work1 != null) {
                    res.setWork1(work1);
                }
                Long work2 = stat.getWork2();
                if (work2 != null) {
                    res.setWork2(work2);
                }
            }
        }
        result.add(stat);
    }
}

Ответы (1 шт):

Автор решения: Regent

Учитывая необходимость мерджить значения work1 и work2, вряд ли будет более короткое и изящное решение

Однако в текущем решении меня напрягает несколько моментов:

  1. Поиск по Set идёт за линейное время (O(n)), что противоестественно для него
  2. result.add(stat) происходит для всех элементов (даже для тех, что уже есть в result). Это выглядит странно с точки зрения логики алгоритма ("если элемент уже есть в Set - обновить имеющийся, иначе добавить этот") и не ломается лишь за счёт того, что элемент не добавляется повторно (в том числе не заменяет уже имеющийся элемент) в HashSet
  3. Модифицируются элементы исходных массивов. В каких-то ситуациях это допустимо, но в общем случае в результирующей коллекции должны быть новые, смердженные элементы

Вариант с исправлением только пунктов 1 и 2:

private Collection<Stat> method()
{
   Map<Integer, Stat> result = new HashMap<>();
   addResult(list1, result);
   addResult(list2, result);
   return result.values();
}

private void addResult(List<Stat> stats, Map<Integer, Stat> result)
{
    for (Stat stat : stats)
    {
        int key = stat.hashCode();

        if (result.containsKey(key))
        {
            Stat res = result.get(key);
            
            Long work1 = stat.getWork1();
            if (work1 != null)
            {
                res.setWork1(work1);
            }
            
            Long work2 = stat.getWork2();
            if (work2 != null)
            {
                res.setWork2(work2);
            }
        }
        else
        {
            result.put(key, stat);
        }
    }
}

Вместо HashSet используется HashMap, чтобы не нужно было пробегать по всему result в поисках подходящего элемента. В качестве ключа HashMap используется hashcode от Stat - можно использовать и сам объект Stat, но такой код будет сложнее в понимании

Вариант с исправлением пунктов 1-3 (метод method выглядит так же как и в предыдущем примере):

private void addResult(List<Stat> stats, Map<Integer, Stat> result)
{
    for (Stat stat : stats)
    {
        int key = stat.hashCode();

        if (result.containsKey(key))
        {
            Stat res = result.get(key);
            
            Long mergedWork1 = chooseWork(stat.getWork1(), res.getWork1());
            Long mergedWork2 = chooseWork(stat.getWork2(), res.getWork2());
            
            Stat mergedStat = new Stat(stat.getName(), stat.getDate(), mergedWork1, mergedWork2);
            result.put(key, mergedStat);
        }
        else
        {
            result.put(key, stat);
        }
    }
}

private static Long chooseWork(Long firstWork, Long secondWork)
{
    return firstWork == null ? secondWork : firstWork;
}

В данном случае в результирующую коллекцию попадают как оригинальные объекты Stat (если не требовался мердж для них), так и новые, смердженные

→ Ссылка