上篇我们提到: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 其实是没有限制的,没办法装门处理某一种特定类型啦。
好了,就到这里,不理解的朋友可以留言一起讨论哦~
关注公众号获取更多内容,有问题也可在公众号提问哦
强哥叨逼叨
叨逼叨编程、互联网的见解和新鲜事