使用自定义标签的标签
有人可以向我指出正确的方向:我想拥有一个自定义标签
而无需更改 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();
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
使用
比较器
withtreetet
as 评论约翰内斯·库恩(Johannes Kuhn),您可以使用
navigableset
(或sortedset
)或在Java 21+,segendset
( jep 431 )。 无需发明自己的班级。
navigableset
例如tree> treetet
可能会提供一个构造函数,以acormactor
cormactor 。该比较器
用于对集合的元素进行排序。在这个问题中,我们的观点是,
比较器
也用于决定接受新的不同元素,而不是使用元素的对象#equals
方法。而且,由于
treeTet
中没有散列的散列,因此不担心覆盖hashcode
。我们可以轻松地定义
比较器
实现。为了方便起见,我们可以致电comparator.comparing
进行比较器实现。我们通过传递所需name
字段的getter方法的方法参考来定义比较器:user :: name
。您可以通过调用
thencomparparing
。我将其作为读者的练习。对于简洁起见,让我们将您的
用户
类定义为记录。我们只是声明成员字段的类型和名称。编译器隐式创建构造函数,getters,equals
&amp;HashCode
和toString
。制作一些示例数据。
定义我们的集合,
treetet
。用3个元素填充我们的场景。通过倾倒到控制台来验证3个元素。
现在,我们尝试在其他字段中添加另一个具有相同名称但不同值的用户。
默认情况下,<代码>记录通过比较每个成员字段来决定平等。因此:
名称
进行比较的目标失败,我们将在此集合中获得4个元素。name
,那么在阻止了该介入者的入学后,我们应该获得3个元素。转储到控制台。
我们在这些结果中看到(a)按名称对元素进行排序,(b)用原始
阻止第二个
剩下。alice
爱丽丝要查看替代行为,请用以下方式替换
setOfusers
定义:在
setOfusers.size()
中,运行该版本会导致该版本。我们在这些结果中看到(a)没有特定的排序,(b)添加第二个“爱丽丝”,将集合从3个元素增加到4
个
。我在这里解决方案的可能缺点是,我们违反了
treeset
的Javadoc的建议,“与equals
> equals ”,从而违反了set的一般合同
。我不确定这个问题是否有问题 - 我没有足够的观点来形成判断。
Use
Comparator
withTreeSet
As commented by Johannes Kuhn, you can get your desired behavior by using a
NavigableSet
(orSortedSet
) or, in Java 21+,SequencedSet
(JEP 431). No need to invent your own class.Implementations of
NavigableSet
such asTreeSet
may offer a constructor taking aComparator
object. ThatComparator
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’ ownObject#equals
method.And since there is no hashing involved in a
TreeSet
, there is no concern about overridinghashCode
.We can easily define our
Comparator
implementation. For convenience, we can callComparator.comparing
to make a comparator implementation. We define the comparator by passing a method reference for the getter method of your desiredname
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
, andtoString
.Make some sample data.
Define our set, a
TreeSet
.Populate our set with 3 elements. Verify 3 elements by dumping to console.
Now we try to add another user with the same name but different values in the other fields.
By default, a
record
decides on equality by comparing each and every member field. So:name
for comparison, we would get 4 elements in this set.name
, then we should get 3 elements after having blocked admission of this interloper.Dump to console.
We see in those results (a) sorting of the elements by name, and (b) Blocking of the second
Alice
, with the originalAlice
remaining.To see the alternate behavior, replace the
setOfUsers
definition with this:Running that edition of the code results in
setOfUsers.size()
being: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 withequals
”, thereby violating the general contract ofSet
.I am not sure if that issue is problematic or not — I do not have enough perspective to form a judgement.
干净有效
您可以应用构图并创建一个可以维护
map
的类,并委派所有调用。封装收藏品并提供有限的访问权限的方法比扩展集合更加灵活,更乏味,而且也不会产生紧密的耦合。
类似的建议,以及通过扩展您的代码取决于现有收藏的弊端的示例,项目偏爱继承。
这就是这样的类的样子:
我们还可以使此类成为 generic 并能够包装任何对象。
为此,我们需要引入一个其他参数-A 函数,该函数将负责从对象中提取目标属性。
这就是在客户端代码中实例化的方式:
简洁而简单地
使用排序的集合的方法,例如
treeset
A/72482101/17949945“> basil的Bourque答案 可用于相对较小的对象数量。重要的是要强调,将数据存储在排序的集合中的成本。它们越大,操作速度较慢。
treeset
由 Tree 和大多数操作(例如基本add()
, remove> remove() andcontains()
操作要求 o(n)时间,除了最低/最高键时,除了边缘箱外。Clean and Efficient
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:
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.
And that's how it would be instantiated in the client code:
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 basicadd()
,remove()
andcontains()
operations require O(n) time, apart from the edge-cases when we deal with the lowest/highest key.Commons-Collections已经提供了赤道接口可以按照您的建议进行操作:
无论基于赤道创建集合的直接支持是有限的。 collectionUtils 。
但是,您可以利用将所需的对象包装到使用赤道的对象中,然后使用所有支持Commons-Collections为变压器提供的对象。 For example using
commons-collections already provides an Equator interface to do what you suggest:
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: