How to Map Distinct Value Types Using Java generics--reference

Source: Internet
Author: User
Tags pears

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

Occasionally the average developer runs into a situation where he had 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 for HashMap example to a single value type. But what if you want to mix apples and pears?

Luckily there is a easy design pattern the allows to map distinct value types using Java generics, which Joshua Bloch ha s described as typesafe hetereogeneous container in He 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 PR Oblem domain and elaborate on some implementation aspects in this post.

Map Distinct Value Types Using Java generics

Consider for the sake of example so you had to provide some kind of application context which allows to bind values of a Rbitrary types to certain keys. A simple non type safe implementation using the String keys backed by a might look like this HashMap :

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 snippet shows how the Context can is used in a program:

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 being seen at line six where a down cast is needed. Obviously this can leads to a in case the ClassCastException Key-value pair have been replaced by a different value type:

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 such problems can be difficult to trace as the related implementation steps might is spread wide apart in you R application. To improve the situation it seems reasonable to bind the value of the not-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:

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:

Context context = new context (); Runnable Runnable = ... context.put ("Key", Runnable, Runnable.class);//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 the ClassCastException scenario during the assignment in Li NE 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 under cast in of Context#get type are T ineffective as type erasure replaces unbounded parameters with a stat IC cast to Object . But more important the implementation does isn't use the type information provided by as Context#put key. At the most it serves as superfluous cosmetic effect.

Typesafe hetereogeneous Container

Although the last variant didn't work out very well it points to the right Context direction. The question is what to properly parameterize the key? To answer-a look at a stripped-down implementation according to the Typesafe hetereogenous container pattern des Cribed by Bloch.

The idea was to use the class type as key itself. Since is Class 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.

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 how the down cast within the Context#get implementation have been replaced with an effective dynamic variant. The context can be used by clients:

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 would work without class cast problems, as it was impossible to exchange a certain key-value pair By one with a different value type.

where there is light, there must are shadow, where there is shadow there must are light. There is no shadow without light and no light without shadow .... Haruki Murakami

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

Public <T> Void put (class<t> key, T value) {  values.put (key, Key.cast (value));}

The second limitation is and the pattern cannot be used in non-reifiable types (see Item, effective Java). Which means you can store value types like or is not in Runnable Runnable[] List<Runnable> a type safe manner.

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

But what if you need to store entries of the same value type? While creating new type extensions just for storage purpose into the type safe container might is 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 being able to store multiple container entries of the same type we could the class to use Context a custom key. Such a key have to provide the type information we need for the type safe behaviour and an identifier for distinction of th e Actual value objects.

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

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 as hook to the Class type information. And the adjusted now Context uses the parameterized Key instead of 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));  }  [...]}

A client would use this version of the like this 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);

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

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

public static  key key (String identifier, Class type) {  return new key (identifier, type);}
Conclusion

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

Given These closing remarks, there is nothing left to being added except for wishing good luck mixing apples and pears su Ccessfully ...

How to Map Distinct Value Types Using Java generics--reference

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.