Ja, verwenden Sie HashMap
... aber auf spezielle Weise: Die Falle, die ich beim Versuch sehe, a HashMap
als Pseudo- zu verwenden, Set
ist die mögliche Verwechslung zwischen "tatsächlichen" Elementen der Map/Set
und "Kandidaten" -Elementen, dh Elementen, die zum Testen verwendet werden, ob ein equal
Element ist bereits vorhanden. Dies ist alles andere als narrensicher, stößt Sie jedoch von der Falle weg:
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 mach das:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Aber ... Sie möchten jetzt candidate
, dass sich das Programm auf irgendeine Weise selbst zerstört, es sei denn, der Programmierer legt es tatsächlich sofort in das Map/Set
... Sie möchten contains
das "beschmutzen", candidate
damit jede Verwendung, es sei denn, es verbindet sich mit dem Map
"Anathema" ". Vielleicht könnten Sie SomeClass
eine neue Taintable
Schnittstelle implementieren lassen.
Eine zufriedenstellendere Lösung ist ein GettableSet ( siehe unten). Damit dies funktioniert, müssen Sie entweder für das Design von verantwortlich SomeClass
sein, damit alle Konstruktoren nicht sichtbar sind (oder ... in der Lage und bereit sind, 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
}
Implementierung:
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 ...
}
Ihre NoVisibleConstructor
Klassen sehen dann so 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 einer solchen NoVisibleConstructor
Klasse: Es kann beanstandet werden, dass eine solche Klasse inhärent ist final
, was unerwünscht sein kann. Eigentlich könnte man immer einen Dummy- protected
Konstruktor ohne Parameter hinzufügen :
protected SomeClass(){
throw new UnsupportedOperationException();
}
... was zumindest eine Unterklasse kompilieren lassen würde. Sie müssten dann darüber nachdenken, ob Sie eine andere getOrCreate()
Factory-Methode in die Unterklasse aufnehmen müssen.
Der letzte Schritt ist eine abstrakte Basisklasse (NB "Element" für eine Liste, "Mitglied" für eine Menge) wie diese für Ihre Gruppenmitglieder (wenn möglich - wieder Spielraum für die Verwendung einer Wrapper-Klasse, bei der die Klasse nicht unter Ihrer Kontrolle steht). oder hat bereits eine Basisklasse usw.) für maximales Ausblenden der Implementierung:
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 );
}
... Nutzung ist ziemlich offensichtlich (in Ihrer SomeClass
‚s static
Factory - Methode):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
SortedSet
und dessen Implementierungen, die kartenbasiert sind (z. B.TreeSet
Zugriff ermöglichenfirst()
).