Sí, use HashMap
... pero de una manera especializada: la trampa que preveo al tratar de usar a HashMap
como pseudo- Set
es la posible confusión entre elementos "reales" de los elementos Map/Set
"candidatos", es decir, elementos utilizados para probar si un equal
El elemento ya está presente. Esto está lejos de ser infalible, pero te aleja de la trampa:
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);
}
}
Entonces haz esto:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Pero ... ahora quieres candidate
que se autodestruya de alguna manera a menos que el programador lo ponga inmediatamente en Map/Set
... querrás contains
"contaminar" candidate
para que su uso a menos que se una al Map
anatema lo haga "anatema" ". Quizás podría hacer SomeClass
implementar una nueva Taintable
interfaz.
Una solución más satisfactoria es un GettableSet , como se muestra a continuación. Sin embargo, para que esto funcione, usted debe estar a cargo del diseño SomeClass
para hacer que todos los constructores no sean visibles (o ... capaces y dispuestos a diseñar y usar una clase de envoltura para ello):
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
}
Implementación:
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 ...
}
Sus NoVisibleConstructor
clases se ven así:
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 un problema técnico con tal NoVisibleConstructor
clase: se puede objetar que tal clase es inherentemente final
, lo que puede ser indeseable. En realidad, siempre puedes agregar un protected
constructor ficticio sin parámetros :
protected SomeClass(){
throw new UnsupportedOperationException();
}
... que al menos permitiría que se compilara una subclase. Debería pensar si necesita incluir otro getOrCreate()
método de fábrica en la subclase.
El paso final es una clase base abstracta (NB "elemento" para una lista, "miembro" para un conjunto) como esta para los miembros de su conjunto (cuando sea posible, de nuevo, alcance para usar una clase envolvente donde la clase no está bajo su control, o ya tiene una clase base, etc.), para una máxima ocultación de la implementación:
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 );
}
... uso es bastante obvio (dentro de su SomeClass
's static
método de fábrica):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
SortedSet
y sus implementaciones, que están basadas en mapas (por ejemplo,TreeSet
permite el accesofirst()
).