加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

How to Map Distinct Value Types Using Java Generics--referen

发布时间:2020-12-14 06:20:37 所属栏目:Java 来源:网络整理
导读:原文:http://www.codeaffine.com/2015/03/04/map-distinct-value-types-using-java-generics/ Occasionally the average developer runs into a situation where he has to map values of arbitrary types within a particular container. However the Java

原文:http://www.codeaffine.com/2015/03/04/map-distinct-value-types-using-java-generics/

Occasionally the average developer runs into a situation where he has to map values of arbitrary types within a particular container. However the Java collection API provides container related parameterization only. Which limits the type safe usage of?HashMap?for example to a?single?value type. But what if you want to mix apples and pears?

Luckily there is an easy design pattern that allows to map distinct value types using Java generics,which??has described as?typesafe hetereogeneous container?in his book?(second edition,Item 29).

Stumbling across some not altogether congenial solutions regarding this topic recently,gave me the idea to explain the problem domain and elaborate on some implementation aspects in this post.

Map Distinct Value Types Using Java Generics

Consider for the sake of example that you have to provide some kind of application context that allows to bind values of arbitrary types to certain keys. A simple non type safe implementation usingString?keys backed by a?HashMap?might look like this:

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 );
}

[...]
}

The following snippet shows how this?Context?can be used in a program:

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

The drawback of this approach can be seen at line six where a down cast is needed. Obviously this can lead to a?ClassCastException?in case the key-value pair has been replaced by a different value type:

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

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

The cause of such problems can be difficult to trace as the related implementation steps might be spread wide apart in your application. To improve the situation it seems reasonable to bind the value not only to its key but also to its type.

Common mistakes I saw in several solutions following this approach boil down more or less to the following?Context?variant:

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

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

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

[...]
}

Again basic usage might look like this:

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

One first glance this code might give the illusion of being more type save as it avoids the down cast in line six. But running the following snippet gets us down to earth as we still run into theClassCastException?scenario during the assignment in line ten:

// 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

So what went wrong?

First of all the down cast in?Context#get?of type?T?is ineffective as??replaces unbounded parameters with a static cast to?Object. But more important the implementation does not use the type information provided by?Context#put?as key. At most it serves as superfluous cosmetic effect.

Typesafe Hetereogeneous Container

Although the last?Context?variant did not work out very well it points into the right direction. The question is how to properly parameterize the key? To answer this take a look at a stripped-down implementation according to the typesafe hetereogenous container pattern described by Bloch.

The idea is to use the?class?type as key itself. Since?Class?is a parameterized type it enables us to make the methods of?Context?type safe without resorting to an unchecked cast to?T. A?Class?object used in this fashion is called a type token.

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

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

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

[...]
}

Note how the down cast within the?Context#get?implementation has been replaced with an effective dynamic variant. And this is how the context can be used by clients:

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

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

This time the client code will work without class cast problems,as it is impossible to exchange a certain key-value pair by one with a different value type.

Bloch mentions two limitations to this pattern. ‘First,a malicious client could easily corrupt the type safety [...] by using a class object in its raw form.’ To ensure the type invariant at runtime a dynamic cast can be used within?Context#put.

 void put( Class key,T value ) {
  values.put( key,key.cast( value ) );
}

The second limitation is that the pattern cannot be used on?non-reifiable?types (see Item 25,Effective Java). Which means you can store value types like?Runnable?or?Runnable[]?but not?List?in a type safe manner.

This is because there is no particular class object for?List. All parameterized types refer to the same?List.class?object. Hence Bloch points out that there is no satisfactory workaround for this kind of limitation.

But what if you need to store two entries of the same value type? While creating new type extensions just for storage purpose into the type safe container might be imaginable,it does not sound as the best design decision. Using a custom key implementation might be a better approach.

Multiple Container Entries of the Same Type

To be able to store multiple container entries of the same type we could change the?Context?class to use a custom key. Such a key has to provide the type information we need for the type safe behaviour and an identifier for distinction of the actual value objects.

A naive key implementation using a?String?instance as identifier might look like this:

 {

final String identifier;
final Class type;

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

Again we use the parameterized?Class?as hook to the type information. And the adjusted?Context?now uses the parameterized?Key?instead of?Class:

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

public void put( Key key,value );
}

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

[...]
}

A client would use this version of?Context?like this:

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

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

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

assertThat( actual ).isSameAs( runnable1 );

Although this snippet works,the implementation is still flawed. The?Key?implementation is used as lookup parameter in?Context#get. Using two?distinct?instances of?Key?initialized with the?same?identifier and class – one instance used with put and the other used with get – would return?null?on?get. Which is not what we want.

Luckily this can be solved easily with an appropriate?equals?and?hashCode?implementation of?Key. That allows the?HashMap?lookup to work as expected. Finally one might provide a factory method for key creation to minimize boilerplate (useful in combination with static imports):

Conclusion

‘The normal use of generics,exemplified by the collection APIs,restricts you to a fixed number of type parameters per container. You can get around this restriction by placing the type parameter on the key rather than the container. You can use?Class?objects as keys for such typesafe heterogeneous containers’ (Joshua Bloch,Item 29,Effective Java).

Given these closing remarks,there is nothing left to be added except for wishing you good luck mixing apples and pears successfully…

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读