(Reproduced) How can the Value in Java Map be of any type? mapvalue
Reprint address: http://www.importnew.com/15556.html if there is infringement, please contact the author to delete in time.
I moved to my blog to have time to savor and play.
This article is translated from javacodegeeks by ImportNew-shutear. Welcome to the translation team. For more information, see the requirements at the end of this document.
In general, developers occasionally encounter this situation: ing any type of value in a specific container. However, Java Collection APIs only provide parameterized containers. This restricts the Safe Use of HashMap for types, such as a single value type. But what should I do if I want to mix apples and pears?
Fortunately, there is a simple design pattern that allows you to map different value types using Java generics, Joshua Bloch in its aggressive Java (second edition, 29th items) describe it as a type-safe heterogeneous container (Typesafe hetereogeneous container).
I have encountered some unsuitable solutions for this topic recently. It gave me an explanation of the problem domain in this article and explained some implementation details.
Use Java generic ing to map different value types
For example, you need to provide the context of an application, which can bind a specific key to any type of value. Using String as the HashMap of the key, a simple type safe implementation may be as follows:
1234567891011121314 |
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 ); } [...] } |
The following code snippet shows how to useContext
:
123456 |
Context context = new Context(); Runnable runnable = ... context.put( "key" , runnable ); // several computation cycles later... Runnable value = ( Runnable )context.get( "key" ); |
It can be seen that the disadvantage of this method is that the downward Transformation (down cast) needs to be performed in line 6th ). If you replace the value type of the key-value pair,ClassCastException
Exception:
12345678910 |
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 |
The cause of this problem is hard to trace, because the implementation steps may be widely distributed in various parts of your program.
To improve this situation, it seems reasonable to bind value to its key and value.
Among the various solutions that I have seen and follow this method, common errors are more or less attributed to the following:Context
Variants:
1234567891011121314 |
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 ); } [...] } |
The same basic usage may be as follows:
123456 |
Context context = new Context(); Runnable runnable = ... context.put( "key" , runnable, Runnable. class ); // several computation cycles later... Runnable value = context.get( "key" , Runnable. class ); |
At first glance, this Code may give you the illusion of more types of security, because it avoids downcast in line 2 ). However, running the following code will bring us back to reality, because we will still fall into the value assignment statement at line 1.ClassCastException
Embrace:
12345678910 |
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 |
What's wrong?
First, the downward transformation in Context # get is invalid because the type erasure replaces the unbonded parameters parameter with the static transformation Object ). In addition, more importantly, this implementation does not useContext#put
Type information. At best, this is an extra beauty.
Type-safe heterogeneous containers
Although the aboveContext
The variant does not work, but indicates the direction. The following question is: how can we reasonably parameterize this key? To answer this question, let's take a look at a simple implementation of typesafe heterogenous container pattern based on the Type Security heterogeneous container model described by Bloch.
Our idea is to use the key itself.class
Type as the key. BecauseClass
Is a parameterized type, which ensures that the Context method is type-safe without the need to resort to an unchecked forced conversionT
. In this formClass
The object is called a type token ).
1234567891011121314 |
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 ) ); } [...] } |
Note thatContext#get
How to replace the downward transformation with an effective dynamic variable. The client can use this context as follows:
12345678910 |
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 ); |
The client code will work normally this time and there will be no class conversion issues, because it is impossible to exchange a key-value pair through a different value type.
Where there is light, there must be shadows, and where there is shadow, there must be light. There is no shadow or shadow. Murakami chunshu
Bloch points out that this pattern has two limitations. "First, malicious clients can use class objects in raw forms to easily break type security ." To ensure the type security during runtime, you canContext#put
Use dynamic cast ).
123 |
public <T> void put( Class<T> key, T value ) { values.put( key, key.cast( value ) ); } |
The second limitation is that it cannot be used in non-Concrete (Non-reifiable). In other words, you can saveRunnable
OrRunnable[]
But cannot saveList<Runnable>
.
This is becauseList<Runnable>
No specific class Object. All parameterized types refer to the sameList.class
Object. Therefore, Bloch points out that there is no satisfactory solution to this limitation.
But what if you need to store two entries with the same value type? If you only want to store a type-Safe Container, you can consider creating a new type extension, but this is obviously not the best design. Using custom keys may be a better solution.
Multiple container entries of the same type
To store multiple container entries of the same type, we can use the custom key to changeContext
Class. This key must provide the type information required for data type security and identify different value objects. AString
The naive key implementation for instances may be like this:
12345678910 |
public class Key<T> { final String identifier; final Class<T> type; public Key( String identifier, Class<T> type ) { this .identifier = identifier; this .type = type; } } |
We use parameterizedClass
As a hook for type information, the adjusted Context uses parameterizedKey
InsteadClass
.
1234567891011121314 |
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 ) ); } [...] } |
The client will use this versionContext
:
1234567891011121314 |
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 code snippet is available, it still has defects. InContext#get
Medium,Key
Used as a query parameter. Use the same identifier and class to initialize two differentKey
Instance, one for put, the other for get, and the lastget
Operation will returnnull
. This is not what we want ......
123456789 |
// The translator attaches a code snippet. 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 ); // One for put context.get(key2); // The other is used for get --> return null; |
Fortunately,Key
Design appropriateequals
AndhashCode
You can easily solve this problem and thenHashMap
Search for jobs as expected. Finally, you can provide a factory method for key creation to simplify the creation process (useful when used together with static import ):
123 |
public static Key key( String identifier, Class type ) { return new Key( identifier, type ); } |
Conclusion
"The collection API illustrates the general usage of generics and limits that each container can have only a fixed number of type parameters. You can avoid this restriction by placing the type parameter on the key instead of the container. For this type of secure heterogeneous containers, Class can be used as the key ." (Joshua Bloch, 29th objective Java ).
There is nothing to add to the above acronyms, except to wish you a successful mix of Apple and pear ......
Original article: javacodegeeks Translation: ImportNew.com-shutear
Http://www.importnew.com/15556.html.
[For reprinting, Please retain the original source, translator, and translation links.]