2015-04-02 3 views
2

Я ищу способ узнать, идентичны ли два набора разных типов элементов, если я могу указать взаимное отношение между этими типами элементов. Есть ли стандартный способ сделать это в java или, возможно, в guava или apache?Сравните два набора типов differrent

Вот моя собственная реализация этой задачи. Например, у меня есть два класса элементов, которые я знаю, как сравнивать. Для простоты, я сравниваю их поле ID:

class ValueObject { 
    public int id; 
    public ValueObject(int id) { this.id=id; } 
    public static ValueObject of(int id) { return new ValueObject(id); } 
} 

class DTO { 
    public int id; 
    public DTO(int id) { this.id=id; } 
    public static DTO of(int id) { return new DTO(id); } 
} 

Тогда я определить интерфейс, который делает при сравнении

interface TwoTypesComparator<L,R> { 
    boolean areIdentical(L left, R right); 
} 

и фактический метод сравнения наборов выглядит как этот

public static <L,R> boolean areIdentical(Set<L> left, Set<R> right, TwoTypesComparator<L,R> comparator) { 
    if (left.size() != right.size()) return false; 
    boolean found; 
    for (L l : left) { 
     found = false; 
     for (R r : right) { 
      if (comparator.areIdentical(l, r)) { 
       found = true; break; 
      } 
     } 
     if (!found) return false; 
    } 
    return true; 
} 

Пример кода клиента

HashSet<ValueObject> valueObjects = new HashSet<ValueObject>(); 
valueObjects.add(ValueObject.of(1)); 
valueObjects.add(ValueObject.of(2)); 
valueObjects.add(ValueObject.of(3)); 

HashSet<DTO> dtos = new HashSet<DTO>(); 
dtos.add(DTO.of(1)); 
dtos.add(DTO.of(2)); 
dtos.add(DTO.of(34)); 

System.out.println(areIdentical(valueObjects, dtos, new TwoTypesComparator<ValueObject, DTO>() { 
    @Override 
    public boolean areIdentical(ValueObject left, DTO right) { 
     return left.id == right.id; 
    } 
})); 

Я ищу стандартное решение этой задачи. Или любые предложения по улучшению этого кода приветствуются.

ответ

1

Это то, что я хотел бы сделать в вашем случае. У вас есть наборы. Наборы трудно сравнивать, но, кроме того, вы хотите сравнить их идентификатор.

Я вижу только одно правильное решение, где вам нужно нормализовать нужные значения (извлечь их идентификатор), затем отсортировать эти идентификаторы, а затем сравнить их в порядке, потому что, если вы не сортируете и не сравниваете, вы можете пропустить пропуск дубликатов и/или значений.

Подумайте о том, что Java 8 позволяет вам играть ленивыми потоками. Так что не спешите и подумайте, что извлечение, а затем сортировка, а затем копирование длинное. Lazyness позволяет довольно быстро по сравнению с итерационными решениями.

HashSet<ValueObject> valueObjects = new HashSet<>(); 
valueObjects.add(ValueObject.of(1)); 
valueObjects.add(ValueObject.of(2)); 
valueObjects.add(ValueObject.of(3)); 

HashSet<DTO> dtos = new HashSet<>(); 
dtos.add(DTO.of(1)); 
dtos.add(DTO.of(2)); 
dtos.add(DTO.of(34)); 

boolean areIdentical = Arrays.equals(
    valueObjects.stream() 
     .mapToInt((v) -> v.id) 
     .sorted() 
     .toArray(), 
    dtos.stream() 
     .mapToInt((d) -> d.id) 
     .sorted() 
     .toArray() 
); 

Вы хотите, чтобы обобщение решения? Нет проблем.

public static <T extends Comparable<?>> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor) { 
    return Arrays.equals(
    vos.stream() 
     .map(voKeyExtractor) 
     .sorted() 
     .toArray(), 
    dtos.stream() 
     .map(dtoKeyExtractor) 
     .sorted() 
     .toArray() 
); 
} 

И для T, который не сравним:

public static <T> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor, Comparator<T> comparator) { 
    return Arrays.equals(
    vos.stream() 
     .map(voKeyExtractor) 
     .sorted(comparator) 
     .toArray(), 
    dtos.stream() 
     .map(dtoKeyExtractor) 
     .sorted(comparator) 
     .toArray() 
); 
} 

Вы упоминаете Guava, и если у вас нет Java 8, вы можете сделать следующее, используя тот же алгоритм:

List<Integer> voIds = FluentIterables.from(valueObjects) 
    .transform(valueObjectIdGetter()) 
    .toSortedList(intComparator()); 
List<Integer> dtoIds = FluentIterables.from(dtos) 
    .transform(dtoIdGetter()) 
    .toSortedList(intComparator()); 
return voIds.equals(dtoIds); 
+0

Наборы на самом деле очень легко и быстро сравнивать, но только тогда, когда они одного типа. – Kayaman

+0

Если бы они действительно были, в Гуаве не было [так много вспомогательных методов] (http://docs.guava-libraries.googlecode.com/git-history/release/javadoc/com/google/common/collect/ Sets.html). Не то, чтобы я против вашего слова, но «Set» явно страдает от недостатка мыслей, когда он впервые был разработан в Java 1.2. –

+0

Ну, большинство этих методов имеют форму 'newXXXSet', которую я бы подсчитал. Затем подсчитайте те, которые возвращают неизменяемые представления, и на самом деле у вас нет таких методов, которые обеспечивали бы несуществующие функции. – Kayaman

0

Вы можете переопределить равные и хэш-код на объекте DTO/значения, а затем сделать: leftSet.containsAll(rightSet) && leftSet.size().equals(rightSet.size())

Если вы не можете изменить классы элементов, сделать декоратор и имеете наборы быть типа декоратора.

+1

кроме того, что это грязное решение, что бы я сделал, если не могу изменить классы элементов –

+0

Почему это грязно? Если вы можете, t изменить классы элюции ... нормально, это другая проблема – NimChimpsky

+0

@ Кайаман, да? Нет, это не так. Как он нарушает что-либо, его рефлексивные, симметричные и т. Д. – NimChimpsky

0

Другим решением было бы использовать List вместо Set (если вам разрешено это делать). List имеет метод get (int index), который извлекает элемент по указанному индексу, и вы можете сравнивать их по одному, когда оба списка имеют одинаковый размер. Дополнительные сведения о списках: http://docs.oracle.com/javase/7/docs/api/java/util/List.html

Также избегайте использования общедоступных переменных в ваших классах. Хорошая практика заключается в том, чтобы сделать ваши переменные частными и использовать методы getter и setter.

Instantiate списки и добавить значения

List<ValueObject> list = new ArrayList<>(); 
    List<DTO> list2 = new ArrayList<>(); 

    list.add(ValueObject.of(1)); 
    list.add(ValueObject.of(2)); 
    list.add(ValueObject.of(3)); 

    list2.add(DTO.of(1)); 
    list2.add(DTO.of(2)); 
    list2.add(DTO.of(34)); 

Метод, который сравнивает списки

public boolean compareLists(List<ValueObject> list, List<DTO> list2) { 
    if(list.size() != list2.size()) { 
     return false; 
    } 
    for(int i = 0; i < list.size(); i++) { 
     if(list.get(i).id == list2.get(i).id) { 
      continue; 
     } else { 
      return false; 
     } 
    } 
    return true; 
} 
+0

Какая разница, если я повторяю элементы набора один за другим, что я на самом деле делаю. –

+0

Списки поддерживают порядок вставки. И из того, что я собираю, вы хотите, чтобы ваш результат был прав, только если каждая коллекция имеет одинаковые элементы в одинаковых положениях. Если я неверно истолковал ваш вопрос, пожалуйста, дайте мне знать. – SSJVegito

+0

нет, то же положение здесь не является обязательным требованием. На самом деле я использую интерфейс Set, который не требует сохранения позиций –

0

Ваш текущий метод является неправильным или, по крайней мере, непоследовательным для общих наборов.

Представьте следующее:

L содержит пары (1,1), (1,2), (2,1).

R содержит пары (1,1), (2,1), (2,2).

Теперь, если ваш идентификатор является первым значением, ваше сравнение вернет true, но действительно ли эти наборы равны? Проблема в том, что у вас нет гарантии, что в наборе есть не более одного элемента с тем же идентификатором, потому что вы не знаете, как L и R реализовать равны, поэтому мне советуют не сравнивать наборы разных типов.

Если вам действительно нужно сравнивать двух наборов, как вы описали, я бы для копирования всех элементов из L в список, а затем пройти через R и каждый раз, когда вы находите элемент в L удалить его из List. Просто убедитесь, что вы используете LinkedList вместо ArrayList.

Смежные вопросы