从泛型的使用情况看出你对语言的理解程度(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 其实是没有限制的,没办法装门处理某一种特定类型啦。


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

相关文章
|
1天前
|
数据采集 人工智能 安全
|
10天前
|
云安全 监控 安全
|
2天前
|
自然语言处理 API
万相 Wan2.6 全新升级发布!人人都能当导演的时代来了
通义万相2.6全新升级,支持文生图、图生视频、文生视频,打造电影级创作体验。智能分镜、角色扮演、音画同步,让创意一键成片,大众也能轻松制作高质量短视频。
906 150
|
15天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1643 8
|
6天前
|
人工智能 前端开发 文件存储
星哥带你玩飞牛NAS-12:开源笔记的进化之路,效率玩家的新选择
星哥带你玩转飞牛NAS,部署开源笔记TriliumNext!支持树状知识库、多端同步、AI摘要与代码高亮,数据自主可控,打造个人“第二大脑”。高效玩家的新选择,轻松搭建专属知识管理体系。
364 152
|
7天前
|
人工智能 自然语言处理 API
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸
一句话生成拓扑图!next-ai-draw-io 结合 AI 与 Draw.io,通过自然语言秒出架构图,支持私有部署、免费大模型接口,彻底解放生产力,绘图效率直接爆炸。
601 152
|
9天前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
565 13
|
2天前
|
编解码 人工智能 机器人
通义万相2.6,模型使用指南
智能分镜 | 多镜头叙事 | 支持15秒视频生成 | 高品质声音生成 | 多人稳定对话