Ja, verwenden Sie HashMap
... aber auf eine spezielle Art und Weise: Die Falle, die ich sehe, wenn ich versuche, eine HashMap
als ein Pseudo- Set
ist die mögliche Verwechslung zwischen "tatsächlichen" Elementen der Map/Set
und "Kandidaten"-Elemente, d. h. Elemente, mit denen geprüft werden kann, ob ein equal
Element bereits vorhanden ist. Das ist bei weitem nicht idiotensicher, aber es hilft Ihnen, die Falle zu umgehen:
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);
}
}
Dann tun Sie dies:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Aber... Sie wollen jetzt die candidate
auf irgendeine Art und Weise selbst zerstören, es sei denn, der Programmierer stellt sie sofort in den Map/Set
... würden Sie wollen contains
zu "beflecken" die candidate
so dass jede Verwendung, sofern sie nicht mit dem Map
macht es zu einem "Anathema". Vielleicht könnten Sie SomeClass
eine neue Taintable
Schnittstelle.
Eine zufriedenstellendere Lösung ist eine GettableSet wie unten. Damit dies funktioniert, müssen Sie jedoch entweder für die Gestaltung von SomeClass
um alle Konstruktoren unsichtbar zu machen (oder... fähig und bereit, eine Wrapper-Klasse dafür zu entwerfen und zu verwenden):
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
}
Umsetzung:
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 ...
}
Ihr NoVisibleConstructor
Klassen sehen dann wie folgt aus:
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 ein technisches Problem mit einem solchen NoVisibleConstructor
Klasse: Es kann eingewendet werden, dass eine solche Klasse von Natur aus final
was unerwünscht sein kann. Eigentlich könnte man immer einen Dummy ohne Parameter hinzufügen protected
Konstrukteur:
protected SomeClass(){
throw new UnsupportedOperationException();
}
... was zumindest die Kompilierung einer Unterklasse ermöglichen würde. Sie müssen sich dann überlegen, ob Sie eine weitere getOrCreate()
Fabrikmethode in der Unterklasse.
Letzter Schritt ist eine abstrakte Basisklasse (NB "element" für eine Liste, "member" für eine Menge) wie diese für die Mitglieder Ihrer Menge (wenn möglich - auch hier ist die Verwendung einer Wrapper-Klasse wenn die Klasse nicht unter Ihrer Kontrolle steht oder bereits eine Basisklasse hat usw.), um die Implementierung möglichst gut zu verbergen:
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 );
}
... die Verwendung ist ziemlich offensichtlich (innerhalb Ihrer SomeClass
's static
Fabrikmethode):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );