Java에서 HashMap과 Map 객체의 차이점은 무엇입니까?


349

내가 만든 다음지도의 차이점은 무엇입니까?

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();

HashMap을 사용하여 구현하고 Mary가 Map을 사용한다고 가정하십시오. 컴파일됩니까?
GilbertS

답변:


446

개체간에 차이가 없습니다. 당신은이 HashMap<String, Object>두 경우 모두를. 객체 와의 인터페이스에 차이가 있습니다 . 첫 번째 경우 인터페이스는 HashMap<String, Object>이고 두 번째 경우 인터페이스는 Map<String, Object>입니다. 그러나 기본 객체는 동일합니다.

사용의 이점은 사용중인 Map<String, Object>코드와의 계약을 위반하지 않고 기본 오브젝트를 다른 종류의 맵으로 변경할 수 있다는 것입니다. 로 선언 HashMap<String, Object>하면 기본 구현을 변경하려면 계약을 변경해야합니다.


예 :이 클래스를 작성한다고 가정 해 봅시다.

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...
}

이 클래스에는 서브 클래스와 (액세서 ​​메소드를 통해) 공유하는 string-> object의 내부 맵이 있습니다. HashMap클래스를 작성할 때 사용할 적절한 구조라고 생각하기 때문에 s로 작성한다고 가정 해 봅시다 .

나중에 Mary는 코드를 서브 클래 싱하는 코드를 작성합니다. 그녀는 그녀가 모두 할 필요가 뭔가가 thingsmoreThings, 자연스럽게 그녀가 일반적인 방법으로 그 박았을, 그녀가 나에 사용 된 것과 동일한 유형을 사용 getThings/ getMoreThings그녀의 방법을 정의 할 때 :

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

나중에, 내가 사용하는 경우 실제로는 더 나은 것을 결정 TreeMap대신 HashMap에서 Foo. 로 업데이트 Foo하고로 변경 HashMap했습니다 TreeMap. 이제 SpecialFoo계약을 어 겼기 때문에 더 이상 컴파일하지 않습니다. 계약 Foo을 제공하는 데 사용 HashMap되었지만 TreeMaps대신 제공 하고 있습니다. 그래서 우리는 SpecialFoo지금 고쳐야합니다 (그리고 이런 종류의 것은 코드베이스를 통해 파문을 일으킬 수 있습니다).

내 구현에서 HashMap(그리고 발생하는) 것을 사용하고 있다는 사실을 공유할만한 이유가 없다면 , 내가해야 할 일은 선언 getThings하고 그보다 더 구체적이지 않고 getMoreThings돌아 오는 Map<String, Object>것입니다. 사실, 심지어 내 다른 뭔가를 좋은 이유를 금지 Foo아마 선언해야 things하고 moreThings같은 Map, 아니 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...
}

이제 Map<String, Object>실제 개체를 만들 때만 구체적으로 할 수있는 모든 곳에서 사용 하고 있습니다.

내가 그렇게했다면 Mary는 다음을 수행했을 것입니다.

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

... 변경 Foo하면 SpecialFoo컴파일 이 중단 되지 않았습니다 .

인터페이스 (및 기본 클래스)를 통해 필요한만큼만 공개 할 수 있으며, 적절하게 변경할 수있는 유연성을 유지합니다. 일반적으로, 우리는 가능한 한 참조가 기본이되기를 원합니다. 우리가 그것이임을 알 필요가 없다면 HashMap그냥이라고 부르십시오 Map.

이것은 맹목적인 규칙은 아니지만 일반적으로 가장 일반적인 인터페이스 로 코딩하는 것이 더 구체적인 것으로 코딩하는 것보다 덜 취약합니다. 내가 그것을 기억했다면, FooMary가 실패로 설정 한 것을 만들지 않았을 것 입니다 SpecialFoo. Mary 가 그것을 기억 했다면 , 내가 엉망이더라도 Foo, 그녀는 Map대신에 그녀의 개인적인 방법을 선언했을 것이고 HashMap나의 Foo계약 변경 은 그녀의 코드에 영향을 미치지 않을 것입니다.

때로는 그렇게 할 수 없으며 때로는 구체적이어야합니다. 그러나 그럴만한 이유가 없다면, 가장 구체적인 인터페이스를 찾아보십시오.


56

MapHashMap이 구현 하는 인터페이스입니다 . 차이점은 두 번째 구현에서 HashMap에 대한 참조는 Map 인터페이스에 정의 된 함수 만 사용할 수 있고 첫 번째는 HashMap의 모든 공용 함수 (Map 인터페이스 포함)를 사용할 수 있다는 것입니다.

Sun의 인터페이스 자습서 를 읽으면 더 이해가 될 것입니다


첫 번째 = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld

이것은 List가 ArrayList로 구현되는 빈도와 비슷합니다.
Gerard

26

여기에 이미지 설명을 입력하십시오

Map은 다음과 같은 구현을가집니다.

  1. 해시 맵 Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. 트리 맵 Map m = new TreeMap();

  4. 약한 해시지도 Map m = new WeakHashMap();

하나의 메소드를 작성했다고 가정하십시오 (이것은 단지 의사 코드 임).

public void HashMap getMap(){
   return map;
}

프로젝트 요구 사항이 변경되었다고 가정하십시오.

  1. 이 메소드는 맵 컨텐츠를 리턴해야합니다 HashMap. 리턴해야합니다 .
  2. 메소드는 맵 키를 삽입 순서대로 리턴해야 HashMap합니다 LinkedHashMap. 리턴 유형 을 로 변경해야 합니다 .
  3. 메소드는 맵 키를 정렬 된 순서로 리턴해야 LinkedHashMap합니다 TreeMap. 리턴 유형 을 로 변경해야 합니다 .

메소드가 Map인터페이스 를 구현하는 대신 특정 클래스를 리턴하는 경우 getMap()매번 메소드 의 리턴 유형을 변경해야합니다 .

그러나 Java의 다형성 기능을 사용하고 특정 클래스를 리턴하는 대신 interface를 사용 Map하면 코드 재사용 성이 향상되고 요구 사항 변경의 영향이 줄어 듭니다.


17

나는 이것을 받아 들인 대답에 대한 의견으로 이것을하려고했지만 너무 펑키했습니다 (줄 바꿈이없는 것을 싫어합니다)

아, 차이점은 일반적으로 Map에는 이와 관련된 특정 방법이 있다는 것입니다. 그러나 HashMap과 같은 다른 방법이나 맵을 만드는 방법이 있으며 이러한 다른 방법은 모든 맵에없는 고유 한 방법을 제공합니다.

정확하고 항상 가능한 가장 일반적인 인터페이스를 사용하려고합니다. ArrayList와 LinkedList를 고려하십시오. 사용 방법이 크게 다르지만 "목록"을 사용하면 쉽게 전환 할 수 있습니다.

실제로 이니셜 라이저의 오른쪽을보다 동적 인 명령문으로 바꿀 수 있습니다. 이런 식으로 어떻습니까 :

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

이 방법으로 삽입 정렬을 사용하여 컬렉션을 채우려면 연결된 목록을 사용합니다 (배열 목록에 대한 삽입 정렬은 범죄입니다). 그러나 정렬 된 상태를 유지하고 추가 할 필요가없는 경우, ArrayList를 사용합니다 (다른 작업에 더 효율적).

컬렉션이 가장 좋은 예가 아니기 때문에 이것은 상당히 큰 확장입니다.하지만 OO 디자인에서 가장 중요한 개념 중 하나는 인터페이스 파사드를 사용하여 동일한 코드로 다른 객체에 액세스하는 것입니다.

댓글에 응답하여 수정 :

아래의 맵 주석과 관련하여, "Map"인터페이스를 사용하는 Yes는 콜렉션을 Map에서 HashMap으로 다시 캐스팅하지 않는 한 해당 메소드로만 제한합니다 (이는 목적을 완전히 상실 함).

종종 "만들기"또는 "초기화"방법으로 객체를 생성하고 특정 유형 (HashMap)을 사용하여 채우는 것이지만 그 방법은 필요하지 않은 "Map"을 반환합니다. 더 이상 HashMap으로 조작되었습니다.

방해가된다면 아마도 잘못된 인터페이스를 사용하고 있거나 코드가 제대로 구성되지 않았을 것입니다. 코드의 한 섹션이 "HashMap"으로 취급하고 다른 섹션은 "Map"으로 취급하도록하는 것이 허용되지만 "아래로"흐릅니다. 당신은 결코 캐스팅하지 않습니다.

또한 인터페이스가 나타내는 역할의 반 깔끔한 측면을 주목하십시오. LinkedList는 좋은 스택 또는 큐를 만들고, ArrayList는 좋은 스택을 만들지 만 끔찍한 큐 (다시 말하면 전체 목록이 바뀔 수 있음)이므로 LinkedList는 큐 인터페이스를 구현하지만 ArrayList는 그렇지 않습니다.


그러나이 예제에서는 일반 List 클래스에서만 메서드를 가져옵니다. LinkedList () 또는 ArrayList ()로 만들지 않습니까? 삽입 정렬을 사용하면 (LinkedList 및 ArrayList가 상속을받는 List의 메서드 여야한다고 생각합니다) LinkedList에서 더 빠르게 작동한다는 것입니다.
Tony Stark

내가 찾고있는 것은 Map <string, string> m = new HashMap <string, string> ()이라고 말할 때 내 Map m이 HashMap에 특정한 메서드를 사용할 수 있는지 여부입니다. 나는 그것을 할 수 없다고 생각하고 있습니까?
Tony Stark

아, 잠깐, 아니, 위의 내 맵 m에는 HashMap의 메소드가 있어야합니다.
Tony Stark

기본적으로 '인터페이스 의미'에서 Map을 사용하는 유일한 특권은 맵이 필요한 메소드가있는 경우 모든 유형의 맵 이이 메소드에서 작동한다는 것을 보장한다는 것입니다. 그러나 해시 맵을 사용하면 메서드가 해시 맵에서만 작동한다고 말하고 있습니다. 또는 달리 말하면 내 메소드는 Map 클래스에 정의되었지만 Map을 확장하는 다른 클래스에 상속 된 메소드 만 사용합니다.
Tony Stark

내가 런타임 때까지 원하는 목록 I의 유형을 결정 할 필요가 없습니다 목록의 수단을 사용하여 위에서 언급 한 특권에 추가하여 인터페이스 것은 내가 컴파일하고 실행하기 전에 하나를 선택해야 할 것이다 존재하지 않은 경우 반면
Tony Stark

12

TJ Crowder와 Adamski가 언급했듯이 하나는 인터페이스에 대한 것이고 다른 하나는 인터페이스의 특정 구현에 대한 것입니다. Joshua Block에 따르면 기본 구현에 대한 변경 사항을보다 잘 처리 할 수 ​​있도록 항상 인터페이스에 코드를 작성해야합니다. 즉, HashMap이 갑자기 솔루션에 적합하지 않고 맵 구현을 변경해야하는 경우에도 여전히 맵을 사용할 수 있습니다 인스턴스화 유형을 변경하십시오.


8

두 번째 예에서 "map"참조는 유형 Map이며 이는 HashMap(및 다른 유형의 Map)에 의해 구현 된 인터페이스 입니다. 이 인터페이스는 것입니다 계약 오브젝트가 다양한 작업을 키를 값에 매핑하고 지원한다는 (예를 들어 put, get). (이 경우 a ) 구현대해서는 아무 것도 말하지 않습니다 .MapHashMap

두 번째 접근법은 일반적으로 MapAPI 정의를 사용 하거나 API 정의를 통해 특정 맵 구현을 메소드에 노출시키지 않으려는 경우에 일반적으로 선호됩니다 .


8

Map은 정적 유형 의지도이고 HashMap은 동적 유형 의지도입니다. 즉, 컴파일러는지도 객체를 런타임에지도의 하위 유형을 가리킬 수 있지만지도 유형 중 하나로 간주합니다.

구현 대신 인터페이스에 대한 프로그래밍에 대한 이러한 실습은 유연성을 유지하는 이점이 있습니다. 예를 들어 맵의 하위 유형 (예 : LinkedHashMap) 인 경우 런타임시 동적 유형의 맵을 교체하고 맵의 동작을 변경할 수 있습니다 파리.

경험적으로 가장 좋은 방법은 API 레벨에서 가능한 한 추상적으로 유지하는 것입니다. 예를 들어 프로그래밍하는 메소드가 맵에서 작동해야하는 경우 더 엄격한 (추상적이지 않기 때문에) HashMap 유형 대신 매개 변수를 Map으로 선언하면 충분합니다. . 그렇게하면 API 소비자는 어떤 종류의 맵 구현을 메소드에 전달할 것인지 유연하게 결정할 수 있습니다.


4

최상위 투표 답변과 "보다 일반적이고 더 나은"스트레스를 강조하는 위의 많은 답변에 덧붙여 조금 더 파고 싶습니다.

Map구조 계약 HashMap은 인덱스를 계산하는 방법, 용량 및 증가 방법, 삽입 방법, 인덱스를 고유하게 유지하는 방법 등 다양한 실제 문제를 처리하기위한 자체 메소드를 제공하는 구현입니다.

소스 코드를 살펴 보자.

에서 Map우리의 방법을 가지고 containsKey(Object key):

boolean containsKey(Object key);

JavaDoc :

부울 java.util.Map.containsValue (객체 값)

이 맵이 하나 이상의 키를 지정된 값에 매핑하면 true를 반환합니다. 보다 공식적으로는,이 맵에 다음 v과 같은 값에 대한 맵핑이 하나 이상 포함 된 경우에만 true를 리턴합니다 (value==null ? v==null : value.equals(v)). 이 작업은 대부분의 Map 인터페이스 구현에 대해 맵 크기에서 시간이 선형 일 것입니다.

매개 변수 : value

이지도의 존재 여부를 테스트 할 값

리턴 : true

이 맵이 하나 이상의 키를 지정된 키에 매핑하는 경우

valueThrows :

ClassCastException-값이이 맵에 부적절한 형태 인 경우 (옵션)

NullPointerException-지정된 값이 null로,이 맵이 null 값을 허가하지 않는 경우 (옵션)

그것을 구현하기 위해서는 구현이 필요하지만, "방법"은 자유로울뿐 아니라 그것이 올바르게 리턴되도록 보장해야합니다.

에서 HashMap:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

HashMap이 맵에 키가 포함되어 있는지 테스트하기 위해 해시 코드를 사용 하는 것으로 나타났습니다 . 따라서 해시 알고리즘의 이점이 있습니다.


3

동일한 맵을 만듭니다.

그러나 사용할 때 차이를 채울 수 있습니다. 첫 번째 경우에는 특별한 HashMap 메소드를 사용할 수 있지만 (실제로 유용한 사람은 기억 나지 않습니다) HashMap 매개 변수로 전달할 수 있습니다.

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 

3

Map은 인터페이스이고 Hashmap은 Map Interface를 구현하는 클래스입니다.


1

Map은 인터페이스이고 Hashmap은이를 구현하는 클래스입니다.

따라서이 구현에서는 동일한 객체를 생성합니다


0

HashMap은 Map의 구현이므로 상당히 동일하지만 참조 안내서에서 볼 수 있듯이 "clone ()"메소드가 있습니다))


0
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

우선은 Map이 같은 다른 구현이있는 인터페이스입니다 - HashMap, TreeHashMap, LinkedHashMap등 인터페이스 구현 클래스의 슈퍼 클래스처럼 작동합니다. 구현이 있음을 OOP의 규칙에 어떤 구체적인 클래스에 따라 그래서 MapA는 Map또한. 즉 HashMap, Map유형을 캐스팅하지 않고 유형 변수에 유형 변수를 할당 / 입력 할 수 있습니다 .

이 경우 우리는 할당 할 수 있습니다 map1map2있는 주조 또는 데이터의 손실없이 -

map2 = map1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.