java 不同类型 映射_如何使用Java泛型映射不同的值类型

java 不同类型 映射

有时,一般的开发人员会遇到这样的情况,即他必须在特定容器内映射任意类型的值。 但是,Java集合API仅提供与容器相关的参数化。 例如,这将HashMap的类型安全使用限制为单个值类型。 但是,如果您想混合苹果和梨怎么办?

幸运的是,有一个简单的设计模式允许使用Java泛型映射不同的值类型, 约书亚·布洛赫(Joshua Bloch)在他的《 有效的Java》 (第二版,项目29)中将其描述为类型安全的异构容器

最近,在有关该主题的一些不完全相同的解决方案中遇到了麻烦,这使我有了解释问题域的想法,并在这篇文章中详细说明了一些实现方面。

使用Java泛型映射不同的值类型

出于示例考虑,您必须提供某种允许将任意类型的值绑定到某些键的应用程序上下文。 使用由HashMap支持的String键的简单非类型安全实现可能如下所示:

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 );}[...]
}

以下代码片段显示了如何在程序中使用此Context

Context context = new Context();
Runnable runnable = ...
context.put( "key", runnable );// several computation cycles later...
Runnable value = ( Runnable )context.get( "key" );

这种方法的缺点可以在需要下浇的第六行看到。 显然,如果键值对已被其他值类型替换,则可能导致ClassCastException

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

由于相关的实现步骤可能会在您的应用程序中分散开来,因此很难跟踪此类问题的原因。 为了改善这种情况,将值不仅绑定到其键而且还绑定到其类型似乎是合理的。

我在采用这种方法的几种解决方案中看到的常见错误或多或少归结为以下Context变体:

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 );}[...]
}

同样,基本用法可能如下所示:

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

乍一看,该代码可能会产生一种节省更多类型的幻想,因为它避免了第六行的转换。 但是运行下面的代码片段会使我们脚踏实地,因为在第十行的分配过程中我们仍然遇到ClassCastException场景:

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

那么出了什么问题呢?

首先,类型T Context#get中的向下强制转换无效,因为类型擦除将静态Object强制转换为Object从而替换了无边界参数。 但是更重要的是,该实现不使用Context#put提供的类型信息作为键。 至多它只是多余的美容效果。

类型安全的异构容器

尽管最后一个Context变体效果不佳,但它指出了正确的方向。 问题是如何正确设置密钥? 为了回答这个问题,请看一下Bloch描述的根据类型安全的异构容器模式精简的实现。

想法是使用class类型作为键本身。 由于Class是参数化类型,它使我们能够使Context类型的方法安全,而无需诉诸于T的未经检查的转换。 以这种方式使用的Class对象称为类型令牌。

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 ) );}[...]
}

请注意,如何使用有效的动态变体替换Context#get实现中的向下转换。 这是客户端可以使用上下文的方式:

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

这次,客户端代码将可以正常工作而不会产生类转换问题,因为不可能用具有不同值类型的一对交换某个键-值对。


有光的地方一定有阴影,有阴影的地方一定有光。 没有光就没有阴影,没有光就没有光……。

村上春树

Bloch提到了此模式的两个限制。 “首先,恶意客户端可以通过使用原始形式的类对象来轻易破坏类型安全性[...]。” 为了确保类型在运行时不变,可以在Context#put使用动态转换。

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

第二个限制是该模式不能用于不可更改的类型(请参阅第25条,有效的Java)。 这意味着您可以通过类型安全的方式存储诸如RunnableRunnable[]类的值类型,但不能存储List<Runnable>

这是因为List<Runnable>没有特定的类对象。 所有参数化类型都引用相同的List.class对象。 因此,Bloch指出,对于这种限制没有令人满意的解决方法。

但是,如果您需要存储两个相同值类型的条目怎么办? 虽然可以想象将新的类型扩展名仅用于存储到类型安全的容器中,但这并不是最佳的设计决策。 使用自定义键实现可能是更好的方法。

多个相同类型的容器条目

为了能够存储相同类型的多个容器条目,我们可以将Context类更改为使用自定义键。 这样的密钥必须提供类型安全行为所需的类型信息,以及用于区分实际值对象的标识符。

使用String实例作为标识符的朴素键实现可能如下所示:

public class Key<T> {final String identifier;final Class<T> type;public Key( String identifier, Class<T> type ) {this.identifier = identifier;this.type = type;}
}

同样,我们使用参数化的Class作为类型信息的挂钩。 调整后的Context现在使用参数化的Key而不是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 ) );}[...]
}

客户端将使用以下版本的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 );

尽管此代码片段有效,但实现仍有缺陷。 Key实现在Context#get用作查找参数。 使用用相同的标识符和类初始化的两个不同Key实例(一个实例与put一起使用,另一个实例与get一起使用)将在get上返回null 。 这不是我们想要的。

幸运的是,可以通过使用适当的equalsKey hashCode实现轻松解决此问题。 这使HashMap查找可以按预期工作。 最后,可以提供一种工厂创建密钥的方法,以最小化样板(与静态导入结合使用):

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

结论

'以集合API为例,泛型的正常使用将每个容器的类型参数限制为固定数量。 您可以通过将类型参数放在键而不是容器上来解决此限制。 您可以将Class对象用作此类类型安全的异构容器的键(Joshua Bloch,有效Java,第29项)。

鉴于这些结束语,除了祝您好运成功混合苹果和梨,别无他法……

翻译自: https://www.javacodegeeks.com/2015/03/how-to-map-distinct-value-types-using-java-generics.html

java 不同类型 映射

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/338179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux vim自动换行,VIM 的自动换行及自动折行设置

VIM 的自动换行及自动折行设置以 .vimrc 文件中的设置为例&#xff1a;" 自动换行是每行超过 n 个字的时候 vim 自动加上换行符" 需要注意的是&#xff0c;如果一个段落的首个单词很长&#xff0c;超出了自动换行设置的字符&#xff0c;" 这种情况下不会换行。&…

lambdas 排序_Java8 Lambdas:解释性能缺陷的排序

lambdas 排序与Peter Lawrey合作撰写 。 几天前&#xff0c;我对使用新的Java8声明式的排序性能提出了一个严重的问题。 在这里查看博客文章。 在那篇文章中&#xff0c;我仅指出了问题&#xff0c;但在这篇文章中&#xff0c;我将更深入地了解和解释问题的原因。 这将通过使用…

strncmp函数用法是什么

strncmp函数用法&#xff1a;函数原型int strcmp(char *str1,char * str2&#xff0c;int n)功能比较字符串str1和str2的前n个字符。头文件#include 返回值返回值&#xff1a;返回整数值&#xff1a;当str1<str2时&#xff0c;返回值<0&#xff1b; str1"str2时&…

cepl进程 Linux,Ubuntu下NS2-2.33安装过程

首先安装下列软件包# sudo apt-get install build-essential# sudo apt-get install tcl8.4 tcl8.4-dev tk8.4 tk8.4-dev# sduo apt-get install libxmu-dev libxmu-headers再下载NS2软件&#xff0c;(http://jaist.dl.sourceforge.net/sourceforge/nsnam/ns-allinone-2.33.tar…

分享10个适合初学者学习的C开源项目代码

1.WebbenchWebbench 是一个在 linux 下使用的非常简单的网站压测工具。它使用 fork ()模拟多个客户端同时访问我们设定的 URL&#xff0c;测试网站在压力下工作的性能&#xff0c;最多可以模拟 3 万个并发连接去测试网站的负载能力。Webbench 使用C语言编写&#xff0c; 代码实…

tomcat与tomee_Apache TomEE(和Tomcat)的自签名证书

tomcat与tomee可能在大多数Java EE项目中&#xff0c;您将拥有具有SSL支持&#xff08; https &#xff09;的部分或整个系统&#xff0c;因此浏览器和服务器可以通过安全连接进行通信。 这意味着在处理数据之前&#xff0c;已发送的数据已加密&#xff0c;传输并最终解密。 …

linux环境变量管理器,运维 - linux(ubuntu) 环境变量管理 (持续更新)

运维 - linux(ubuntu) 环境变量管理 (持续更新)注: 本教程以 Ubuntu16.04 操作, 请细看. 如果看完还不明白, 联系我, 我给你发红包.一, 查看环境变量:方法 1: 直接用 $PATH 命令:wafaubuntu:~$ $PATHbash:/home/king/bin:/home/king/.local/bin:/usr/local/sbin:/usr/local/bin…

C 线程的使用~(上)

C 11 之前&#xff0c;C 语言没有对并发编程提供语言级别的支持&#xff0c;这使得我们在编写可移植的并发程序时&#xff0c;存在诸多的不便。现在 C 11 中增加了线程以及线程相关的类&#xff0c;很方便地支持了并发编程&#xff0c;使得编写的多线程程序的可移植性得到了很大…

k8s中graphite_在Graphite中存储Hystrix的几个月历史指标

k8s中graphiteHystrix的杀手级功能之一是低延迟&#xff0c;数据密集和美观的仪表板 &#xff1a; 即使这只是Hystrix实际操作的副作用&#xff08;断路器&#xff0c;线程池&#xff0c;超时等&#xff09;&#xff0c;它也往往是最令人印象深刻的功能。 为了使其工作&#…

linux下gate版本管理,Linux安装使用GoldenGate

如何安装使用goldengate一.环境:OS&#xff1a;linux CentOS_Final_5.5(64bit)DB&#xff1a;oracle11gR2(单机模式)goldengate: ggs_Linux_x64_ora11g_64bit_v11_1_1_0_0_078.tar网络&#xff1a;局域网&#xff0c;源端IP 192.168.128.100 镜像端IP 192.168.128.101二&…

C语言中的“三字母词”坑了工程师

某软件工程师接盘了前同事的项目&#xff0c;进度一拖再拖&#xff0c;最后发现问题出现在如下代码&#xff1a;// 注释语句 ??/2a b c;请注意代码中的“??/”&#xff0c;就是这注释隐藏的很深&#xff0c;让项目一拖再拖。"??/"会被编译器当作 /&#xff0c…

guice 实例_使用Google Guice消除实例之间的歧义

guice 实例如果接口有多个实现&#xff0c;则Google guice提供了一种精巧的方法来选择目标实现。 我的示例基于Josh Long &#xff08; starbuxman &#xff09;的出色文章&#xff0c;内容涉及Spring提供的类似机制。 因此&#xff0c;请考虑一个名为MarketPlace的接口&#…

linux uboot 源码分析,UBoot源码分析1.pdf

UBoot源码分析1• UBoot源码解析(一)主要内容• 分析UBoot是如何引导Linux内核• UBoot源码的一阶段解析BootLoader概念• Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序&#xff0c;我们可以初始化硬件设备、建立内存空间的映射图&#xff0c;从而…

C 线程的使用~(下)

2.3 detach()detach() 函数的作用是进行线程分离&#xff0c;分离主线程和创建出的子线程。在线程分离之后&#xff0c;主线程退出也会一并销毁创建出的所有子线程&#xff0c;在主线程退出之前&#xff0c;它可以脱离主线程继续独立的运行&#xff0c;任务执行完毕之后&#x…

javafx有布局管理器吗_JavaFX技巧17:带有AnchorPane的动画工作台布局

javafx有布局管理器吗最近&#xff0c;我不得不为应用程序实现一个布局&#xff0c;其中可以根据用户是否登录来隐藏或显示菜单区和状态区&#xff0c;并通过滑入/滑出动画显示该区域。 以下视频显示了实际的布局&#xff1a; 过去&#xff0c;我可能会使用自定义控件和自定义…

linux双wan网关负载均衡,Csico2951路由器,如何做到双WAN口负载均衡?

Router2800(config-if)#ip addressY.Y.Y.Y 255.255.255.252(Y为移动地址)Router2800(config-if)#ip nat ouosideRouter2800(config-if)#no shutdownRouter (config)#ip dns serverRouter (config)#ip name-server A.A.A.A B.B.B.B(A为电信解析地址&#xff0c;B为移动解析地址)…

用c语言编写爱心的代码是什么

用c语言编写爱心的代码&#xff1a;输入完整代码如下&#xff1a;#include int main(void){float a,x,y;for(y1.5f; y>-1.5f; y-0.1f){for(x-1.5f; x<1.5f; x 0.05f){a x*x y*y-1;char ch a*a*a-x*x*y*y*y<0.0f?*: ; putchar(ch); }printf("\n");}retur…

c++ lambda 重载_您会后悔对Lambdas应用重载!

c lambda 重载编写好的API很难。 非常辛苦。 如果您希望用户喜欢您的API&#xff0c;则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡&#xff1a; 用处 易用性 向后兼容 前向兼容性 之前&#xff0c;在我们的文章&#xff1a; 如何设计良好的常规API中&#xf…

linux1到10累加,10个有趣的 Linux 命令

在终端工作是一件很有趣的事情。今天&#xff0c;我们将会列举一些有趣得为你带来欢笑的Linux命令。1. rev创建一个文件&#xff0c;在文件里面输入几个单词&#xff0c;rev命令会将你写的东西反转输出到控制台。# revSelection_002Selection_0012. fortune这个命令没有被默认安…

7个华为关于C语言的经典面试题

1、找错void test1(){ char string[10]; char* str1"0123456789"; strcpy(string, str1);}这里string数组越界&#xff0c;因为字符串长度为10&#xff0c;还有一个结束符’\0’。所以总共有11个字符长度。string数组大小为10&#xff0c;这里越界了。PS&am…