Nie ma różnicy między przedmiotami; masz HashMap<String, Object>
w obu przypadkach. Istnieje różnica w interfejsie, jaki masz do obiektu. W pierwszym przypadku interfejs jest HashMap<String, Object>
, podczas gdy w drugim jest Map<String, Object>
. Ale podstawowy obiekt jest taki sam.
Zaletą użycia Map<String, Object>
jest to, że możesz zmienić obiekt bazowy na inny rodzaj mapy bez zerwania umowy z dowolnym kodem, który go używa. Jeśli zadeklarujesz to jako HashMap<String, Object>
, musisz zmienić umowę, jeśli chcesz zmienić implementację.
Przykład: Załóżmy, że piszę tę klasę:
class Foo {
private HashMap<String, Object> things;
private HashMap<String, Object> moreThings;
protected HashMap<String, Object> getThings() {
return this.things;
}
protected HashMap<String, Object> getMoreThings() {
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
Klasa ma kilka wewnętrznych map ciągu typu string>, które dzieli (za pomocą metod akcesorów) z podklasami. Powiedzmy, że piszę od HashMap
początku, ponieważ uważam, że jest to odpowiednia struktura do użycia podczas pisania klasy.
Później Mary pisze kod podklasujący go. Ma coś, co musi zrobić z obiema things
rzeczami moreThings
, więc naturalnie umieszcza to we wspólnej metodzie i używa tego samego typu, którego użyłam na getThings
/ getMoreThings
podczas definiowania swojej metody:
class SpecialFoo extends Foo {
private void doSomething(HashMap<String, Object> t) {
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
// ...more...
}
Później zdecydować, że właściwie, to lepiej, jeśli mogę użyć TreeMap
zamiast HashMap
w Foo
. Aktualizuję Foo
, zmieniam HashMap
na TreeMap
. Teraz SpecialFoo
już się nie kompiluje, bo złamałem kontrakt: Foo
zwykłem mawiać, że to HashMap
s, ale teraz to zapewnia TreeMaps
. Więc musimy to SpecialFoo
teraz naprawić (i tego rodzaju rzeczy mogą przeniknąć przez bazę kodu).
O ile nie miałem naprawdę dobrego powodu do dzielenia się tym, że moja implementacja używała HashMap
(i tak się dzieje), to co powinienem był zrobić, to zadeklarować getThings
i getMoreThings
po prostu powrócić, Map<String, Object>
nie będąc bardziej szczegółowym. W rzeczywistości, z wyjątkiem dobrego powodu, aby zrobić coś innego, nawet w środku Foo
prawdopodobnie powinienem zadeklarować things
i moreThings
jako Map
, nie HashMap
/ TreeMap
:
class Foo {
private Map<String, Object> things; // <== Changed
private Map<String, Object> moreThings; // <== Changed
protected Map<String, Object> getThings() { // <== Changed
return this.things;
}
protected Map<String, Object> getMoreThings() { // <== Changed
return this.moreThings;
}
public Foo() {
this.things = new HashMap<String, Object>();
this.moreThings = new HashMap<String, Object>();
}
// ...more...
}
Zwróć uwagę, że teraz używam Map<String, Object>
wszędzie, gdzie mogę, będąc konkretnym tylko podczas tworzenia rzeczywistych obiektów.
Gdybym to zrobił, Mary zrobiłaby to:
class SpecialFoo extends Foo {
private void doSomething(Map<String, Object> t) { // <== Changed
// ...
}
public void whatever() {
this.doSomething(this.getThings());
this.doSomething(this.getMoreThings());
}
}
... a zmiana Foo
nie SpecialFoo
zatrzymałaby kompilacji.
Interfejsy (i klasy podstawowe) pozwalają nam ujawniać tylko tyle, ile jest konieczne , zachowując naszą elastyczność pod przykryciem, aby wprowadzać odpowiednie zmiany. Ogólnie rzecz biorąc, chcemy, aby nasze referencje były jak najbardziej podstawowe. Jeśli nie musimy wiedzieć, że to jest HashMap
, po prostu nazwij to Map
.
Nie jest to reguła ślepa, ale ogólnie kodowanie do najbardziej ogólnego interfejsu będzie mniej kruche niż kodowanie do czegoś bardziej konkretnego. Gdybym to pamiętał, nie stworzyłbym Foo
takiego zestawu, w którym Mary byłaby porażka SpecialFoo
. Gdyby Mary to zapamiętała, to mimo że popełniłem błąd Foo
, zadeklarowałaby swoją prywatną metodę Map
zamiast, HashMap
a moja zmiana Foo
umowy nie wpłynęłaby na jej kod.
Czasami nie możesz tego zrobić, czasem musisz być konkretny. Ale jeśli nie masz ku temu powodu, popełniaj błąd w kierunku najmniej szczegółowego interfejsu.