How to Map Distinct Value Types Using Java Generics--reference
2015-03-11 14:41
585 查看
原文: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
Luckily there is an easy design pattern that allows to map distinct value types using Java generics, which Joshua Bloch has described as typesafe hetereogeneous container in his book Effective Java(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.
The drawback of this approach can be seen at line six where a down cast is needed. Obviously this can lead to a
Again basic usage might look like this:
So what went wrong?
First of all the down cast in
The idea is to use the
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
Again we use the parameterized
Although this snippet works, the implementation is still flawed. The
Luckily this can be solved easily with an appropriate
public static Key key( String identifier, Class type ) {
return new Key( identifier, type );
}
[/code]
Given these closing remarks, there is nothing left to be added except for wishing you good luck mixing apples and pears successfully…
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
HashMapfor 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 Joshua Bloch has described as typesafe hetereogeneous container in his book Effective Java(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 usingStringkeys backed by a
HashMapmight look like this:
Context context = new Context(); Runnable runnable = ... context.put( "key", runnable ); // 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
ClassCastExceptionin case the key-value pair has been replaced by a different value type:
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 followingContextvariant:
[code]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 );
}
[...]
}
Again basic usage might look like this:
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 theClassCastExceptionscenario during the assignment in line ten:
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
So what went wrong?
First of all the down cast in
Context#getof type
Tis ineffective as type erasure replaces unbounded parameters with a static cast to
Object. But more important the implementation does not use the type information provided by
Context#putas key. At most it serves as superfluous cosmetic effect.
Typesafe Hetereogeneous Container
Although the lastContextvariant 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
classtype as key itself. Since
Classis a parameterized type it enables us to make the methods of
Contexttype safe without resorting to an unchecked cast to
T. A
Classobject used in this fashion is called a type token.
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 );
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.
Where there is light, there must be shadow, where there is shadow there must be light. There is no shadow without light and no light without shadow….Haruki Murakami
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.
public class Key<T> { final String identifier; final Class<T> type; public Key( String identifier, Class<T> type ) { this.identifier = identifier; this.type = type; } }
Again we use the parameterized
Classas hook to the type information. And the adjusted
Contextnow uses the parameterized
Keyinstead of
Class:
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 );
Although this snippet works, the implementation is still flawed. The
Keyimplementation is used as lookup parameter in
Context#get. Using two distinct instances of
Keyinitialized with the same identifier and class – one instance used with put and the other used with get – would return
nullon
get. Which is not what we want.
Luckily this can be solved easily with an appropriate
equalsand
hashCodeimplementation of
Key. That allows the
HashMaplookup to work as expected. Finally one might provide a factory method for key creation to minimize boilerplate (useful in combination with static imports):
public static Key key( String identifier, Class type ) {
return new Key( identifier, type );
}
[/code]
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 useClassobjects 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…
相关文章推荐
- How to Check if an Array Contains a Value in Java Efficiently?---reference
- How to sort a Map<Key, Value> on the values in Java?
- The expected argument types are (java.util.Map) but the supplied types were(java.lang.String) and converted to (null).
- Unable to find a value for "dwmc" in object of class "java.lang.String" using operator "." (null
- How to invoke Java web service in ASP.net using C#
- Unable to find a value for "字段名" in object of class java.lang.String using operator "."
- How to Call Java functions from C Using JNI
- JAXB: how to marshall map into <key>value</key>
- Java Tutorial: How to Create RESTful Java Client using Apache HttpClient – Example
- JAXB: how to marshall map into <key>value</key>
- How to handle crash problem?(2.Finding crash information using the MAP file 2)
- How to execute webject using java api in Windchill
- How to Iterate Over a Map in Java
- java使用rest api登录salesforce | How to do authentication to salesforce from java class using Rest API
- How To Use JavaSoft References For Caching @ JDJ
- How does JVM map a Java thread to a native thread
- How to read ini file using Java
- Big Data Counting: How To Count A Billion Distinct Objects Using Only 1.5KB Of Memory
- Pass value from child popup window to parent page window using JavaScript--reference
- How to Iterate Over a Map in Java