Подтвердить что ты не робот

Возможно ли в java сделать что-то вроде Comparator, но для реализации пользовательских equals() и hashCode()

У меня есть массив объектов, и я хочу объединить его с другим массивом объектов, за исключением тех объектов, которые имеют одинаковые идентификаторы. Эти объекты используются во многих местах в системе и не имеют хеш-кода или равных реализованных. Поэтому я не хочу реализовывать hashCode() и equals(), потому что я боюсь сломать что-то где-то в системе, где используются эти объекты, и я об этом не знаю. Поэтому я хочу поместить все эти объекты в набор, но каким-то образом сделать эти объекты использующими настраиваемые hashCode() и equals(). Что-то вроде пользовательского Comparator(), но для equals.

4b9b3361

Ответ 1

Да, это можно сделать. Но это не позволит вам помещать ваши объекты в HashMap, HashSet и т.д. Это потому, что стандартные классы классов ожидают ключевые объекты для предоставления методов equals и hashCode. (То, как они предназначены для работы...)

Альтернатива:

  • Внедрить класс-оболочку, содержащий экземпляр реального класса, и предоставляет собственную реализацию equals и hashCode.

  • Реализовать собственные классы на основе хэш-таблицы, которые могут использовать объект "hashable" для обеспечения равных и хэш-кодов.

  • Укусить пулю и реализовать equals и hashCode переопределяет соответствующие классы.

Фактически, третий вариант, вероятно, лучший, потому что ваша кодовая база, скорее всего, должна использовать последовательное представление о том, что означает, что эти объекты будут равны. Есть и другие вещи, которые предполагают, что ваш код нуждается в капитальном ремонте. Например, тот факт, что в настоящее время он использует массив объектов вместо реализации Set для представления того, что, по-видимому, должно быть набором.

С другой стороны, возможно, была/есть реальная (или предполагаемая) причина производительности для текущей реализации; например сокращение использования памяти. В этом случае вы, вероятно, должны написать кучу вспомогательных методов для выполнения операций, таких как объединение двух наборов, представленных в виде массивов.

Ответ 2

90% времени, когда пользователь хочет получить отношение эквивалентности, уже есть более прямое решение. Вы хотите де-дублировать кучу вещей на основе только идентификаторов? Можете ли вы просто поместить их все в карту с идентификаторами в качестве ключей, а затем получить коллекцию values()?

Ответ 3

HashingStrategy - это концепция, которую вы ищете. Это интерфейс стратегии, который позволяет вам определять пользовательские реализации equals и hashcode.

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

Как указывали другие, вы не можете использовать HashingStrategy со встроенными HashSet или HashMap. Eclipse Collections включает в себя набор под названием UnifiedSetWithHashingStrategy и карту под названием UnifiedMapWithHashingStrategy.

Посмотрим на пример. Вот простой класс Data, который мы можем использовать в UnifiedSetWithHashingStrategy.

public class Data
{
    private final int id;

    public Data(int id)
    {
        this.id = id;
    }

    public int getId()
    {
        return id;
    }

    // No equals or hashcode
}

Здесь вы можете настроить UnifiedSetWithHashingStrategy и использовать его.

java.util.Set<Data> set =
  new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(Data::getId));
Assert.assertTrue(set.add(new Data(1)));

// contains returns true even without hashcode and equals
Assert.assertTrue(set.contains(new Data(1)));

// Second call to add() doesn't do anything and returns false
Assert.assertFalse(set.add(new Data(1)));

Почему бы просто не использовать Map? UnifiedSetWithHashingStrategy использует половину памяти UnifiedMap, а одну четверть - память HashMap. И иногда у вас нет удобного ключа и вы должны создать синтетический, как кортеж. Это может потерять больше памяти.

Как мы выполняем поиск? Помните, что наборы имеют contains(), но не get(). UnifiedSetWithHashingStrategy реализует Pool в дополнение к MutableSet, поэтому он также реализует форму get().

Примечание. Я являюсь коммиттером для коллекций Eclipse.

Ответ 4

Конечно, вы можете создать некоторый внешний объект, обеспечивающий сравнение равенства и HashCode. Но встроенные коллекции Java не используют такой объект для их сравнения/поиска.

Я однажды создал такой интерфейс в моей коллекции пакетов (только что переведенный на английский язык):

public interface HashableEquivalenceRelation {

    /**
     * Returns true if two objects are considered equal.
     *
     * This should form an equivalence relation, meaning it
     * should fulfill these properties:
     *  <ul>
     *    <li>Reflexivity:  {@code areEqual(o, o)}
     *            should always return true.</li>
     *    <li>Symmetry: {@code areEqual(o1,o2) == areEqual(o2,o1)}
     *            for all objects o1 and o2</li>
     *    <li>Transitivity: If {@code areEqual(o1, o2)} and {@code areEqual(o2,o3)},
     *            then {@code areEqual(o1,o3}} should hold too.</li>
     *  </ul>
     * Additionally, the relation should be temporary consistent, i.e. the
     * result of this method for the same two objects should not change as
     * long as the objects do not change significantly (the precise meaning of
     * <em>change significantly</em> is dependent on the implementation).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public boolean areEqual(Object o1, Object o2);

    /**
     * Returns a hashCode for an arbitrary object.
     *
     * This should be temporary consistent, i.e. the result for the same
     * objects should not change as long as the object does not change significantly
     * (with change significantly having the same meaning as for {@link areEqual}).
     *
     * Also, if {@code areEqual(o1, o2)} holds true, then {@code hashCode(o1) == hashCode(o2)}
     * must be true too.
     */
    public int hashCode(Object o);

}

Чем я имел группу интерфейсов CustomCollection, CustomSet, CustomList, CustomMap и т.д., определенных как интерфейсы в java.util, но используя такое отношение эквивалентности для всех методов вместо построенное отношение Object.equals. У меня также были некоторые реализации по умолчанию:

/**
 * The equivalence relation induced by Object#equals.
 */
public final static EquivalenceRelation DEFAULT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return
                o1 == o2 ||
                o1 != null &&
                o1.equals(o2);
        }
        public int hashCode(Object ob)
        {
            return
                ob == null?
                0 :
                ob.hashCode();
        }
        public String toString() { return "<DEFAULT>"; }
    };

/**
 * The equivalence relation induced by {@code ==}.
 * (The hashCode used is {@link System#identityHashCode}.)
 */
public final static EquivalenceRelation IDENTITY =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return o1 == o2; }
        public int hashCode(Object ob) { return System.identityHashCode(ob); }
        public String toString() { return "<IDENTITY>"; }
    };

/**
 * The all-relation: every object is equivalent to every other one.
 */
public final static EquivalenceRelation ALL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) { return true; }
        public int hashCode(Object ob) { return 0; }
        public String toString() { return "<ALL>"; }
    };

/**
 * An equivalence relation partitioning the references
 * in two groups: the null reference and any other reference.
 */
public final static EquivalenceRelation NULL_OR_NOT_NULL =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return (o1 == null && o2 == null) ||
                (o1 != null && o2 != null);
        }
        public int hashCode(Object o) { return o == null ? 0 : 1; }
        public String toString() { return "<NULL_OR_NOT_NULL>"; }
    };

/**
 * Two objects are equivalent if they are of the same (actual) class.
 */
public final static EquivalenceRelation SAME_CLASS =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == o2 || o1 != null && o2 != null &&
                o1.getClass() == o2.getClass();
        }
        public int hashCode(Object o) { return o == null ? 0 : o.getClass().hashCode(); }
        public String toString() { return "<SAME_CLASS>"; }
    };


/**
 * Compares strings ignoring case.
 * Other objects give a {@link ClassCastException}.
 */
public final static EquivalenceRelation STRINGS_IGNORE_CASE =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2)
        {
            return o1 == null ?
                o2 == null :
                ((String)o1).equalsIgnoreCase((String)o2);
        }
        public int hashCode(Object o)
        {
            return o == null ? -12345 : ((String)o).toUpperCase().hashCode();
        }
        public String toString() { return "<STRINGS_IGNORE_CASE>"; }
    };


/**
 * Compares {@link CharSequence} implementations by content.
 * Other object give a {@link ClassCastException}.
 */
public final static EquivalenceRelation CHAR_SEQUENCE_CONTENT =
    new EquivalenceRelation() {
        public boolean areEqual(Object o1, Object o2) 
        {
            CharSequence seq1 = (CharSequence)o1;
            CharSequence seq2 = (CharSequence)o2;
            if (seq1 == null ^ seq2 == null) // nur eins von beiden null
                return false;
            if (seq1 == seq2)   // umfasst auch den Fall null == null
                return true;
            int size = seq1.length();
            if (seq2.length() != size)
                return false;
            for (int i = 0; i < size; i++)
                {
                    if (seq1.charAt(i) != seq2.charAt(i))
                        return false;
                }
            return true;
        }
        /**
         * Entrspricht String.hashCode
         */
        public int hashCode(Object o)
        {
            CharSequence sequence = (CharSequence)o;
            if (sequence == null)
                return 0;
            int hash = 0;
            int size = sequence.length();
            for (int i = 0; i < size; i++)
                {
                    hash = hash * 31 + sequence.charAt(i);
                }
            return hash;
        }
    };

Ответ 5

Использует ли здесь TreeSet помощь здесь? TreeSet фактически выполняет упорядочение и устанавливает поведение на основе с помощью compare/compareTo и позволяет вам определить пользовательский Компаратор для использования я n одного из конструкторов.

Ответ 6

Просто возникла эта проблема и выработал простое решение. Не уверен, насколько интенсивна память; Я уверен, что люди могут усовершенствовать его на линии.

Когда Comparator возвращает 0, элементы соответствуют.

public static <E> Set<E> filterSet(Set<E> set, Comparator<E> comparator){
    Set<E> output = new HashSet<E>();
    for(E eIn : set){
        boolean add = true;
        for(E eOut : output){
            if(comparator.compare(eIn, eOut) == 0){
                add = false;
                break;
            }
        }
        if(add) output.add(eIn);
    }
    return output;
}

Моим вариантом было то, что мне нужно было отфильтровать повторяющиеся URL-адреса, как в URL-адресах, указывающих на один и тот же документ. Объект URL имеет метод samePage(), который вернет true, если все, кроме фрагмента, будет одинаковым.

filtered = Misc.filterSet(filtered, (a, b) -> a.sameFile(b) ? 0 : 1);

Ответ 7

Вам не удастся выполнить де-дублирование конкатенации с помощью компаратора. Предположительно, вы хотите сделать что-то вроде этого:

List<Object> list = new ArrayList<Object>();
list.addAll( a );
list.addAll( b );
Collections.sort( list, new MyCustomComparator() );

Проблема заключается в том, что Компаратор должен сравнивать не только для equals/not-equals, но и для относительного порядка. Если объекты x и y не равны, вы должны ответить, если один из них больше другого. Вы не сможете этого сделать, поскольку на самом деле вы не пытаетесь сравнивать объекты. Если вы не дадите последовательный ответ, вы отправите алгоритм сортировки в бесконечный цикл.

У меня есть решение для вас. Java имеет класс LinkedHashSet, чья добродетель заключается в том, что он не позволяет вставлять дубликаты, но поддерживает порядок вставки. Вместо реализации компаратора реализуем класс-оболочку для хранения фактического объекта и реализации hashCode/equals.