如何使用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来替换无界参数。 但是更重要的是,该实现不使用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

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

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

相关文章

php用正则去掉一些固定字符,用PHP正则表达式清除字符串的空白

我们经常会处理来自用户输入或从数据库中读取的数据&#xff0c;可能在你的字符串中有多余的空白或制表符&#xff0c;回车等。存储这些额外的字符是有点浪费空间的。如果您想要去掉字符串开始和结束的空白可以使用PHP内部函数trim() 。但是, 我们经常想完全清除空白。需要把开…

Windows上编译libpng

Windows上编译libpng 下载libpng 1.5.10并解压到[工作目录]/png/libpng-1.5.10 用CMake选择png/libpng-1.5.10目录并Configure&#xff1a; CMAKE_C_FLAGS_DEBUG/D_DEBUG /MTd /Zi /Ob0 /Od /RTC1 CMAKE_C_FLAGS_RELEASE/MT /O2 /Ob2 /D NDEBUG CMAKE_INSTALL_PREFIX[工作目录…

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

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

html和php文件怎么连接,html页面跟php文件连接的方法

html页面跟php文件连接的方法发布时间&#xff1a;2020-09-25 11:11:05来源&#xff1a;亿速云阅读&#xff1a;115作者&#xff1a;小新小编给大家分享一下html页面跟php文件连接的方法&#xff0c;相信大部分人都还不怎么了解&#xff0c;因此分享这篇文章给大家参考一下&…

java定义一个course类,求指教定义一个学生类 ,大学生小学生,定义一个选课接口...

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼interface XC{abstract String CourseName();abstract String CourseID();}abstract class Student{protected String Name;protected String Ban;protected String Sex;public Student(){}public Student(String Name,String Ban,…

NYOJ-----最少乘法次数

最少乘法次数 时间限制&#xff1a;1000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;3描述给你一个非零整数&#xff0c;让你求这个数的n次方&#xff0c;每次相乘的结果可以在后面使用&#xff0c;求至少需要多少次乘。如24&#xff1a;2*222&#xff08;第一次乘&a…

在Java 7或更早版本中使用Java 8 Lambda表达式

我认为没有人会拒绝Java 8引入的Lambda表达式的有用性。但是&#xff0c;许多项目都停留在Java 7甚至旧版本上。 升级可能既耗时又昂贵。 如果第三方组件与Java 8不兼容&#xff0c;则可能根本无法升级。 除此之外&#xff0c;整个Android平台都停留在Java 6和7上。 尽管如此…

php获得昨天零时的时间戳,php 获取时间今天明天昨天时间戳

echo "今天:".date("Y-m-d")."";echo "昨天:".date("Y-m-d",strtotime("-1 day")), "";echo "明天:".date("Y-m-d",strtotime("1 day")). "";echo "一周…

Zend Debugger 配置

到官网 http://www.zend.com/en/products/studio/downloads 下载 windows 版 Studio Web Debugger打开下载得到的压缩包&#xff0c;里面有一些文件夹列表&#xff08;4_3_x_comp &#xff0c; 4_4_x_comp &#xff0c; 5_0_x_comp &#xff0c; 5_2_x_comp &#xff0c; 5_2_x…

JavaFX技巧17:带有AnchorPane的动画工作台布局

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

php投票系统中各个文件的作用说明,PHP开发简单投票系统之投票页面功能模块(二)...

当完成前面的投票后&#xff0c;可以选择点击查看结果查看每个项目的总票数和所有项目的投票百分比。点击“查看结果”后程序会自动计算每个项目的票数和所占百分比。使用了隐藏表单属性隐藏域在页面中对于用户是不可见的&#xff0c;在表单中插入隐藏域的目的在于收集或发送信…

使用Spring Boot对REST URL进行集成测试

我们正在构建一个具有REST接口的Spring Boot应用程序&#xff0c;并且在某个时候我们想测试我们的REST接口&#xff0c;并在可能的情况下将此测试与常规单元测试集成。 一种方法是Autowire我们的REST控制器&#xff0c;并使用它来调用我们的端点。 但是&#xff0c;这不会完全融…

php最常用方法,php 常用方法

/*** 返回token参数* 参数 result 需要先urldecode*/function getToken($result) {$result urldecode ( $result ); // URL转码$Arr explode ( &, $result ); // 根据 & 符号拆分$temp array (); // 临时存放拆分的数组$myArray array (); // 待签名的数组// 循环构…

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

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

ref 和out 关键字

ref 和out 关键字 通过对CLR的学习&#xff0c;我们可以知道&#xff0c;CLR默认所有方法参数都是传值的。对于引用类型的对象&#xff0c;传递的是对象的引用&#xff08;指向对象的指针&#xff09;&#xff0c;被调用者拥有该对象的引用的拷贝&#xff0c;能够修改对象&…

php-cli下载,php-cli-color

一个简单的 PHP 命令行 cli 输出彩色的类库安装composer require wujunze/php-cli-color ~1.0使用getColoredString("Testing Colors class, this is purple string on yellow background.", "purple", "yellow") . PHP_EOL;echo $colors->ge…

删除递归建立的文件

使用php的eclipse导入项目时不小心导致文件夹创建出现了恐怖的递归&#xff0c;创建了一个超级长的文件夹。删除时出现无法删除&#xff0c;指定的文件名无效或太长&#xff0c;资源管理器&#xff0c;或者命令行下del都用了&#xff0c;还是搞不定。曾尝试着一个个的改文件名并…

您会后悔对Lambdas应用重载!

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

java 匿名对象有引用,封闭对象的引用通过匿名类java进行转义

我在实践中阅读Java并发性,下面的例子来自于此.我的问题是这个参考逃脱是什么意思&#xff1f;会有什么问题&#xff1f; .这个引用是如何从doSomething(e)中逃脱的.public class ThisEscape {public ThisEscape(EventSource source) {source.registerListener(new EventListen…

7.25第一次组队赛

Problem A UVA 11877 The Coco-Cola Store 直接输出n/2 1 #include <stdio.h>2 int main()3 {4 int n;5 while(~scanf("%d",&n) && n)6 {7 printf("%d\n",n/2);8 }9 return 0; 10 } 也可以模拟 1 #include <stdio.h>2 int main()…