从泛型的使用情况看出你对语言的理解程度(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;  }  @Override  public 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.String  at 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 如:paperMoneycoinMoneyobjMoney 。也就实现了一个泛型的单例工厂。


上面代码的输出结果如下:


1101823143346114717123103054


《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:paperMoneycoinMoneyobjMoney 。如果不适用泛型,用上面的代码,那么这三个paperMoneycoinMoneyobjMoney 其实是没有限制的,没办法装门处理某一种特定类型啦。


好了,就到这里,为了在文字中显示代码中的特定变量,导致排版看起来有点奇怪。不过内容确实是干货。不理解的朋友可以留言一起讨论哦~

相关文章
|
8月前
|
安全 Java Kotlin
Kotlin泛型:灵活的类型参数化
Kotlin泛型:灵活的类型参数化
|
9月前
|
Rust 编译器
Rust 泛型
Rust 泛型
70 1
|
4月前
|
JavaScript 前端开发 程序员
动态语言、静态语言、强类型语言、弱类型语言的区别
动态语言、静态语言、强类型语言、弱类型语言的区别
|
8月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
9月前
|
存储 JavaScript Java
Java编程基础 - 泛型
Java编程基础 - 泛型
47 0
|
9月前
|
存储 安全 算法
C# 泛型:类型参数化的强大工具
【1月更文挑战第7天】本文将深入探讨C#语言中的泛型编程,包括泛型的定义、用途、优势以及实际应用。通过类型参数化,泛型允许开发者编写更加灵活且可重用的代码,同时提高程序的类型安全性和性能。本文将通过示例代码和详细解释,帮助读者更好地理解泛型在C#中的重要性和实用性。
|
存储 JavaScript 安全
TypeScript中的泛型:深入理解泛型的概念和应用场景
TypeScript中的泛型:深入理解泛型的概念和应用场景
|
存储 安全 Java
Java泛型详解:为什么使用泛型?如何使用泛型?
Java泛型详解:为什么使用泛型?如何使用泛型?
180 0
|
Rust 编译器
Rust之泛型特化
Rust之泛型特化
|
编译器 C++
多态(C++语言实现)
多态(C++语言实现)