原文: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 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 usingString keys backed by a HashMap might look like this:

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 this Context can be 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 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:

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

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 theClassCastException scenario 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#get of type T is 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#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.

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 has been replaced with an effective dynamic variant. And this is how 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 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 <T> void put( Class<T> 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<Runnable> in a type safe manner.

This is because there is no particular class object for List<Runnable>. 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:

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 Class as hook to the type information. And the adjusted Context now 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 Context like this:

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 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):

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

转载于:https://www.cnblogs.com/davidwang456/p/4329860.html

How to Map Distinct Value Types Using Java Generics--reference相关推荐

  1. java map常用类及其方法_Day50.Map类常用的方法 -Java常用类、集合#、IO

    Day50.Map类常用的方法 -Java常用类.集合#.IO Day50.Map类常用的方法 -Java常用类.集合#.IO Map类常用的方法 . 总结: 常用方法 添加: put(Object ...

  2. java不同垃圾回收器_细述 Java垃圾回收机制→Types of Java Garbage Collectors

    本文非原创,翻译自Types of Java Garbage Collectors 在Java中为对象分配和释放内存空间都是由垃圾回收线程自动执行完成的.和C语言不一样的是Java程序员不需要手动写垃 ...

  3. 【Java】- Incompatible types. Found: java. lang. String', required:' byte, char, short or int'

    IDEA项目编译报错:Incompatible types. Found: java. lang. String', required:' byte, char, short or int',swit ...

  4. Java - The Basics of Java Generics

    分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net 1. Introduction Java Gener ...

  5. Java Generics示例教程 - 通用方法,类,接口

    Java Generics示例教程 - 通用方法,类,接口 Java Genrics是Java 5中引入的最重要的功能之一.如果您一直在使用Java Collections 对于版本5或更高版本,我确 ...

  6. Java Generics

    一 Generics简介 Generics 是程序设计语言的一种技术,指将程序中数据类型进行参数化,它本质上是对程序的数据类型进行一次抽象,扩展语言的表达能力,同时支持更大粒度的代码复用. 对于一些数 ...

  7. Java - Generics are not real generics

    分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net Java 5 added generics into ...

  8. Map集合的遍历(java)

    2019独角兽企业重金招聘Python工程师标准>>> Map集合通过entrySet 和 keySet都可以使用迭代器 以及for循环拿到key和value: import jav ...

  9. 如何判断map为空_在Java中如何优雅地判空

    来自:李良逸 链接:http://blog.imuxuan.com/archives/86 判空灾难 作为搬砖党的一族们,我们对判空一定再熟悉不过了,不要跟我说你很少进行判空,除非你喜欢 NullPo ...

最新文章

  1. Webpack模块化原理简析
  2. ubuntu14.04下的NVIDIA Tesla K80显卡驱动的安装教程
  3. signature=c58a3c5e53e2eed44d09cd8589207f96,合同英语
  4. 计算1+2+……+N的和。
  5. P4178 Tree
  6. erwin怎么设置编码_Word页码的设置方式
  7. 计算机应用基础165791,人大网大计算机应用基础试题答案资料.doc
  8. mysql-mmm官方安装指南翻译
  9. 20200203_selenium爬取百度新闻
  10. 最适合菜鸟的汉诺塔讲解
  11. 容器镜像仓库Harbor安装部署及简单使用
  12. es6 javascript 尾调用
  13. 笔记本选购指南2020双十一
  14. 人类计划软件测试,人类分裂了16种人格,测测你是哪一种?
  15. win10蓝牙鼠标、耳机无法连接,无蓝牙开关标志解决方案
  16. 学习笔记 | NIPS 2021 regularization cocktail 调优的 MLPs 在表格数据上优于 GBDTs | Regularization is All Your Need
  17. Macbook如何打开Chrome调试工具Developer Tools
  18. 华为ModelArts(声音分类与文本分类项目的创建及使用)
  19. ai如何置入_ai图片(ai怎么把图片嵌入到图形里)
  20. method属性值为get提交表单信息,为什么在地址栏不会显示呢。

热门文章

  1. 添加halcon图像显示控件_初级应用实战来咯!C#联合Halcon读取图像,带讲解!!...
  2. oa提醒模块要素_OA办公系统的选型有哪些要素?OA系统如何给企业带来巨大价值?...
  3. oracle union详解,Oracle中的union和join
  4. iis7.5php.2,Windows2008 IIS7.5搭建PHP5.2
  5. java+queue+se_「013期」JavaSE面试题(十三):多线程(3)
  6. C++中的逻辑操作符重载
  7. linux系统虚拟化测试,网络性能与磁盘测试 - Linux虚拟化性能PK:Ubuntu系统6大版本_Linux新闻_Linux公社-Linux系统门户网站...
  8. 支架预压弹性变形值计算_桥梁支架的预压方法与流程
  9. android按钮点击事件(多种方法实现)
  10. 游戏维护关闭服务器,游戏服务器正在维护中