解释型语言和编译型语言的区别_从泛型的使用情况看出你对语言的理解程度(2)...
上篇我們提到:Java中的泛型是不可變的,可以通過<? extends E>實現了泛型的協變,<? super E>實現泛型的逆變。從泛型的使用情況看出你對語言的理解程度(1)
今天我們來講講泛型單例工廠,在之前的推文中也有推送過單例模式的實現,但是不是用泛型實現的,這次我們先講一個泛型單例的例子,然后再講泛型單例工廠會更好理解一些。
首先定義一個泛型接口,里面包含一個apply方法:
public interface Money<T> { T apply(T args); }然后我們需要一種String類型的數據進行apply操作的單例對象。按慣性思維,我們會這么實現。
public class PaperMoney implements Money<String>{private static PaperMoney paperInstance = new PaperMoney();private PaperMoney(){} public PaperMoney getInstance() {return paperInstance;}@Overridepublic String apply(String args) { return args;} }在工程需求復雜的情況下,這種模式沒有考慮到代碼的擴展性,當未來需要對Integer對象數據進行apply操作呢?再寫一個類嗎?這明顯是過于麻煩了。這時候就需要再結合工廠的設計模式了。
泛型單例工廠
在工廠中,會產生不同參數類型的Money對象,在結合static的特性,實現復用生成的Money對象。
public class MoneyImp {//Money是類似函數式接口實現private static Money<Object> IDENTITY_FUNCTION = arg -> String.valueOf(arg.hashCode());@SuppressWarnings("unchecked")public static <T> Money<T> getMoneyInstance() {return (Money<T>) IDENTITY_FUNCTION;}public static void main(String[] args) {String[] strings = { "one", "five", "ten" };Money<String> paperMoney = getMoneyInstance();for (String s : strings) {System.out.println(paperMoney.apply(s));}Integer[] numbers = { 1, 2, 3 };Money<Integer> coinMoney = getMoneyInstance();for (Integer n : numbers)System.out.println(coinMoney.apply(n));JSONObject[] jsonObjects = {JSON.parseObject("{hah:1}")};Money<JSONObject> objMoney = getMoneyInstance();for (JSONObject n : jsonObjects)System.out.println(objMoney.apply(n));} }上面的代碼中Money接口其實是仿照Java8中的Function函數式接口定義的,或者說是仿照Function接口的子類接口:UnaryOperator。關于Function函數式接口可以看今天的另一篇推文介紹。簡單的說,Function就是實現了這樣的一個公式:y=f(x),而UnaryOperator實現的是:x=f(x)。
也就是說IDENTITY_FUNCTION 其實實現的是x=String.valueOf(f(x))的功能。在這里我們將傳進來的x獲取其hashCode,然后轉換成字符串形式返回回去。同時由于IDENTITY_FUNCTION 是一個Money<Object> 。用Object接收返回的String(f(x))所以是合理的(父類引用指向子類對象)所以,還是可以把IDENTITY_FUNCTION 看作是x=f(x)。
arg -> String.valueOf(arg.hashCode());這個函數由于hashCode() 屬于Object,所以任何類調用都不會報錯。否則很容易報錯,這里要多注意。
如果沒有getMoneyInstance() 方法,而是直接把IDENTITY_FUNCTION 賦值給paperMoney 即:
Money<String> paperMoney = IDENTITY_FUNCTION();
則會報編譯錯誤:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat com.ltjh.imports.job.MoneyImp.main(MoneyImp.java:27)這個很明顯,因為泛型是不可以協變的。所以我們需要一個靜態工廠方法:getMoneyInstance()。
所有重點來了:
getMoneyInstance() 方法的作用,則是作為一個靜態工廠方法用于獲取我們編寫的IDENTITY_FUNCTION 函數。代碼中由于對IDENTITY_FUNCTION 進行了強制類型轉換return (Money<T>) IDENTITY_FUNCTION; 所以需要添加unchecked 轉換警告,因為編譯器并不知道Money<Object> 的每個都是Money<T> ,但是因為IDENTITY_FUNCTION 表示的是x=f(x),所以我們可以確定返回的就是Money<T> 。這是類型安全的。一旦我們這樣做了,代碼編譯就不會出現錯誤或警告。
于是,我們就可通過getMoneyInstance() 獲取到處理特定類型的Money 如:paperMoney 、coinMoney 和 objMoney 。也就實現了一個泛型的單例工廠。
上面代碼的輸出結果如下:
110182 3143346 114717 1 2 3 103054《Effective Java》中對泛型單例工廠的描述如下:
有時,你需要創建一個對象,該對象是不可變的,但適用于許多不同類型。因為泛型是由擦除實現的,所以你可以為所有需要的類型參數化使用單個對象,但是你需要編寫一個靜態工廠方法,為每個請求的類型參數化重復分配對象。這種模式稱為泛型單例工廠,可用于函數對象,如 Collections.reverseOrder,偶爾也用于集合,如 Collections.emptySet。
最后再提一點關于擦除的,由于Java泛型是由擦除實現的,所以,其實上面的代碼在便后后類似于這樣:
public class MoneyImp {//Money是類似函數式接口實現private static Money IDENTITY_FUNCTION = arg -> String.valueOf(arg.hashCode());@SuppressWarnings("unchecked")public static Money getMoneyInstance() {return IDENTITY_FUNCTION;}public static void main(String[] args) {String[] strings = { "one", "five", "ten" };Money paperMoney = getMoneyInstance();for (String s : strings) {System.out.println(paperMoney.apply(s));}Integer[] numbers = { 1, 2, 3 };Money coinMoney = getMoneyInstance();for (Integer n : numbers)System.out.println(coinMoney.apply(n));JSONObject[] jsonObjects = {JSON.parseObject("{hah:1}")};Money objMoney = getMoneyInstance();for (JSONObject n : jsonObjects)System.out.println(objMoney.apply(n));} }運行結果和前面的一樣且不報錯。那我們為什么還這么費勁用個泛型搞得云里霧里的呢?因為我們想要獲取到專門處理某一種類型的Money:paperMoney 、coinMoney 、objMoney 。如果不適用泛型,用上面的代碼,那么這三個paperMoney 、coinMoney 、objMoney 其實是沒有限制的,沒辦法裝門處理某一種特定類型啦。
好了,就到這里,不理解的朋友可以留言一起討論哦~
關注公眾號獲取更多內容,有問題也可在公眾號提問哦
強哥叨逼叨
叨逼叨編程、互聯網的見解和新鮮事
總結
以上是生活随笔為你收集整理的解释型语言和编译型语言的区别_从泛型的使用情况看出你对语言的理解程度(2)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php fuzzy,模糊C均值聚类算法(
- 下一篇: mysql 占用swap_查看swap占