2010-12-08 10 views
4

Ich benutze Java 6.Wann sollte ich eine Klasse für einen Map-Schlüssel erstellen?

Angenommen, ich habe eine Klasse, die ich möchte ihre Instanzen in einer Karte speichern. Später möchte ich Instanzen nur mit den "Schlüsselfeldern" abrufen. Ich werde Feldmodifikatoren, Getter und Setter für Prägnanz ignorieren.

class A { 

    String field1; 
    String field2; 
    String field3; 
    String field4; 
    //more fields 

    public int hashCode(){ 
     //uses only field1 and field2 
    } 

    public boolean equals(Object o){ 
     //uses only field1 and field2 
    } 
} 

Da Java Standard API nicht die MultikeyMap haben, und ich will nicht 3rd-Party-Bibliotheken verwenden, habe ich eine Auswahl von
1) KeyA Erstellen einer neuen Klasse der Schlüssel einer Karte darstellen
2) Verwenden Sie A selbst als Schlüssel und füllen Sie nur die "Schlüsselfelder", wenn ich Objekte aus einer Karte abrufen müssen
3) Verschachteln Sie die Karten, z HashMap<String, HashMap<String, A>>
4) andere Problemumgehungen

Was benutzen die Leute normalerweise und wann?

+2

Sie müssen die Klasse oder Instanzen dieser Klasse speichern? Wenn Sie Objekte getrennt speichern möchten, können Sie HashSet verwenden. – khachik

+0

Instanzen dieser Klasse (Bearbeitung der Frage). Wie kann ich eine Instanz von einem HashSet abrufen, wenn ich nur field1 und field2 habe? – Russell

Antwort

2

Aufgrund Ihrer letzten Änderung sollten Sie in dieser Situation Instanzen der Klasse A als Schlüssel verwenden. Lookups werden basierend auf der Semantik von equals() und hashCode() durchgeführt, so dass dies dazu führt, dass Instanzen nur von den "Schlüsselfeldern" abgerufen werden. Daher würde der folgende Code arbeiten, wie Sie beabsichtigen:

final Map<A, String> map = new HashMap<A, Object>(); 
final A first = new A("fe", "fi", "fo", "fum"); 

map.put(first, "success"); 

// later on 
final A second = new A ("fe", "fi", "foo", "bar"); 
System.out.println(map.get(second)); // prints "success"; 

Having said that, Ihre Beschreibung der Option 2 macht mich ein wenig besorgt, dass dies nicht die vernünftigste Option sein könnte. Wenn Sie eine Map<A, String> erstellen, ist dies eine Zuordnung von Instanzen der Klasse A zu Strings. Ihr zweiter Punkt impliziert jedoch, dass Sie es als eine Zuordnung von Paaren von Schlüsselfeldern zu Strings vorstellen möchten. Wenn Sie in der Regel Werte nach ein paar "rohen" Strings suchen, würde ich davon abraten. Es fühlt sich falsch an (für mich), eine "falsche" Instanz von A zu erstellen, nur um eine Suche durchzuführen - in diesem Fall sollten Sie wahrscheinlich eine Schlüsselklasse erstellen, die das Paar von Zeichenfolgen enthält, wie in Option 1 beschrieben Sie können sogar Instanzen dieser Objekte in Ihre A Objekte einbetten, um die Schlüsselfelder zu halten.

Es gibt ein ähnliches Argument für oder gegen Option 3. Wenn die Strings wirklich konzeptuell hierarchisch sind, könnte das durchaus Sinn machen. Zum Beispiel, wenn field1 war Land, und field2 war Town, könnte man definitiv argumentieren, dass die verschachtelte Karten sinnvoll sind - Sie haben eine Zuordnung von Land, um die Karte von Stadt-> A-Beziehungen innerhalb des Landes. Aber wenn Ihre Schlüssel nicht auf diese Weise natürlich zusammengesetzt werden (sagen wir, wenn sie (x, y) -Koordinaten wären), wäre dies wiederum kein sehr natürlicher Weg, um die Daten darzustellen, und eine einstufige Karte von XYPoint zu bewerten wäre vernünftiger. (Ebenso, wenn Sie nie verwenden Sie die zweistufige Karte mit Ausnahme von immer durch beide Ebenen gehen, könnte man argumentieren, die einstufige Karte macht noch mehr Sinn.)

Und schließlich, wie für Option 4 -if Du bist immer Mapping zu A selbst, und speichert den Schlüssel als seinen eigenen Wert (zB wenn Sie Ihre A Instanzen kanonisieren möchten, ein bisschen wie String.intern()) dann wie Sie darauf hingewiesen wurde, müssen Sie kein Map überhaupt verwenden, und ein Set wird hier die Arbeit machen. Die Map ist nützlich, wenn Sie Beziehungen zwischen verschiedenen Objekte herstellen möchten, während eine Set automatisch die Einzigartigkeit von Objekten ohne zusätzlichen konzeptionellen Aufwand bietet.


Wenn Sie tun, um die Klasse selbst als Schlüssel verwenden, gewarnt werden, dass auch nur im allgemeinen verwendet werden als Schlüssel-Objekte sollten, wenn ihre hashCode (und das Verhalten von equals) nicht im Laufe der Zeit ändern. Typischerweise bedeutet dies, dass die Schlüssel unveränderbar sind, obwohl Sie es sich hier leisten könnten, veränderliche "Nicht-Schlüssel" -Felder zu haben. Wenn Sie diese Regel zu brechen, würde man seltsame Verhalten wie die folgenden:

// Populate a map, with an A as the key 
final Map<A, String> map = new HashMap<A, Object>(); 
final A a = new A("one", "two", "three", "four"); 
map.put(a, "here"); 

// Mutate a 
a.setField1("un"); 

// Now look up what we associated with it 
System.out.println(map.get(a)); // prints "null" - huh? 
System.out.println(map.containsKey(a)); // prints "false" 
0

ich eine Index Klasse schaffen würde, so etwas wie diese (Warnung: ungetesteter Code), zu abstrahieren der Indizierung Funktionalität . Warum Java so etwas nicht schon hat, ist mir rätselhaft.

interface Indexer<T, K> 
{ 
    /** extract key from index */ 
    public K getIndexKey(T object); 
} 

class Index<T,K> 
{ 
    final private HashMap<K,List<T>> indexMap = new HashMap<K,List<T>>(); 
    final private Indexer<T,K> indexer; 

    public Index(Indexer<T,K> indexer) 
    { 
     this.indexer = indexer; 
    } 

    public void add(T object) { 
     K key = this.indexer.getIndexKey(object); 
     List<T> values = this.indexMap.get(key); 
     if (values == null) 
     { 
      values = new ArrayList<T>(); 
      this.indexMap.put(key, values); 
     } 
     values.add(object); 
    } 
    public void remove(T object) { 
     K key = this.indexer.getIndexKey(object); 
     List<T> values = this.indexMap.get(key); 
     if (values != null) 
     { 
      values.remove(object); 
     } 
    } 
    public List<T> lookup(K key) { 
     List<T> values = this.indexMap.get(key); 
     return values == null 
      ? Collections.emptyList() 
      : Collections.unmodifiableList(values); 
    } 
} 

Beispiel zu Ihrer class A:

Index<A,String> index1 = new Index<A,String>(new Indexer<A,String>() { 
    @Override public String getIndexKey(A object) 
    { 
     return object.field1; 
    } 
});  
Index<A,String> index2 = new Index<A,String>(new Indexer<A,String>() { 
    @Override public String getIndexKey(A object) 
    { 
     return object.field2; 
    } 
});  
/* repeat for all desired fields */ 

Sie müssten manuell hinzufügen und entfernen Sie Einträge aus den Indizes, aber alle grungework unterhalb dieser Operationen durch den Index-Klasse behandelt wird.

0

Ihre Klasse hat "Schlüsselfelder". Ich würde vorschlagen, eine Elternklasse, ParentA, mit diesen Schlüsselfeldern zu erstellen (die sicherlich zu einem Konzept in Ihrer Domäne zuordnen) und erben diese Klasse in Ihrer Kindklasse A.

Überschreiben hashCode() und equals() in der ParentA Klasse.

Verwenden Sie eine Map<ParentA, A>, um Ihre A Instanzen zu speichern und geben Sie die Instanz als Schlüssel und Wert an.

Um eine bestimmte A Instanz, erstellen Sie ein neues ParentA Beispiel pA, mit Ihren Schlüsselfeldern gesetzt, abrufen und

A a = map.get(pA); 

tun, was es ist. Eine andere Möglichkeit besteht darin, eine AIdentifier Klasse mit Schlüsselfeldern zu erstellen und eine Instanz als A Eigenschaft id hinzuzufügen. Also fügen Sie Ihre Instanz mit map.put(a.id, a); Dies ist Vererbung vs Komposition Muster Diskussion :)

Verwandte Themen