ImmutableMap 또는 Map을 반환하는 것이 더 낫습니까?


90

Map을 반환해야하는 메서드를 작성하고 있다고 가정 해 보겠습니다 . 예를 들면 :

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

잠시 생각한 끝에이 맵이 생성되면 수정할 이유가 없다고 판단했습니다. 따라서 ImmutableMap 을 반환하고 싶습니다 .

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

반환 유형을 일반 맵으로 두어야합니까, 아니면 ImmutableMap을 반환하도록 지정해야합니까?

한쪽에서, 이것이 바로 인터페이스가 만들어진 이유입니다. 구현 세부 정보를 숨 깁니다.
반면에 이렇게두면 다른 개발자들은이 객체가 불변이라는 사실을 놓칠 수 있습니다. 따라서 불변 객체라는 주요 목표를 달성하지 못할 것입니다. 변경할 수있는 개체의 수를 최소화하여 코드를 더 명확하게 만듭니다. 최악의 경우라도 잠시 후 누군가이 객체를 변경하려고 시도 할 수 있으며 이로 인해 런타임 오류가 발생합니다 (컴파일러가 이에 대해 경고하지 않음).


2
나는 누군가가 결국이 질문을 논쟁하기에 너무 개방적이라고 태그 할 것이라고 생각합니다. "WDYT"대신 더 구체적인 질문을 입력 할 수 있습니까? 그리고 "괜찮아 ...?" 예를 들어, "일반지도를 반환하는 데 문제 나 고려 사항이 있습니까?" 그리고 "어떤 대안이 존재합니까?"
ash

나중에 실제로 변경 가능한 맵을 반환해야한다고 결정하면 어떨까요?
user253751

3
@immibis lol 또는 그 문제에 대한 목록?
djechlin 2016-06-29

3
API에 Guava 종속성을 적용 하시겠습니까? 이전 버전과의 호환성을 깨지 않고는 변경할 수 없습니다.
user2357112 모니카 지원

1
나는 질문이 너무 개략적이고 많은 중요한 세부 사항이 누락되었다고 생각합니다. 예를 들어, 구아바를 이미 (보이는) 종속성으로 가지고 있는지 여부. 이미 가지고 있더라도 코드 조각은 의사 코드이며 실제로 달성하려는 것을 전달하지 않습니다. 당신은 확실히 할 수 없습니다 쓰기 return ImmutableMap.of(somethingElse). 대신을 ImmutableMap.of(somethingElse)필드로 저장 하고이 필드 만 반환합니다. 이 모든 것이 여기의 디자인에 영향을 미칩니다.
Marco13

답변:


70
  • 공개용 API를 작성 중이고 불변성이 디자인의 중요한 측면 인 경우 메서드 이름이 반환 된 맵이 불변이라는 것을 명확하게 나타내거나 구체적인 유형을 반환하여 명시 적으로 만들 것입니다. 지도. 제 생각에는 javadoc에서 언급하는 것만으로는 충분하지 않습니다.

    분명히 Guava 구현을 사용하고 있으므로 문서를 살펴 보았고 추상 클래스이므로 실제 구체적인 유형에 약간의 유연성을 제공합니다.

  • 내부 도구 / 라이브러리를 작성하는 경우 일반 Map. 사람들은 자신이 호출하는 코드의 내부에 대해 알거나 최소한 쉽게 액세스 할 수 있습니다.

내 결론은 명시적인 것이 좋다는 것입니다. 일을 우연에 맡기지 마십시오.


1
하나의 추가 인터페이스, 하나의 추가 추상 클래스 및이 추상 클래스를 확장하는 하나의 클래스. 이것은 OP가 요구하는 간단한 일, 즉 메소드가 Map인터페이스 유형 또는 ImmutableMap구현 유형을 반환하는지 여부와 같은 간단한 일에 너무 번거 롭습니다 .
fps

20
Guava의를 참조하는 경우 ImmutableMapGuava 팀의 조언은 ImmutableMap의미 론적 보장이있는 인터페이스 를 고려 하고 해당 유형을 직접 반환해야한다는 것입니다.
루이 Wasserman

1
@LouisWasserman mm, Guava 구현에 대한 링크를 제공하는 질문을 보지 못했습니다. 난 당신을 감사합니다, 다음 조각을 제거 할 수 있습니다
DICI

3
나는 Louis와 동의합니다. 만약 당신이 Immutable 객체 인지도를 반환하고자한다면, 반환 객체가 그 유형인지 확인하는 것이 바람직합니다. 어떤 이유로 든 변경 불가능한 유형이라는 사실을 숨기려면 고유 한 인터페이스를 정의하십시오. 지도로두면 오해의 소지가 있습니다.
WSimpson

1
@WSimpson 나는 그것이 내 대답이 말하는 것이라고 믿습니다. Louis가 내가 추가 한 일부 스 니펫에 대해 이야기하고 있었지만 제거했습니다.
Dici

38

ImmutableMap반환 유형으로 가지고 있어야 합니다. Map구현에 의해 지원되지 않는 방법을 포함 ImmutableMap(예 put) 및 표시된 @deprecated하여 ImmutableMap.

더 이상 사용되지 않는 메서드를 사용하면 컴파일러 경고가 발생하며 대부분의 IDE는 사람들이 더 이상 사용되지 않는 메서드를 사용하려고 할 때 경고합니다.

이 고급 경고는 무언가 잘못되었다는 첫 번째 힌트로 런타임 예외를 갖는 것보다 낫습니다.


1
이것은 제 생각에 가장 현명한 대답입니다. Map 인터페이스는 계약이며 ImmutableMap은 분명히 그것의 중요한 부분을 위반하고 있습니다. 이 경우 Map을 반환 유형으로 사용하는 것은 의미가 없습니다.
TonioElGringo

@TonioElGringo Collections.immutableMap그럼 어때?
OrangeDog 2016-06-29

@TonioElGringo는 ImmutableMap이 구현하지 않는 메서드는 인터페이스의 문서 docs.oracle.com/javase/7/docs/api/java/util/… 에서 명시 적으로 선택 사항으로 표시되어 있습니다. 따라서 (적절한 javadocs로) Map을 반환하는 것이 여전히 합리적이라고 생각합니다. 하지만 위에서 논의한 이유 때문에 ImmutableMap을 명시 적으로 반환하도록 선택했습니다.
AMT

3
@TonioElGringo 그것은 아무것도 위반하지 않으며, 그 방법은 선택 사항입니다. 에서와 같이 유효한 List보장이 없습니다 List::add. 시도해보십시오 Arrays.asList("a", "b").add("c");. 런타임에 실패 할 것이라는 표시는 없지만 실패합니다. 적어도 구아바는 당신에게주의를 기울입니다.
njzk2

14

반면에 이렇게두면 다른 개발자들은이 객체가 불변이라는 사실을 놓칠 수 있습니다.

javadocs에서 언급해야합니다. 개발자들은 그것들을 읽습니다.

따라서 불변 객체라는 주요 목표를 달성하지 못할 것입니다. 변경할 수있는 개체의 수를 최소화하여 코드를 더 명확하게 만듭니다. 최악의 경우라도 잠시 후 누군가이 객체를 변경하려고 시도 할 수 있으며 이로 인해 런타임 오류가 발생합니다 (컴파일러가 이에 대해 경고하지 않음).

테스트되지 않은 코드를 게시하는 개발자는 없습니다. 그리고 그가 그것을 테스트 할 때, 그는 이유뿐만 아니라 그가 불변의 맵에 쓰려고 한 파일과 줄을 볼 때 예외가 발생합니다.

하지만 Map포함 된 객체가 아닌 자체 만 변경할 수 없습니다.


10
"어떤 개발자도 테스트되지 않은 코드를 게시하지 않습니다." 이것에 베팅 하시겠습니까? 이 문장이 사실이라면 퍼블릭 소프트웨어에 버그가 훨씬 적을 것입니다. 나는 "대부분의 개발자들 ..."이 사실 일 것이라고 확신 할 수 없습니다. 그리고 네, 실망 스럽습니다.

1
좋아, 톰이 맞아요. 무슨 말을 하려는지 잘 모르겠지만 실제로 쓴 내용은 분명히 거짓입니다. (또한 : 성별 포괄 슬쩍 찌르다)
djechlin

@ 톰 난 당신이 대답 풍자 놓친 것 같아요
경감 Fogetti을

10

이렇게두면 다른 개발자가이 객체가 불변이라는 사실을 놓칠 수 있습니다.

사실이지만 다른 개발자는 자신의 코드를 테스트하고 적용되는지 확인해야합니다.

그럼에도 불구하고이 문제를 해결할 수있는 두 가지 옵션이 더 있습니다.

  • Javadoc 사용

    @return a immutable map
    
  • 설명이 포함 된 메서드 이름을 선택했습니다.

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    구체적인 사용 사례의 경우 메서드 이름을 더 잘 지정할 수도 있습니다. 예

    public Map<String, Integer> getUnmodifiableCountByWords()
    

너는 어떤 다른 일을 할 수 있니?!

당신은 반환 할 수 있습니다

  • private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    이 방법은 많은 클라이언트가 맵을 수정할 것으로 예상하고 맵에 몇 개의 항목 만 포함되어있는 한 사용해야합니다.

  • CopyOnWriteMap

    COW 컬렉션은 일반적으로
    동시성 을 처리해야 할 때 사용됩니다 . 그러나 CopyOnWriteMap은 변경 작업 (예 : 추가, 제거)에서 내부 데이터 구조의 복사본을 생성하기 때문에이 개념은 상황에서도 도움이 될 것입니다.

    이 경우 변경 작업을 제외하고 모든 메서드 호출을 기본 맵에 위임하는 맵 주위에 얇은 래퍼가 필요합니다. 변경 작업이 호출되면 기본 맵의 복사본이 생성되고 모든 추가 호출이이 복사본에 위임됩니다.

    일부 클라이언트가 맵을 수정할 것으로 예상되는 경우이 접근 방식을 사용해야합니다.

    슬프게도 Java에는 그러한 CopyOnWriteMap. 그러나 제 3자를 찾거나 직접 구현할 수 있습니다.

마지막으로지도의 요소는 여전히 변경 가능할 수 있다는 점을 기억해야합니다.


8

ImmutableMap을 확실히 반환합니다. 이유는 다음과 같습니다.

  • 메서드 서명 (반환 유형 포함)은 자체 문서화되어야합니다. 의견은 고객 서비스와 같습니다. 고객이 의견에 의존해야하는 경우 주요 제품에 결함이있는 것입니다.
  • 어떤 것이 인터페이스인지 클래스인지는 확장하거나 구현할 때만 관련이 있습니다. 인스턴스 (객체)가 주어지면 클라이언트 코드의 99 %는 무언가가 인터페이스인지 클래스인지 알거나 신경 쓰지 않습니다. 처음에는 ImmutableMap이 인터페이스라고 가정했습니다. 링크를 클릭 한 후에야 그것이 수업이라는 것을 깨달았습니다.

5

수업 자체에 따라 다릅니다. 구아바 ImmutableMap는 변경 가능한 클래스에 대한 변경 불가능한보기를 의도하지 않습니다. 클래스가 불변하고 기본적으로 인 구조가 있다면 ImmutableMap반환 유형을 만드십시오 ImmutableMap. 그러나 클래스가 변경 가능하면하지 마십시오. 이 경우 :

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

Guava는 매번지도를 복사합니다. 느립니다. 그러나 internalMap이미 이면 ImmutableMap완전히 괜찮습니다.

클래스를 returning으로 제한하지 않으면 ImmutableMap대신 다음 Collections.unmodifiableMap과 같이 반환 할 수 있습니다 .

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

이것은 지도에 대한 변경 불가능한 보기 입니다. 경우 internalMap변경, 그래서의 복사 캐시합니다 Collections.unmodifiableMap(internalMap). 그러나 나는 여전히 게터에게 그것을 선호합니다.


1
이것들은 좋은 점이지만 완전성을 위해 나는 참조 보유자 만 수정할 수 없다는 것을 설명하는 이 답변이 마음에 unmodifiableMap들었습니다.보기이고지지지도 보유자는지지지도를 수정할 수 있으며보기가 변경됩니다. 그래서 그것은 중요한 고려 사항입니다.
davidbak

@ davidback이 언급했듯이 Collections.unmodifiableMap. 이 메소드는 별도의 고유 한 새지도 객체를 만드는 대신 원래지도에 보기 를 반환 합니다. 따라서 원래지도를 참조하는 다른 코드는 수정할 수 있으며 수정 불가능한지도의 소유자는지도 아래에서 실제로 수정할 수있는 어려운 방법을 알게됩니다. 나는 그 사실에 스스로 물었다. 지도 사본을 반환하거나 Guava를 사용하는 방법을 배웠습니다 ImmutableMap.
Basil Bourque

5

정확한 질문에 대한 답은 아니지만지도를 반환해야하는지 여부를 고려해 볼 가치가 있습니다. 맵이 변경 불가능한 경우 제공되는 기본 메소드는 get (key)를 기반으로합니다.

public Integer fooOf(String key) {
    return map.get(key);
}

이것은 API를 훨씬 더 단단하게 만듭니다. 맵이 실제로 필요한 경우 항목 스트림을 제공하여 API 클라이언트에 맡길 수 있습니다.

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

그런 다음 클라이언트는 필요에 따라 자체 불변 또는 변경 가능한 맵을 만들거나 항목을 자체 맵에 추가 할 수 있습니다. 클라이언트가 값이 존재하는지 알아야하는 경우 대신 선택 사항이 리턴 될 수 있습니다.

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}

-1

불변지도는지도의 한 유형입니다. 따라서 반환 유형의 Map은 그대로 두는 것이 좋습니다.

사용자가 반환 된 객체를 수정하지 않도록하기 위해 메서드 설명서에서 반환 된 객체의 특성을 설명 할 수 있습니다.


-1

이것은 논쟁의 여지가 있지만 여기에서 더 나은 아이디어는 맵 클래스에 대한 인터페이스를 사용하는 것입니다. 이 인터페이스는 불변이라고 명시 적으로 말할 필요는 없지만 인터페이스에서 부모 클래스의 setter 메서드를 노출하지 않으면 메시지는 동일합니다.

다음 기사를 살펴보십시오.

앤디 깁슨


1
유해한 것으로 간주되는 불필요한 포장지.
djechlin 2016-06-29

당신 말이 맞아요, 인터페이스가 충분하기 때문에 여기서도 래퍼를 사용하지 않을 것입니다.
WSimpson

이것이 옳은 일이라면 ImmutableMap이 ImmutableMapInterface를 이미 구현할 것이라고 생각하지만 기술적으로 이해하지 못합니다.
djechlin

요점은 Guava 개발자가 의도 한 것이 아니라이 개발자가 아키텍처를 캡슐화하고 ImmutableMap의 사용을 노출하지 않기를 원한다는 사실입니다. 이를 수행하는 한 가지 방법은 인터페이스를 사용하는 것입니다. 지적한대로 래퍼를 사용하는 것은 필요하지 않으므로 권장되지 않습니다. 지도 반환은 오해의 소지가 있으므로 바람직하지 않습니다. 따라서 목표가 캡슐화라면 인터페이스가 최선의 선택 인 것 같습니다.
WSimpson

Map인터페이스를 확장 (또는 구현)하지 않는 무언가를 반환 할 때의 단점은 Map캐스팅하지 않고 는 어떤 API 함수에도 전달할 수 없다는 것입니다. 여기에는 다른 유형의지도에 대한 모든 종류의 복사 생성자가 포함됩니다. 또한 변경 가능성이 인터페이스에 의해 "숨겨진"경우 사용자는 항상 코드를 캐스팅하고 이러한 방식으로 중단하여이 문제를 해결할 수 있습니다.
헐크
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.