一级域名网站多少钱,浮雕模东莞网站建设,wordpress优化主题,营销机构代码查询有时#xff0c;一般的开发人员会遇到这样的情况#xff0c;即他必须在特定容器内映射任意类型的值。 但是#xff0c;Java集合API仅提供与容器相关的参数化。 例如#xff0c;这将HashMap的类型安全使用限制为单个值类型。 但是#xff0c;如果您想混合苹果和梨怎么办一般的开发人员会遇到这样的情况即他必须在特定容器内映射任意类型的值。 但是Java集合API仅提供与容器相关的参数化。 例如这将HashMap的类型安全使用限制为单个值类型。 但是如果您想混合苹果和梨怎么办 幸运的是有一个简单的设计模式允许使用Java泛型映射不同的值类型 约书亚·布洛赫Joshua Bloch在他的著作《 有效的Java》 第二版第29项中将其描述为类型安全的异构容器 。 最近在有关该主题的一些不太合适的解决方案中遇到了麻烦这使我有了解释问题域的想法并在这篇文章中详细介绍了一些实现方面。 使用Java泛型映射不同的值类型 出于示例考虑您必须提供某种类型的应用程序上下文该上下文允许将任意类型的值绑定到某些键。 使用由HashMap支持的String键的简单非类型安全实现可能如下所示 public class Context {private final MapString,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, ClassT valueType ) {values.put( key, value );}public T T get( String key, ClassT 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 MapClass?, Object values new HashMap();public T void put( ClassT key, T value ) {values.put( key, value );}public T T get( ClassT 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( ClassT key, T value ) {values.put( key, key.cast( value ) );
} 第二个限制是该模式不能在不可更改的类型上使用请参阅第25条有效的Java。 这意味着您可以通过类型安全的方式存储诸如Runnable或Runnable[]类的值类型但不能存储ListRunnable 。 这是因为ListRunnable没有特定的类对象。 所有参数化类型都引用相同的List.class对象。 因此Bloch指出对于这种限制没有令人满意的解决方法。 但是如果您需要存储两个相同值类型的条目怎么办 虽然可以想象仅将新类型的扩展名存储在类型安全的容器中但这听起来并不是最佳的设计决策。 使用自定义键实现可能是更好的方法。 多个相同类型的容器条目 为了能够存储相同类型的多个容器条目我们可以将Context类更改为使用自定义键。 这样的键必须提供类型安全行为所需的类型信息以及用于区分实际值对象的标识符。 使用String实例作为标识符的朴素键实现可能如下所示 public class KeyT {final String identifier;final ClassT type;public Key( String identifier, ClassT type ) {this.identifier identifier;this.type type;}
} 同样我们使用参数化的Class作为类型信息的挂钩。 调整后的Context现在使用参数化的Key而不是Class public class Context {private final MapKey?, Object values new HashMap();public T void put( KeyT key, T value ) {values.put( key, value );}public T T get( KeyT key ) {return key.type.cast( values.get( key ) );}[...]
} 客户端将使用以下版本的Context Context context new Context();Runnable runnable1 ...
KeyRunnable key1 new Key( id1, Runnable.class );
context.put( key1, runnable1 );Runnable runnable2 ...
KeyRunnable 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 。 这不是我们想要的。 幸运的是可以通过使用适当的equals和Key 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