嗨,當我想在不更改 hashCode() 方法的情況下使用自定義 HashSet 時,有人可以為我指明正確的方向。用法是擁有一組必須具有不同的一個屬性(或更多)的物件。
例如對于這個類:
@FieldDefaults(level = AccessLevel.PRIVATE)
@Getter @Setter
public class User{
String name;
String email;
String age;
}
我想擁有 UserNameSet ,它只允許包含具有不同名稱的用戶。我不想覆寫 User 中的 hashCode 和 equals 方法,因為我仍然想區分名稱相同但電子郵件不同的用戶。
我想以某種方式只為這個 HashMap 覆寫 hashCode() 方法。
已編輯
乍一看,我想出了這個解決方案,它可以作業,有人可以檢查一下嗎?
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();
}
}
}
uj5u.com熱心網友回復:
我想要 UserNameSet 允許僅包含具有不同名稱的用戶
您可以應用組合并創建一個類來維護 aMap并將所有呼叫委托給它。
這種方法比擴展集合更靈活,更不乏味,而且不會產生緊密耦合。類似的建議,以及通過擴展使您的代碼依賴于現有集合的缺點示例,您可以在Joshua Bloch的“Effective Java”一書中找到,專案偏好組合優于繼承。
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
}
我們還可以使這個類成為通用的并且能夠包裝任何物件。
為此,我們需要引入一個額外的引數 - 一個負責從物件中提取目標屬性的函式。
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);
uj5u.com熱心網友回復:
Comparator與_TreeSet
正如評論的那樣,您可以通過使用NavigableSet(或SortedSet)來獲得所需的行為。無需發明自己的課程。
NavigableSet這樣的實作TreeSet可以提供一個建構式來獲取一個Comparator物件。這Comparator用于對集合的元素進行排序。
就我們在??這個問題中的觀點而言,這Comparator也用于決定承認新的不同元素,而不是使用元素自己的Object#equals方法。
并且由于 a 中不涉及散列TreeSet,因此無需擔心覆寫hashCode。
我們可以Comparator通過使用所需name欄位的 getter 方法的方法參考輕松定義我們的實作:User :: name.
為簡潔起見,讓我們將您的User類定義為記錄。我們只需宣告成員欄位的型別和名稱。編譯器隱式創建建構式、getter、equals&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 )
);
定義我們的集合 a TreeSet。
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 ) );
默認情況下,arecord通過比較每個成員欄位來決定是否相等。所以:
- 如果我們未能實作僅
name用于比較的目標,我們將在該集合中獲得 4 個元素。 - 如果我們成功地使用了 only
name,那么在阻止了這個闖入者的進入之后我們應該得到 3 個元素。
轉儲到控制臺。
System.out.println( setOfUsers.size() " elements in setOfUsers = " setOfUsers );
setOfUsers 中的 3 個元素 = [User[name=Alice, [email protected], age=42], User[name=Bob, [email protected], age=7], User[name=Carol,電子郵件[email protected],年齡=77]]
setOfUsers 中的 3 個元素 = [User[name=Alice, [email protected], age=42], User[name=Bob, [email protected], age=7], User[name=Carol,電子郵件[email protected],年齡=77]]
我們在這些結果中看到(a)按名稱對元素進行排序,以及(b)阻塞第二個元素,保留Alice原始元素。Alice
要查看替代行為,請將setOfUsers定義替換為:
Set < User > setOfUsers = new HashSet <>();
運行該版本的代碼會導致setOfUsers.size():
setOfUsers 中的 3 個元素 = [User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77], User[name=Alice,電子郵件[email protected],年齡=42]]
setOfUsers 中的 4 個元素 = [User[name=Bob, [email protected], age=7], User[name=Carol, [email protected], age=77], User[name=Alice,電子郵件[email protected],年齡=42],用戶[姓名=愛麗絲,電子郵件[email protected],年齡=-666]]
我們在這些結果中看到 (a) 沒有特定的排序,并且 (b) 添加了第二個“Alice”,將集合從 3 個元素增加到 4 個。
uj5u.com熱心網友回復:
commons-collections 已經提供了一個Equator介面來執行您的建議:
public interface Equator<T> {
boolean equate(T o1, T o2);
int hash(T o);
}
但是,對基于赤道創建集合的直接支持是有限的。CollectionUtils中有一些涉及赤道的操作。
但是,您可以利用Transformer將所需的物件包裝到使用赤道的物件中,然后使用為轉換器提供的所有支持 commons-collections。例如使用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));
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/485457.html
