Java 범용 매핑의 다른 값 유형 상세 정보 및 인스턴스 코드

8405 단어 Java범용 매핑
Java 범용 맵의 다른 값 유형 상세 정보
선언:
일반적으로 개발자들은 특정한 용기에 임의의 유형의 값을 비추는 경우가 종종 있다.그러나 Java 컬렉션 API는 패라메트릭 컨테이너만 제공합니다.이것은 단일 값 유형과 같은 HashMap을 안전하게 사용할 수 있도록 제한합니다.그런데 사과와 배를 섞으려면 어떻게 해야 하나요?
다행히도 Java 범용 맵을 사용할 수 있는 간단한 디자인 모델이 있습니다. Joshua Bloch는 (두 번째 버전, 29항) 형식이 안전한 이구 용기 (typesafe hetereogeneous Container) 라고 설명했습니다.
이 주제에 관해서 최근에 그다지 적합하지 않은 해결 방안을 만났다.그것은 나에게 이 문장에서 이 문제의 영역을 설명하고 세부 사항을 실현하는 생각을 논술해 주었다.
Java 범주를 사용하여 다른 값 유형 매핑
예를 들어, 특정한 키를 임의의 형식으로 연결할 수 있는 프로그램의 상하문을 제공해야 한다.String을 키로 하는 HashMap을 이용하여 간단하고 유형이 아닌 안전(type safe)을 실현할 수 있습니다.

public class Context {

 private final Map<String,Object> values = new HashMap<>();

 public void put( String key, Object value ) {
  values.put( key, value );
 }

 public Object get( String key ) {
  return values.get( key );
 }

 [...]
}

다음 코드 세션에서는 프로그램에서 Context를 사용하는 방법을 보여 줍니다.

Context context = new Context();
Runnable runnable = ...
context.put( "key", runnable );

// several computation cycles later...
Runnable value = ( Runnable )context.get( "key" );

이런 방법의 단점은 여섯 번째 줄에서 아래로 전환해야 한다는 것이다(down cast).키 값 대 중간 값 유형을 바꾸면 ClassCastException 예외가 발생합니다.

Context context = new Context();
Runnable runnable = ...
context.put( "key", runnable );

// several computation cycles later...
Executor executor = ...
context.put( "key", executor );

// even more computation cycles later...
Runnable value = ( Runnable )context.get( "key" ); // runtime problem

이런 문제가 발생하는 원인은 추적되기 어렵다. 왜냐하면 관련 실현 절차는 이미 당신의 프로그램 각 부분에 광범위하게 분포되어 있기 때문이다.
이런 상황을 개선하기 위해서는value와key,value를 모두 연결하는 것이 합리적인 것 같다.
이러한 방법에 따른 여러 가지 해결 방안에서 흔히 볼 수 있는 오류는 다음과 같은 Context의 변종에 귀결된다.

public class Context {

 private final <String, Object> values = new HashMap<>();

 public <T> void put( String key, T value, Class<T> valueType ) {
  values.put( key, value );
 }

 public <T> T get( String key, Class<T> valueType ) {
  return ( T )values.get( key );
 }

 [...]
}

같은 기본적인 사용법은 다음과 같을 수 있다.

Context context = new Context();
Runnable runnable = ...
context.put( "key", runnable, Runnable.class );

// several computation cycles later...
Runnable value = context.get( "key", Runnable.class );

언뜻 보기에, 이 코드는 6줄에서 아래로 전환하는 것을 피하기 때문에 더 안전한 착각을 줄 수 있다.그러나 아래의 코드를 실행하면 우리는 현실로 돌아갈 것이다. 왜냐하면 우리는 여전히 10줄의 값 부여 문장에서 ClassCastException의 품으로 떨어질 것이다.

Context context = new Context();
Runnable runnable = ...
context.put( "key", runnable, Runnable.class );

// several computation cycles later...
Executor executor = ...
context.put( "key", executor, Executor.class );

// even more computation cycles later...
Runnable value = context.get( "key", Runnable.class ); // runtime problem

뭐가 문제지?
우선, Context #get에서 아래로 전환하는 것은 무효입니다. 형식 지우기는 무계 매개 변수 (unbonded parameters) 를 대체하기 위해 정적 변환된 Object를 사용하기 때문입니다.그 밖에 더 중요한 것은 이 실현은 Context #put에서 제공하는 유형 정보를 전혀 사용하지 않았다는 것이다.이것은 고작 이런 짓을 많이 하는 미용일 뿐이다.
유형 안전한 이구 용기
위의 Context의 변종은 작용하지 않지만 방향을 가리킨다.다음 문제는 어떻게 합리적으로 이 키를 매개 변수화합니까?이 질문에 대답하기 위해 Bloch가 기술한 유형에 따라 안전한 이구 용기 모드 (typesafe heterogenous container pattern) 의 간편한 설치를 살펴보자.
우리의 생각은 키 자체의class 유형을 키로 하는 것이다.Class는 매개 변수화된 형식이기 때문에, 우리가 Context 방법을 형식적으로 안전하게 할 수 있으며, 검사되지 않은 강제적인 T로 전환할 필요가 없다.이런 형식의 클래스 대상을 유형 토큰(type token)이라고 부른다.

public class Context {

 private final Map<Class<?>, Object> values = new HashMap<>();

 public <T> void put( Class<T> key, T value ) {
  values.put( key, value );
 }

 public <T> T get( Class<T> key ) {
  return key.cast( values.get( key ) );
 }

 [...]
}

Context#get의 실현에서 어떻게 효과적인 동적 변수로 아래로 전환하는지 주의하십시오.클라이언트는 이 context를 사용할 수 있습니다.

Context context = new Context();
Runnable runnable ...
context.put( Runnable.class, runnable );

// several computation cycles later...  
Executor executor = ...
context.put( Executor.class, executor );

// even more computation cycles later...
Runnable value = context.get( Runnable.class );

이번 클라이언트의 코드는 정상적으로 작동할 수 있으며, 더 이상 클래스 변환 문제가 없을 것이다. 왜냐하면 서로 다른 값 형식을 통해 어떤 키 값을 교환할 수 없기 때문이다.
광명이 있는 곳에는 반드시 음영이 있고, 음영이 있는 곳에는 반드시 광명이 있다.그늘이 없는 광명도 없고, 빛이 없는 그늘도 없다.촌상춘수
Bloch는 이 모델에는 두 가지 한계가 있다고 지적했다."우선, 악성 클라이언트는 원생태 형식(raw form)으로class 대상을 사용하여 유형의 안전을 쉽게 파괴할 수 있다."실행할 때 형식이 안전한지 확인하기 위해 Context#put에서 동적 변환 (dynamic cast) 을 사용할 수 있습니다.

public <T> void put( Class<T> key, T value ) {
 values.put( key, key.cast( value ) );
}
두 번째 한계는 구체화할 수 없는 (non-reifiable) 유형에 사용할 수 없다는 것이다('Effective Java'25항 참조).다시 말하면 당신은 Runnable나 Runnable[]를 저장할 수 있지만 List를 저장할 수 없습니다.
이것은 List에 특정한 클래스 대상이 없기 때문에 모든 매개 변수화 형식은 같은 List를 가리킨다.class 대상.따라서 Bloch는 이러한 한계에 대해 만족스러운 해결 방안이 없다고 지적했다.
그러나 만약 당신이 같은 값 유형을 가진 두 항목을 저장해야 한다면 어떻게 해야 합니까?형식이 안전한 용기만 저장하기 위해서라면 새로운 형식 확장을 고려할 수 있지만, 이것은 분명히 가장 좋은 디자인이 아니다.맞춤형 키를 사용하는 것이 더 좋은 방안일 수도 있다.
여러 유형의 컨테이너 항목
여러 종류의 용기 항목을 저장할 수 있도록 사용자 정의 키로 Context 클래스를 변경할 수 있습니다.이 키는 우리 유형의 안전에 필요한 유형 정보를 제공하고 서로 다른 값 대상 (value objects) 을 구분하는 표지를 제공해야 합니다.String 인스턴스를 표식으로 하는 유치한 key 구현은 다음과 같습니다.

public class Key<T> {

 final String identifier;
 final Class<T> type;

 public Key( String identifier, Class<T> type ) {
  this.identifier = identifier;
  this.type = type;
 }
}

우리는 매개 변수화된 Class를 유형 정보의 갈고리로 다시 사용합니다. 조정된 Context는 Class가 아닌 매개 변수화된 키를 사용합니다.

public class Context {

 private final Map<Key<?>, Object> values = new HashMap<>();

 public <T> void put( Key<T> key, T value ) {
  values.put( key, value );
 }

 public <T> T get( Key<T> key ) {
  return key.type.cast( values.get( key ) );
 }

 [...]
}

클라이언트는 이 버전의 Context를 사용합니다.

Context context = new Context();

Runnable runnable1 = ...
Key<Runnable> key1 = new Key<>( "id1", Runnable.class );
context.put( key1, runnable1 );

Runnable runnable2 = ...
Key<Runnable> key2 = new Key<>( "id2", Runnable.class );
context.put( key2, runnable2 );

// several computation cycles later...
Runnable actual = context.get( key1 );

assertThat( actual ).isSameAs( runnable1 );

비록 이 코드 단편은 사용할 수 있지만, 여전히 결함이 있다.Context#get에서 키는 질의 매개 변수로 사용됩니다.같은 identifier와class로 두 개의 다른 키를 초기화하는 실례입니다. 하나는put에 사용되고, 다른 하나는get에 사용되며, 마지막 get 작업은null로 되돌아옵니다.이것은 우리가 원하는 것이 아니다..

// 
Context context = new Context();

Runnable runnable1 = ...
Key<Runnable> key1 = new Key<>( "same-id", Runnable.class );
Key<Runnable> key2 = new Key<>( "same-id", Runnable.class );
context.put( key1, runnable1 );// put

context.get(key2); // get --> return null;

다행히도 키를 위해 적합한 equals와hashCode를 설계하면 이 문제를 쉽게 해결할 수 있고 HashMap에서 예상대로 작업을 찾을 수 있습니다.마지막으로, 키를 만드는 데 공장 방법을 제공하여 창설 과정을 간소화할 수 있습니다. (static import와 함께 사용할 때 유용합니다.)

public static Key key( String identifier, Class type ) {
 return new Key( identifier, type );
}
결론
"집합 API는 모든 용기에 고정된 수량의 유형 매개 변수만 있을 수 있도록 제한하는 일반적인 사용법을 설명합니다. 용기가 아닌 키 위에 유형 매개 변수를 놓아서 이 제한을 피할 수 있습니다. 이 유형의 안전한 이구 용기는 Class로 대응할 수 있습니다."(Joshua Bloch, Effective Java 29항).
상술한 폐막사를 드리면 보충할 것도 없습니다. 사과와 배를 혼합하는 데 성공하기를 기원하는 것 외에는...
읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기