Oui, utilisez HashMap
... mais d'une manière spécialisée: le piège que je prévois en essayant d'utiliser un HashMap
comme pseudo- Set
est la confusion possible entre les éléments "réels" des éléments Map/Set
et les "candidats", c'est-à-dire les éléments utilisés pour tester si un equal
l'élément est déjà présent. C'est loin d'être infaillible, mais vous éloigne du piège:
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);
}
}
Ensuite, faites ceci:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Mais ... vous voulez maintenant que l' candidate
autodestruction soit en quelque sorte à moins que le programmeur ne le place immédiatement dans le Map/Set
... vous voudriez contains
"corrompre" le candidate
afin que toute utilisation à moins qu'il ne le rejoigne le Map
rende "anathème" ". Vous pourriez peut-être faire SomeClass
implémenter une nouvelle Taintable
interface.
Une solution plus satisfaisante est un GettableSet , comme ci-dessous. Cependant, pour que cela fonctionne, vous devez être en charge de la conception SomeClass
afin de rendre tous les constructeurs non visibles (ou ... capables et désireux de concevoir et d'utiliser une classe wrapper pour cela):
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
}
La mise en oeuvre:
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 ...
}
Vos NoVisibleConstructor
cours ressemblent alors à ceci:
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 problème technique avec une telle NoVisibleConstructor
classe: on peut objecter qu'une telle classe est intrinsèquement final
, ce qui peut être indésirable. En fait, vous pouvez toujours ajouter un protected
constructeur sans paramètre factice :
protected SomeClass(){
throw new UnsupportedOperationException();
}
... ce qui laisserait au moins une sous-classe se compiler. Vous devriez alors vous demander si vous devez inclure une autre getOrCreate()
méthode d'usine dans la sous-classe.
L'étape finale est une classe de base abstraite (NB "élément" pour une liste, "membre" pour un ensemble) comme celle-ci pour les membres de votre ensemble (si possible - encore une fois, possibilité d'utiliser une classe wrapper où la classe n'est pas sous votre contrôle, ou a déjà une classe de base, etc.), pour un masquage maximal de l'implémentation:
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 );
}
... l' utilisation est assez évident (dans votre SomeClass
de static
méthode d'usine):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
SortedSet
et ses implémentations, qui sont basées sur une carte (par exemple,TreeSet
permet d'accéderfirst()
).