Tak, używaj HashMap
... ale w wyspecjalizowany sposób: pułapka, którą przewiduję, próbując użyć HashMap
jako pseudonimu, Set
jest możliwym pomyleniem między „rzeczywistymi” elementami Map/Set
i „kandydującymi” elementami, tj. Elementami używanymi do testowania, czy equal
element jest już obecny. Jest to dalekie od niezawodności, ale odsuwa cię od pułapki:
class SelfMappingHashMap<V> extends HashMap<V, V>{
@Override
public String toString(){
// otherwise you get lots of "... object1=object1, object2=object2..." stuff
return keySet().toString();
}
@Override
public V get( Object key ){
throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
}
@Override
public V put( V key, V value ){
// thorny issue here: if you were indavertently to `put`
// a "candidate instance" with the element already in the `Map/Set`:
// these will obviously be considered equivalent
assert key.equals( value );
return super.put( key, value );
}
public V tryToGetRealFromCandidate( V key ){
return super.get(key);
}
}
Następnie zrób to:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Ale ... chcesz teraz candidate
autodestrukcji w jakiś sposób, chyba że programista faktycznie natychmiast umieści go w Map/Set
... chciałbyś contains
"skazić" candidate
tak, aby każde użycie go, chyba że dołączy do Map
anatemy „. Być może mógłbyś SomeClass
zaimplementować nowy Taintable
interfejs.
Bardziej satysfakcjonującym rozwiązaniem jest GettableSet , jak poniżej. Jednak aby to zadziałało, musisz być odpowiedzialny za projektowanie SomeClass
, aby wszystkie konstruktory były niewidoczne (lub ... zdolne i chętne do zaprojektowania i użycia do tego klasy opakowania):
public interface NoVisibleConstructor {
// again, this is a "nudge" technique, in the sense that there is no known method of
// making an interface enforce "no visible constructor" in its implementing classes
// - of course when Java finally implements full multiple inheritance some reflection
// technique might be used...
NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};
public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
V getGenuineFromImpostor( V impostor ); // see below for naming
}
Realizacja:
public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
private Map<V, V> map = new HashMap<V, V>();
@Override
public V getGenuineFromImpostor(V impostor ) {
return map.get( impostor );
}
@Override
public int size() {
return map.size();
}
@Override
public boolean contains(Object o) {
return map.containsKey( o );
}
@Override
public boolean add(V e) {
assert e != null;
V result = map.put( e, e );
return result != null;
}
@Override
public boolean remove(Object o) {
V result = map.remove( o );
return result != null;
}
@Override
public boolean addAll(Collection<? extends V> c) {
// for example:
throw new UnsupportedOperationException();
}
@Override
public void clear() {
map.clear();
}
// implement the other methods from Set ...
}
Twoje NoVisibleConstructor
zajęcia wyglądają następująco:
class SomeClass implements NoVisibleConstructor {
private SomeClass( Object param1, Object param2 ){
// ...
}
static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
SomeClass candidate = new SomeClass( param1, param2 );
if (gettableSet.contains(candidate)) {
// obviously this then means that the candidate "fails" (or is revealed
// to be an "impostor" if you will). Return the existing element:
return gettableSet.getGenuineFromImpostor(candidate);
}
gettableSet.add( candidate );
return candidate;
}
@Override
public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
// more elegant implementation-hiding: see below
}
}
PS jeden problem techniczny z taką NoVisibleConstructor
klasą: można się sprzeciwić, że taka klasa jest z natury final
, co może być niepożądane. W rzeczywistości zawsze można dodać fałszywy protected
konstruktor bez parametrów :
protected SomeClass(){
throw new UnsupportedOperationException();
}
... co pozwoliłoby przynajmniej na kompilację podklasy. Będziesz musiał pomyśleć o tym, czy musisz dołączyć inną getOrCreate()
podrzędną metodę fabryczną.
Ostatnim krokiem jest abstrakcyjna klasa podstawowa (NB „element” dla listy, „element członkowski” dla zestawu), taki jak ten dla członków zestawu (jeśli to możliwe - ponownie, zakres zastosowania klasy opakowania, w której klasa nie jest pod twoją kontrolą, lub już ma klasę podstawową itp.), aby maksymalnie ukryć implementację:
public abstract class AbstractSetMember implements NoVisibleConstructor {
@Override
public NoVisibleConstructor
addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
AbstractSetMember member = this;
@SuppressWarnings("unchecked") // unavoidable!
GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
if (gettableSet.contains( member )) {
member = set.getGenuineFromImpostor( member );
cleanUpAfterFindingGenuine( set );
} else {
addNewToSet( set );
}
return member;
}
abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}
... użycie jest dość oczywiste (wewnątrz SomeClass
„s static
metody fabryki):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
SortedSet
i jego implementacji, które są oparte na mapie (np.TreeSet
Umożliwia dostępfirst()
).