使用自定义标签的标签

发布于 2025-02-04 07:19:28 字数 7238 浏览 3 评论 0 原文

有人可以向我指出正确的方向:我想拥有一个自定义标签而无需更改 hashcode()/equals()方法。

用法是拥有一组必须具有不同属性(或更多)的对象。

因此,例如,对于此类:

@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter @Setter
public class User{
    String name;
    String email;
    String age;
}

我想拥有 usernameset ,它将仅包含具有不同名称的用户。我不想覆盖用户中的主题和等于用户的方法,因为我仍然想区分具有相同名称但不同电子邮件的用户。

我想以某种方式“ Override” hashcode()/equals()方法仅适用于此 hashmap

编辑

我乍看之下提出了这个解决方案,有人可以检查吗?

package com.znamenacek.debtor.util;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@FieldDefaults(level = AccessLevel.PRIVATE)
public class CustomizableHashSet<T> implements Set<T> {
    Function<T, Integer> customHashCode = Object::hashCode;
    HashSet<ClassWrapper> storage = new HashSet<>();

    public CustomizableHashSet(Function<T, Integer> customHashCode) {
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet() {}

    public CustomizableHashSet(Collection<? extends T> c, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(c.stream().map(ClassWrapper::new).toList());
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(Collection<? extends T> c) {
        storage = new HashSet<>(c.stream().map(ClassWrapper::new).toList());
    }

    public CustomizableHashSet(int initialCapacity, float loadFactor, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(initialCapacity, loadFactor);
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(int initialCapacity, float loadFactor) {
        storage = new HashSet<>(initialCapacity, loadFactor);
    }

    public CustomizableHashSet(int initialCapacity, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(initialCapacity);
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(int initialCapacity) {
        storage = new HashSet<>(initialCapacity);
    }

    @Override
    public Iterator<T> iterator() {
        return storage.stream().map(ClassWrapper::get).iterator();
    }

    @Override
    public int size() {
        return storage.size();
    }

    @Override
    public boolean isEmpty() {
        return storage.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).contains(o);
    }

    @Override
    public boolean add(T t) {
        return storage.add(new ClassWrapper(t));
    }

    @Override
    public boolean remove(Object o) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.remove(o);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public void clear() {
        storage.clear();
    }

    @Override
    public Object clone() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Spliterator<T> spliterator() {
        return storage.stream().map(ClassWrapper::get).spliterator();
    }

    @Override
    public Object[] toArray() {
        return storage.stream().map(ClassWrapper::get).toArray();
    }

    @Override
    public <T1> T1[] toArray(T1[] a) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toArray(a);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.removeAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.containsAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        return storage.addAll(c.stream().map(ClassWrapper::new).collect(Collectors.toSet()));
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.retainAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public String toString() {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toString();
    }

    @Override
    public <T1> T1[] toArray(IntFunction<T1[]> generator) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toArray(generator);
    }

    @Override
    public boolean removeIf(Predicate<? super T> filter) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.removeIf(filter);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public Stream<T> stream() {
        return storage.stream().map(ClassWrapper::get);
    }

    @Override
    public Stream<T> parallelStream() {
        return storage.parallelStream().map(ClassWrapper::get);
    }

    @Override
    public void forEach(Consumer<? super T> action) {
        storage.stream().map(ClassWrapper::get).forEach(action);
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @AllArgsConstructor
    public class ClassWrapper{
        T object;

        @Override
        public int hashCode() {
            return customHashCode.apply(object);
        }

        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;

            if(obj == null) return false;

            return hashCode() == obj.hashCode();
        }

        public T get(){
            return object;
        }

        @Override
        public String toString() {
            return "" + hashCode() + " - " + object.toString();
        }
    }
}

Could someone point me the right direction: I would like to have a custom HashSet without changing hashCode()/equals() methods.

Usage would be to have a Set of objects which must have different one attribute (or more).

So, for example, for this Class:

@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter @Setter
public class User{
    String name;
    String email;
    String age;
}

I would like to have UserNameSet which would allow to contain only users which have different name. I do not want to override the hashCode and equals method in User, because I still want to differentiate between users with same name but different email for example.

I would like to somehow "override" the hashCode()/equals() method just for this one HashMap.

EDITED

I have come up with this solution on first glance it works, could someone check it?

package com.znamenacek.debtor.util;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@FieldDefaults(level = AccessLevel.PRIVATE)
public class CustomizableHashSet<T> implements Set<T> {
    Function<T, Integer> customHashCode = Object::hashCode;
    HashSet<ClassWrapper> storage = new HashSet<>();

    public CustomizableHashSet(Function<T, Integer> customHashCode) {
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet() {}

    public CustomizableHashSet(Collection<? extends T> c, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(c.stream().map(ClassWrapper::new).toList());
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(Collection<? extends T> c) {
        storage = new HashSet<>(c.stream().map(ClassWrapper::new).toList());
    }

    public CustomizableHashSet(int initialCapacity, float loadFactor, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(initialCapacity, loadFactor);
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(int initialCapacity, float loadFactor) {
        storage = new HashSet<>(initialCapacity, loadFactor);
    }

    public CustomizableHashSet(int initialCapacity, Function<T, Integer> customHashCode) {
        storage = new HashSet<>(initialCapacity);
        this.customHashCode = customHashCode;
    }

    public CustomizableHashSet(int initialCapacity) {
        storage = new HashSet<>(initialCapacity);
    }

    @Override
    public Iterator<T> iterator() {
        return storage.stream().map(ClassWrapper::get).iterator();
    }

    @Override
    public int size() {
        return storage.size();
    }

    @Override
    public boolean isEmpty() {
        return storage.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).contains(o);
    }

    @Override
    public boolean add(T t) {
        return storage.add(new ClassWrapper(t));
    }

    @Override
    public boolean remove(Object o) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.remove(o);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public void clear() {
        storage.clear();
    }

    @Override
    public Object clone() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Spliterator<T> spliterator() {
        return storage.stream().map(ClassWrapper::get).spliterator();
    }

    @Override
    public Object[] toArray() {
        return storage.stream().map(ClassWrapper::get).toArray();
    }

    @Override
    public <T1> T1[] toArray(T1[] a) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toArray(a);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.removeAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.containsAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public boolean addAll(Collection<? extends T> c) {
        return storage.addAll(c.stream().map(ClassWrapper::new).collect(Collectors.toSet()));
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.retainAll(c);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public String toString() {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toString();
    }

    @Override
    public <T1> T1[] toArray(IntFunction<T1[]> generator) {
        return storage.stream().map(ClassWrapper::get).collect(Collectors.toSet()).toArray(generator);
    }

    @Override
    public boolean removeIf(Predicate<? super T> filter) {
        boolean returnValue;
        var storageContent = storage.stream().map(ClassWrapper::get).collect(Collectors.toSet());
        returnValue = storageContent.removeIf(filter);
        storage = storageContent.stream().map(ClassWrapper::new).collect(Collectors.toCollection(HashSet::new));

        return returnValue;
    }

    @Override
    public Stream<T> stream() {
        return storage.stream().map(ClassWrapper::get);
    }

    @Override
    public Stream<T> parallelStream() {
        return storage.parallelStream().map(ClassWrapper::get);
    }

    @Override
    public void forEach(Consumer<? super T> action) {
        storage.stream().map(ClassWrapper::get).forEach(action);
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @AllArgsConstructor
    public class ClassWrapper{
        T object;

        @Override
        public int hashCode() {
            return customHashCode.apply(object);
        }

        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;

            if(obj == null) return false;

            return hashCode() == obj.hashCode();
        }

        public T get(){
            return object;
        }

        @Override
        public String toString() {
            return "" + hashCode() + " - " + object.toString();
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

草莓酥 2025-02-11 07:19:28

使用比较器 with treetet

as 评论约翰内斯·库恩(Johannes Kuhn),您可以使用 navigableset (或 sortedset )或在Java 21+, segendset jep 431 )。 无需发明自己的班级。

navigableset 例如 tree> treetet 可能会提供一个构造函数,以a cormactor cormactor 。该比较器用于对集合的元素进行排序。

在这个问题中,我们的观点是,比较器也用于决定接受新的不同元素,而不是使用元素的对象#equals 方法。

而且,由于 treeTet 中没有散列的散列,因此不担心覆盖 hashcode

我们可以轻松地定义比较器实现。为了方便起见,我们可以致电 comparator.comparing 进行比较器实现。我们通过传递所需 name 字段的getter方法的方法参考来定义比较器: user :: name

您可以通过调用 thencomparparing 。我将其作为读者的练习。

对于简洁起见,让我们将您的用户类定义为记录。我们只是声明成员字段的类型和名称。编译器隐式创建构造函数,getters, equals &amp; HashCode toString

record User( String name , String email , int age ) { }

制作一些示例数据。

List < User > listOfUsers =
        List.of(
                new User( "Bob" , "[email protected]" , 7 ) ,
                new User( "Alice" , "[email protected]" , 42 ) ,
                new User( "Carol" , "[email protected]" , 77 )
        );

定义我们的集合, treetet

NavigableSet < User > setOfUsers = new TreeSet <>( Comparator.comparing( User :: name )  );

用3个元素填充我们的场景。通过倾倒到控制台来验证3个元素。

setOfUsers.addAll( listOfUsers );
System.out.println( setOfUsers.size() + " elements in setOfUsers = " + setOfUsers );

现在,我们尝试在其他字段中添加另一个具有相同名称但不同值的用户。

setOfUsers.add( new User( "Alice" , "[email protected]" , -666  ) );

默认情况下,<代码>记录通过比较每个成员字段来决定平等。因此:

  • 如果我们仅使用名称进行比较的目标失败,我们将在此集合中获得4个元素。
  • 如果我们仅成功使用 name ,那么在阻止了该介入者的入学后,我们应该获得3个元素。

转储到控制台。

System.out.println( setOfUsers.size() + " elements in setOfUsers = " + setOfUsers );

setofusers中的3个元素= [user [name = alice,,age = 42],用户[name = bob,,年龄= 7],用户[name = carol,,年龄= 77]]


setOfusers中的3个元素= [user [name = alice,,age = 42],用户[name = bob,,年龄= 7],用户[name = carol,,年龄= 77]]


我们在这些结果中看到(a)按名称对元素进行排序,(b)用原始阻止第二个 alice 爱丽丝剩下。

要查看替代行为,请用以下方式替换 setOfusers 定义:

Set < User > setOfUsers = new HashSet <>();

setOfusers.size()中,运行该版本会导致该版本。

setOfusers中的3个元素= [user [name = bob,,年龄= 7],用户[name = carol,,年龄= 77],用户[name = alice,,年龄= 42]]


setofusers中的4个元素= [user [name = bob, /a>,年龄= 7],用户[name = carol, /a>,年龄= 77],用户[name = alice,,年龄= 42],用户[name = alice, /a>,age = -666]]


我们在这些结果中看到(a)没有特定的排序,(b)添加第二个“爱丽丝”,将集合从3个元素增加到4

。我在这里解决方案的可能缺点是,我们违反了 treeset 的Javadoc的建议,“与 equals > equals ”,从而违反了 set的一般合同

我不确定这个问题是否有问题 - 我没有足够的观点来形成判断。

Use Comparator with TreeSet

As commented by Johannes Kuhn, you can get your desired behavior by using a NavigableSet (or SortedSet) or, in Java 21+,SequencedSet (JEP 431). No need to invent your own class.

Implementations of NavigableSet such as TreeSet may offer a constructor taking a Comparator object. That Comparator is used for sorting the elements of the set.

To our point here in this Question, that Comparator is also used in deciding to admitting new distinct elements rather than using the elements’ own Object#equals method.

And since there is no hashing involved in a TreeSet, there is no concern about overriding hashCode.

We can easily define our Comparator implementation. For convenience, we can call Comparator.comparing to make a comparator implementation. We define the comparator by passing a method reference for the getter method of your desired name field: User :: name.

You can add more criteria to your comparator by calling thenComparing. I leave that as an exercise for the reader.

For brevity, let's define your User class as a record. We simply declare the type and name of member fields. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.

record User( String name , String email , int age ) { }

Make some sample data.

List < User > listOfUsers =
        List.of(
                new User( "Bob" , "[email protected]" , 7 ) ,
                new User( "Alice" , "[email protected]" , 42 ) ,
                new User( "Carol" , "[email protected]" , 77 )
        );

Define our set, a TreeSet.

NavigableSet < User > setOfUsers = new TreeSet <>( Comparator.comparing( User :: name )  );

Populate our set with 3 elements. Verify 3 elements by dumping to console.

setOfUsers.addAll( listOfUsers );
System.out.println( setOfUsers.size() + " elements in setOfUsers = " + setOfUsers );

Now we try to add another user with the same name but different values in the other fields.

setOfUsers.add( new User( "Alice" , "[email protected]" , -666  ) );

By default, a record decides on equality by comparing each and every member field. So:

  • If we have failed in our goal of using only name for comparison, we would get 4 elements in this set.
  • If we have succeeded in using only name, then we should get 3 elements after having blocked admission of this interloper.

Dump to console.

System.out.println( setOfUsers.size() + " elements in setOfUsers = " + setOfUsers );

3 elements in setOfUsers = [User[name=Alice, [email protected], age=42], User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77]]

3 elements in setOfUsers = [User[name=Alice, [email protected], age=42], User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77]]

We see in those results (a) sorting of the elements by name, and (b) Blocking of the second Alice, with the original Alice remaining.

To see the alternate behavior, replace the setOfUsers definition with this:

Set < User > setOfUsers = new HashSet <>();

Running that edition of the code results in setOfUsers.size() being:

3 elements in setOfUsers = [User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77], User[name=Alice, [email protected], age=42]]

4 elements in setOfUsers = [User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77], User[name=Alice, [email protected], age=42], User[name=Alice, [email protected], age=-666]]

We see in those results (a) no particular sorting, and (b) the addition of a second "Alice", having increased the set from 3 elements to 4.

Caveat

One possible downside to my solution here is that we are violating the recommendation of the Javadoc of TreeSet to be “consistent with equals”, thereby violating the general contract of Set.

I am not sure if that issue is problematic or not — I do not have enough perspective to form a judgement.

药祭#氼 2025-02-11 07:19:28

干净有效

我想拥有一个用户名,它只能包含具有不同名称的用户

您可以应用构图并创建一个可以维护 map 的类,并委派所有调用。

封装收藏品并提供有限的访问权限的方法比扩展集合更加灵活,更乏味,而且也不会产生紧密的耦合。

类似的建议,以及通过扩展您的代码取决于现有收藏的弊端的示例,项目偏爱继承

这就是这样的类的样子:

class UserNameSet {
    private Map<String, User> userByName = new HashMap<>();
    
    public User add(User user) {
        return userByName.put(user.getName(), user); // or `putIfAbsent()` if you want to retain the previously added user
    }
    
    public User remove(User user) {
        return userByName.remove(user.getName());
    }
    
    public boolean contains(User user) {
        return userByName.containsValue(user.getName());
    }
    
    public User remove(String name) {
        return userByName.remove(name);
    }
    
    public boolean contains(String name) {
        return userByName.containsKey(name);
    }
    
    // all other methods that are required
}

我们还可以使此类成为 generic 并能够包装任何对象。

为此,我们需要引入一个其他参数-A 函数,该函数将负责从对象中提取目标属性。

class MyCustomSet<K, V> {
    private Map<K, V> userByName = new HashMap<>();
    private Function<V, K> keyExtractor;
    
    public MyCustomSet(Function<V, K> keyExtractor) {
        this.keyExtractor = keyExtractor;
    }
    
    public V add(V user) {
        return userByName.put(keyExtractor.apply(user), user); // or `putIfAbsent()` if you want to retain the previously added user
    }
    
    public V remove(V user) {
        return userByName.remove(keyExtractor.apply(user));
    }
    
    public boolean contains(V user) {
        return userByName.containsValue(user);
    }
    
    public V removeByKey(K name) {
        return userByName.remove(name);
    }
    
    public boolean containsKey(K name) {
        return userByName.containsKey(name);
    }
    
    // all other methods that are required
}

这就是在客户端代码中实例化的方式:

MyCustomSet<String, User> uniqueNameUsers = new MyCustomSet<>(User::getName);

简洁而简单地

使用排序的集合的方法,例如 treeset A/72482101/17949945“> basil的Bourque答案 可用于相对较小的对象数量。

重要的是要强调,将数据存储在排序的集合中的成本。它们越大,操作速度较慢。 treeset Tree 和大多数操作(例如基本 add(), remove> remove() and contains()操作要求 o(n)时间,除了最低/最高键时,除了边缘箱外。

Clean and Efficient

I would like to have UserNameSet which would allow to contain only users which have different name

You can apply composition and create a class that would maintain a Map and delegate all calls to it.

The approach of encapsulating a collection and providing limited access to it is more flexible and less tedious than extending a collection, and also doesn't create a tight coupling.

Similar advice, and also examples of disadvantages that come with making your code dependent on the existing collection by extending it, you can find in the book "Effective Java" by Joshua Bloch, item Favor composition over inheritance.

That's how such a class might look like:

class UserNameSet {
    private Map<String, User> userByName = new HashMap<>();
    
    public User add(User user) {
        return userByName.put(user.getName(), user); // or `putIfAbsent()` if you want to retain the previously added user
    }
    
    public User remove(User user) {
        return userByName.remove(user.getName());
    }
    
    public boolean contains(User user) {
        return userByName.containsValue(user.getName());
    }
    
    public User remove(String name) {
        return userByName.remove(name);
    }
    
    public boolean contains(String name) {
        return userByName.containsKey(name);
    }
    
    // all other methods that are required
}

We can also make this class to be generic and capable of wrapping any object.

For that, we need to introduce an additional argument - a function that will be responsible for extracting the target property from the object.

class MyCustomSet<K, V> {
    private Map<K, V> userByName = new HashMap<>();
    private Function<V, K> keyExtractor;
    
    public MyCustomSet(Function<V, K> keyExtractor) {
        this.keyExtractor = keyExtractor;
    }
    
    public V add(V user) {
        return userByName.put(keyExtractor.apply(user), user); // or `putIfAbsent()` if you want to retain the previously added user
    }
    
    public V remove(V user) {
        return userByName.remove(keyExtractor.apply(user));
    }
    
    public boolean contains(V user) {
        return userByName.containsValue(user);
    }
    
    public V removeByKey(K name) {
        return userByName.remove(name);
    }
    
    public boolean containsKey(K name) {
        return userByName.containsKey(name);
    }
    
    // all other methods that are required
}

And that's how it would be instantiated in the client code:

MyCustomSet<String, User> uniqueNameUsers = new MyCustomSet<>(User::getName);

Concise and Simple

The approach of utilizing sorted collections like TreeSet described in the Basil's Bourque answer can be used for a relatively small number of objects.

It's important to emphasize that storing the data in sorted collections has a cost. The larger they become, the slower they operate. TreeSet is backed by the Red-black tree and most of the actions like basic add(), remove() and contains() operations require O(n) time, apart from the edge-cases when we deal with the lowest/highest key.

梦里寻她 2025-02-11 07:19:28

Commons-Collections已经提供了赤道接口可以按照您的建议进行操作:

public interface Equator<T> {
    boolean equate(T o1, T o2);
    int hash(T o);
}

无论基于赤道创建集合的直接支持是有限的。 collectionUtils

但是,您可以利用将所需的对象包装到使用赤道的对象中,然后使用所有支持Commons-Collections为变压器提供的对象。 For example using

class EquatorWrapper<T> {
    private final Class<T> clazz;
    private final T wrapped;
    private final Equator<T> equator;

    public EquatorWrapper(Class<T> clazz, T wrapped, Equator<T> equator) {
        this.clazz = clazz;
        this.wrapped = wrapped;
        this.equator = equator;
    }

    @Override
    public boolean equals(Object obj) {
        if (clazz.isInstance(obj)) {
            return equator.equate(wrapped, clazz.cast(obj));
        }
        return false;
    }

    @Override
    public int hashCode() {
        return equator.hash(wrapped);
    }
}

class EquatorTransformer<T> implements Transformer<T, Object> {
    private final Class<T> clazz;
    private final Equator<T> equator;

    public EquatorTransformer(Class<T> clazz, Equator<T> equator) {
        this.clazz = clazz;
        this.equator = equator;
    }
        
    @Override
    public Object transform(T input) {
        return new EquatorWrapper<>(clazz, input, equator);
    }
}

SetUtils.transformedSet(someSet, EquatorTransformer.of(someEquator, SomeClazz.clazz));

commons-collections already provides an Equator interface to do what you suggest:

public interface Equator<T> {
    boolean equate(T o1, T o2);
    int hash(T o);
}

However direct support for creating collections based on an equator is limited. There are a few operations involving an equator available in CollectionUtils.

However you could leverage Transformer to wrap your desired objects into ones that use an equator and then use all the support commons-collections provides for transformers. For example using SetUtils.transformedSet:

class EquatorWrapper<T> {
    private final Class<T> clazz;
    private final T wrapped;
    private final Equator<T> equator;

    public EquatorWrapper(Class<T> clazz, T wrapped, Equator<T> equator) {
        this.clazz = clazz;
        this.wrapped = wrapped;
        this.equator = equator;
    }

    @Override
    public boolean equals(Object obj) {
        if (clazz.isInstance(obj)) {
            return equator.equate(wrapped, clazz.cast(obj));
        }
        return false;
    }

    @Override
    public int hashCode() {
        return equator.hash(wrapped);
    }
}

class EquatorTransformer<T> implements Transformer<T, Object> {
    private final Class<T> clazz;
    private final Equator<T> equator;

    public EquatorTransformer(Class<T> clazz, Equator<T> equator) {
        this.clazz = clazz;
        this.equator = equator;
    }
        
    @Override
    public Object transform(T input) {
        return new EquatorWrapper<>(clazz, input, equator);
    }
}

SetUtils.transformedSet(someSet, EquatorTransformer.of(someEquator, SomeClazz.clazz));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文