日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

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

發布時間:2023/12/3 java 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何使用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)。 這意味著您可以通過類型安全的方式存儲諸如Runnable或Runnable[]類的值類型,但不能存儲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 。 這不是我們想要的。

幸運的是,可以通過使用適當的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

總結

以上是生活随笔為你收集整理的如何使用Java泛型映射不同的值类型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。