解释型语言和编译型语言的区别_从泛型的使用情况看出你对语言的理解程度(2)...

709f16729d5b31268fa787f28ed25192.png

上篇我们提到: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 其实是没有限制的,没办法装门处理某一种特定类型啦。

好了,就到这里,不理解的朋友可以留言一起讨论哦~

关注公众号获取更多内容,有问题也可在公众号提问哦

9b0b09927d3ee4402523ddf9f130935f.png

强哥叨逼叨

叨逼叨编程、互联网的见解和新鲜事

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/551856.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

泸州职业技术学院计算机单招试题,2021年泸州职业技术学院单招英语考试模拟试题库...

2021年高职单招升学一对一咨询高职单招郭老师:15683905627(微信)单招考试考什么单招专业技能考试文化统考;文化统考科目包括语文、数学、英语、专业综合理论。单招考试形式&#xff1a;专业技能考试文化统考。专业技能考试形式和内容由各专业大类联考委确定。文化统考科目为语文…

showdoc如何创建文件夹_showDoc生成文档

1. 创建项目2. 获取api_key和api_token1. 打开设置2. 获取api_key和token3. 生成文档1. 先cd进入你的项目目录&#xff0c;命令行模式下输入&#xff1a;wget https://www.showdoc.cc/script/showdoc_api.sh下载完毕&#xff0c;编辑vi showdoc_api.sh脚本内容的前面有两个变量…

win7下安装python失败问题_win7下安装ipython失败

关键就是报错啊大哥,没个报错信息你让我们怎么帮你,天眼通吗?请看:提问的艺术(中文版)在网络世界里&#xff0c;当提出一个技术问题时&#xff0c;你能得到怎样的回答&#xff1f;这取决于挖出答案的难度&#xff0c; 同样取决于你提问的方法。本指南旨在帮助你提高发问技巧&a…

职称计算机技巧集锦,2014职称计算机考试《Excel》使用技巧集锦(4)

四、 数据分析和管理技巧1. 管理加载宏Excel包括各种特殊作用的加载宏&#xff0c;它们使用自定义的函数、向导、对话框和其他工具&#xff0c;扩充了工作表的基本功能。默认情况下&#xff0c;每个加载宏都配置为在第一次使用时安装&#xff0c;也就是说在第一次需要某个加载宏…

map集合的putall_Map.put和Map.putAll方法之间的区别?

当使用putAll而不是put时&#xff0c;我看到巨大的性能优势。 请参见下面的示例程序&#xff1a;公共类SampleTest {public static void main(final String[] args) {final Map testMap new HashMap<>();final Map testMap2 new HashMap<>();final LocalDateTime…

ubuntu20.04自带python版本_替换 ubuntu 自带的python版本

首先在这里下载你想用的各个版本的python&#xff0c;我用的是2.7.11&#xff1a; https://www.python.org/ftp/python/还是老样子&#xff1a; ./configure --> make --> make install接下来将你自带的版本替换成自己的版本&#xff1a; 先 which python 查看你的p…

物料编码是计算机系统对物料,物料编码是什么?

物料编码是唯一标识物料的代码,通常用字符串(定长或不定长)或数字表示.物料编码是计算机系统对物料的惟一识别代码.它用一组代码来代表一种物料.物料编码必须是惟一的,也就是,一种物料不能有多个物料编码,一个物料编码不能有多种物料.目录简介意义原则方法注意事项处理特点展开…

python requests get post_python+requests进行get、post方法接口测试

简介&#xff1a;Requests 是用Python语言编写&#xff0c;基于 urllib&#xff0c;采用 Apache2 Licensed 开源协议的 HTTP 库。它比 urllib 更加方便&#xff0c;可以节约我们大量的工作&#xff0c;完全满足 HTTP 测试需求。Requests 的哲学是以 PEP 20 的习语为中心开发的&…

python中config命令_【Python】 配置解析ConfigParser 命令行参数解析optparser

ConfigParserConfigParser包装了配置文件的读取和写入&#xff0c;使得python程序可以更加轻松操作配置文件了。这里的配置文件是指.ini的那种文件&#xff0c;基本格式如下[section_a]a_key1a_value1a_key2a_value2[section_b]b_key1b_value1b_key2b_value2b_key3 b_value3将一…

sata接口测试软件,如何查看电脑是否支持USB 3.0?Hwinfo32检测SATA端口的方法

Hwinfo32检测SATA端口有很多效用&#xff0c;其中我们查看该SATA是否支持USB 3.0就是一个判断&#xff0c;更多的信息是为了了解SATA目前已经可使用的和在使用的端口情况&#xff0c;具体方法可以查看下文中的方法进行判断。Hwinfo32检测SATA端口的方法&#xff1a;1、直接下载…

python中deepcopy函数_Python学习笔记函数之copy()和deepcopy()

随笔记录方便自己和同路人查阅。#------------------------------------------------我是可耻的分割线-------------------------------------------在处理列表和字典时&#xff0c;尽管传递引用常常是最方便的方法&#xff0c;但如果函数修改了传入的列表或字典&#xff0c;你…

常规计算机 符号键是,电脑键盘上的字母和符号都表示什么

键盘上的键可以根据功能划分为几个组&#xff1a;键入(字母数字)键。这些键包括与传统打字机上相同的字母、数字、标点符号和符号键。控制键。这些键可单独使用或者与其他键组合使用来执行某些操作。最常用的控制键是 Ctrl、Alt、Windows 徽标键 和 Esc。功能键。功能键用于执行…

mfc try catch 捕获并显示_你的异常捕获够优雅不?求你别只会try{...} catch{...}了

文章来源 | cnblogs.com/jurendage/p/11255197.html作者 | 巨人大哥软件开发过程中&#xff0c;不可避免的是需要处理各种异常&#xff0c;就我自己来说&#xff0c;至少有一半以上的时间都是在处理各种异常情况&#xff0c;所以代码中就会出现大量的try {...} catch {...} fin…

vue 修改对象的值视图没有发生改变_在vue中处理对象属性改变视图不更新问题? - echart...

...图等等&#xff0c;但是这些代码比较难写&#xff0c;因此我们通常会用借助echarts&#xff0c;那你知道如何使用echarts吗&#xff1f;这篇文章就和大家讲讲echarts的使用方法&#xff0c;有一定的参考价值&#xff0c;感兴趣的朋友可以看看。以饼状图为例&#xff0c;介绍…

五年级计算机教材内容,五年级计算机教学计划

五年级计算机教学计划教育结构不断发生变革&#xff0c;现代教育和教学理论主张对教学计划的结构实行改革。下面是小编为您整理的关于五年级计算机教学计划的相关资料&#xff0c;欢迎阅读&#xff01;五年级计算机教学计划 范例1一、教材分析选用的教材是&#xff0c;浙江摄影…

linux添加用户命令_为Linux的cp和mv命令添加进度条

cp和mv可能是大家日常中使用最多的Linux命令之一。但是有一个突出的问题是这两个命令都不会有任何提示信息&#xff0c;这在操作大文件时候只能干等。可能大家对此已经习以为常&#xff0c;但是其实上也有解决解决方法&#xff0c;本文我们就介绍一个Gnu Coreutils(cp和mv的源代…

date time 分开存储如何合并_关于TDateTime的TDate与TTime合并的问题 | 菲菲的家

遇到TDate与TTime合并为TDateTime的问题&#xff0c;想想应该很容易&#xff0c;没想到发现一个百思不得其解的问题&#xff0c;在这里跟大家分享并求教。比如有两个组件DateTimePicker1和DateTimePicker2&#xff0c;Kind属性分别为dtkDate和dtkTime&#xff0c;一个用来定义T…

unsigned long long 溢出 乘_Java整数相加溢出怎么办?Java8一步搞定

问题在之前刷题的时候遇见一个问题&#xff0c;需要解决int相加后怎么判断是否溢出&#xff0c;如果溢出就返回Integer.MAX_VALUE解决方案JDK8已经帮我们实现了Math下&#xff0c;不得不说这个方法是在StackOverflow找到了的&#xff0c;确实比国内一些论坛好多了~加法public s…

计算机模块word2003和2007,以Word2003的名义熟悉Word2007

相信很多朋友都已经用上了最新的Office2007办公套件&#xff0c;Office2007提供了全新的外观、全新的用户界面&#xff0c;用简单明了的单一机制取代了早期版本中的菜单、工具栏和大部分的任务窗格&#xff0c;从而使用户可以更高效、更容易地找到完成各种任务的合适功能&#…

golang python rpc_golang rpc的两种调用方法

golang的rpc有两种方法进行调用&#xff0c;一种是rpc例子中给的&#xff1a;package mainimport ("net/rpc""net/http""log""net""time")type Args struct {A, B int}type Arith intfunc (t *Arith) Multiply(args *Args, …