2016-03-24 4 views
15

Возможно ли переопределить Object#equals(Object) локально при использовании list.contains(someObject)?Переопределение объекта # равно (объект) вне класса

Пример:

class SomeObject { 
    ... 
    private int id; 
    private String name; 

    @Overrdide 
    public boolean equals(Object other){ 
     ... 
     return this.id == other.id; 
    } 
} 

Но что, если я хочу другой вид равных, когда я использую list.contains(someObject)? Например, я хочу знать, содержит ли список определенное имя? Можно ли переопределить Object#equals(Object) 'анонимно'?

Более конкретные объяснения, почему я должен был бы это:

int objectId = ... // Some event which passes me the attribute of an object but not the object itself 

Теперь у меня есть List<SomeObject> someObjects, и я хотел бы знать, если этот список содержит объект с objectId без необходимости переборе над ним.

Один «решение» я мог придумать бы использовать Map<Integer, SomeObject> mapping и затем someObject = mapping.get(objectId)

EDIT: Мой вопрос не является дубликатом, так как я специально с просьбой отменить Object#equals(Object).

+0

Это должно быть, но это ИМХО, самое плохое в API коллекций. Вы можете использовать пользовательский Компаратор во многих местах, но вы застряли только с одним понятием 'equals'. –

+1

Если у Java были только классы case :-( – SklogW

+0

Использование компаратора для задания действий равенства для ключей Set и for Map (но оно наносит ущерб производительности - операции становятся O (log n), потому что вам нужно использовать TreeMap, а не HashMap). Тем не менее, это не помогает в List List. Должно быть возможно использовать обычное понятие равенства с методами, такими как 'indexOf',' contains' и т. Д., Но это невозможно без создания класса-оболочки и переопределения равных, как вы хотите. –

ответ

16

Вы можете применить фильтр к объекту потока, полученному из списка. Фильтр принимает предикат, где вы можете установить условие. Затем вы можете проверить, если отфильтрованный поток не пуст:

list.stream().filter(e -> e.name.equals(otherName)).findAny().isPresent() 

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

private <T> boolean containsWithPredicate(List<T> list, Predicate<T> predicate) { 
    return list.stream().filter(predicate).findAny().isPresent(); 
} 

и называют его следующим образом:

boolean containsElement = containsWithPredicate(myListOfObjects, 
               (e -> e.name.equals(otherName))); 

EDIT:

Существует гораздо более простой способ сделать таже выше, просто вызывая Stream.anyMatch вместо делать filter затем findAny:

list.stream().anyMatch(predicate); 
+7

Не уверен, что это решение или временное решение, так как я мог бы также выполнять итерацию с циклом for-each. Но это определенно подход. – SklogW

+2

@SklogW Я думаю, что это решение. Нужен анонимный метод «равно», который может быть настроен, поэтому на первый взгляд возникает функциональное программирование стиля (вы можете передать предикат фильтра в качестве аргумента). Это также кажется более простым для чтения. – manouti

+2

@SklogW Это то, что вы на самом деле хотите сделать. Вы не хотите создавать новый метод 'equals', вы просите проверить, содержат ли списки вещи на основе определенных параметров, и для этого вы хотите [' Predicate'] (https://docs.oracle.com /javase/8/docs/api/java/util/function/Predicate.html) - это на самом деле то, что лямбда в ['filter'] (https://docs.oracle.com/javase/8/docs /api/java/util/stream/Stream.html#filter-java.util.function.Predicate-) метод есть. –

7

Вы можете использовать TreeSet с обычаем Comparator, чтобы достичь того, чего вы хотите. Он не использует equals() для равенства, но считает, что если compare() возвращает 0, объекты равны.

Допустим, мы хотим все Strings быть равными, если они имеют одинаковую длину:

TreeSet<String> set = new TreeSet(new Comparator<>() { 
    public int compare(String o1, String o2) { 
     return o1.length() - o2.length(); 
    } 
} 
set.add("asd"); 
set.contains("foo"); // Returns true 

Это может быть полезным конструкт (работает с TreeMap а) в тех случаях, когда вам нужно иметь различные «равенство определения "для объектов в разное время, в то же время работая с коллекциями.

+0

Но он все еще дает мне список с постоянным определением того, что равно или нет, не так ли? Что, если я хочу, чтобы какой-то один равный только один раз? Могу ли я изменить компаратор, например? – SklogW

+1

Он определит «что равно» для этой коллекции. Вы можете написать «Компаратор», чтобы он отличался логикой для разных объектов, но он станет более подверженным ошибкам, и вам нужно убедиться, что вы придерживаетесь контракта «compare()». – Kayaman

6

Невозможно иметь несколько equals для того же класса.Но вы можете подкласс ваш класс и переопределить hashCode() и equals(), а затем использовать нужный экземпляр в зависимости от equals, который вы хотите использовать.

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

+3

Хороший совет. Если мне нужны разные равные, то, может быть, мой дизайн - проблема. Я подумаю об этом. – SklogW

+1

@SklogW Это хорошая идея. Подумайте о других программистах, которые могут использовать вашу программу, как они узнают, какую реализацию выбрать? 'equals' должно быть действительно четко определено. – Maroun

+0

Это имеет смысл. Я думаю, что я пытаюсь работать с плохим дизайном. Спасибо ! – SklogW

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