深入浅出泛型,框架设计的基础 什么是框架思路
yuyutoo 2024-10-20 13:08 6 浏览 0 评论
泛型在 Java 5 出现,实现了参数化类型,主要作用是使得类或接口更加通用。比如 Java 中的容器类,通过泛型实现了对各种类型的兼容,成为极其通用的类库。如果我们要设计自己的框架,泛型基本上已经算是标配了。
类使用泛型
在类型上使用泛型,非常简单,只需要使用 <> 声明类型即可。如下 TestDO 类型声明了泛型 T,并使用 T 声明了字段 value。这样做的好处便是,TestDO 可以承载各种各样的数据,而不用定义具体的类型。这也说明了,泛型不仅可以用于类型,也可以用于字段或变量。
Java 在运行时对泛型进行擦除,运行时所有的泛型类型都退化为 Object 类型。这样的设计主要是为了保证对 Java 5 之前的代码进行兼容。尽管如此,使用泛型后,在编译阶段仍会对类型进行检查,确保使用泛型的地方都是类型正确的。
@Getter
@Setter
public
classTestDO{Tvalue;}
如下代码中,SubTestDO 传递给父类的具体类型为默认类型 Object 类型,而SubTestDOSimple 传递给父类的具体类型则是 String 类型。这两个子类都对泛型做了具体化,是泛型类型的常规使用。
/** 无泛型,即为 Object */
classSubTestDOextendsTestDO{}
/** 继承时指定父类实际类型 */
classSubTestDOSimpleextendsTestDO{}
在定义 TestDO 的子类时,子类也可以使用泛型。子类的泛型可以独自使用,也可以传递给父类。如 SubTestDOTrans 定义了泛型 T 并把 T 传递给父类,而 SubTestDOComp 定义了泛型 T 要求必须是 HashMap 的子类,但传递给父类的泛型变成了 List。
/** 继承时传递泛型 */classSubTestDOTransextendsTestDO{}
/** 继承时传递复杂类型 */
publicclassSubTestDOCompextendsSubTestDOTrans>{}
接口使用泛型可以定义适用于各种场景的通用接口,在设计模式中具有重要作用。如工厂模式就可以实现根据返回值类型自动转型,涉及到方法的泛型,在后文给出解释。
如下代码中,IGeneric 接口使用了泛型,并用泛型定义了方法。如此定义后,IGeneric 接口成为了一个通用接口,方法的入参类型与实现类或子接口对接口的定义保持一致,大大提高了通用性。接口可以继承,在继承中泛型可以有形式的变化,并定义新的泛型。
/** 普通泛型接口,泛型 T */
publicinterfaceIGeneric{publicvoidtest(Tt);}
ISubGenericSimple 接口在继承接口时,直接确定了泛型的类型为 String 类型,ISubGenericSimple 接口退化为一个普通接口,子接口或实现类不再需要对泛型进行处理。这是通过子接口对泛型进行具体化,也是架构设计中的常用手段。
/** 继承时指定泛型 */
interfaceISubGenericSimpleextendsIGeneric{}
ISubGenericTrans 接口同样使用了泛型,并把泛型传递给父接口。这种一般用于父接口的扩展,在子接口中可以定义一些特有方法,同时又完全兼容父接口的引用。
/** 继承时传递泛型 */
interfaceISubGenericTransextendsIGeneric{}
ISubGenericSubType 接口在泛型定义中,明确声明了泛型类型的范围必须是 TestDO 及其子类,这也是对父接口的一种退化,缩小了泛型的范围。这种情况一般用于比较复杂的接口体系,父接口完成顶层定义,多个子接口进行类型具体化实现分层设计。
/** 继承时指定泛型为特定类型的子类 */
interfaceISubGenericSubTypeextendsIGeneric{}
ISubGenericCompType 接口使用了一个相对复杂的泛型定义。子接口定义了泛型 T,而传递给父接口的泛型变成了 TestDO。这种接口继承方式在大型软件架构中有比较广泛的应用,通俗理解为父接口使用容器但元素类型为泛型,而子接口定义容器的元素类型,在组件化继承体系有比较明确的应用。这样的继承关系,可以确保父接口完成容器的操作方法而不用关心具体元素类型,子接口可以有少许定制逻辑甚至可以不定义方法。
/** 继承时指定复杂的泛型类型 */
interfaceISubGenericCompTypeextendsIGeneric>{}
方法使用泛型
方法也可以使用泛型,并且有很大的现实意义,在工厂模式、建造器模式中都可以使用泛型方法。
toString 方法定义了一个简单的泛型方法。在访问权限和返回值类型之间使用 声明泛型类型为 T,然后把入参类型设置为 T。在这个泛型方法中,入参类型可以是任何类型,这个通用方法可以在任何场景下使用。
/** 泛型 T 仅用于入参 */
staticStringtoString(Tt){returnnull;}
getInstance 方法不但在入参中使用了泛型,也把这个泛型作为返回值的类型。类似 getInstance 方法的定义有很多,比如 Spring 的 getBean 方法就重载了这种形式的应用。在确保方法通用性的基础上,也保证了返回值类型与入参的一致性。
/** 返回 T 类型 */
staticTgetInstance(Classclazz)throwsException{returnclazz.newInstance();}
getTestDO 方法是另一种形式的泛型方法,这里定义了返回值的泛型必须是 TestDO 及其子类。这种泛型方法常用于继承体系明确,需要对类型进行向下转型的场景。在方法内部需要对返回值进行强制转型,如果类型不匹配便会抛出类型转换异常,因此要求调用者必须了解方法的定义和使用要求。
/** 返回 TestDO 的子类类型 */
staticTgetTestDO(){return(T)newSubTestDOTrans<>();}
下面 getTestDO 的重载方法对泛型做了一定程度的退化,虽然定义了泛型 T,但这个泛型仅仅是容器的元素类型,方法的返回值是 TestDO。这种类型的泛型方法可用于复杂对象的组装,比上面的重载方法更安全。
staticTestDOgetTestDO(Tv){TestDOtestDO=newTestDO<>();testDO.setValue(v);returntestDO;}
泛型擦除
前面提到过 Java 会在运行时对泛型进行擦除,也就是抹去泛型信息,全部变为 Object 类型。因此,在运行时,TestDO 与 TestDO 在类型上并没有多大区别。虽然实际存储的 value 对象的真正类型是不同的,但是都会当做 Object 进行处理。也是因为擦除,引用类型对象时,我们可以使用 TestDO.class 却不能使用 TestDO.class。
泛型通配
前面的泛型都明确了泛型的类型为 T,并使用 T 定义字段、参数和返回值。在对具体类型依赖不那么强烈的情况下,比如,我们仅仅是要求类型是某个类型的子类即可,最终处理时使用父类类型而不依赖于子类类型,则可以使用通配符 ? 来定义泛型。
在如下代码中,set1 的元素类型为 TestDO 的子类。我们只关心 Set 中的元素是 TestDO 类型即可,并不关心实际元素是 SubTestDOSimple 还是 SubTestDOComp。而 set2 的元素类型可以是任何类型。
Setset1=newHashSet<>();
Setset2=newHashSet<>();
泛型逆变
前面提到的泛型定义用到了 extends 来确定泛型的边界,要求其必须是某个类型的子类。实际上,泛型还支持向上确定边界,也就是泛型为某个类型的父类。在如下代码中,定义了 Set 的元素类型为 SubTestDOComp 的父类。
SettestDOs=newHashSet<>();
使用泛型创建类型安全的分层结构
在架构设计中,常常涉及复杂的数据结构与设计分层,使用泛型对类型进行定义和约束,可以确保抽象层的设计有足够的通用性,同时最终产生的具体类型依然保持正确的数据类型。
比如在一个架构设计中,可能会分为抽象层、基础层、实现层三个层次。
在抽象层定义极度抽象的业务流程,需要这里的类型定义都具有较好的通用性,这时可以大量使用泛型和抽象类,只定义流程而不涉及具体类型。
在基础层定义一些基础服务,可能会在 core 层类型的基础上做一定程度的扩展,封装出多个适用范围不同的业务处理流程。在这一层,也会使用泛型,但会更加具体。
而在实现层不会再使用泛型,而是给出真正的类型,并实现具体的逻辑。
/** 抽象层,指定泛型 K,V */
abstractclassAbstractGenericImplimplementsIGeneric>{publicMapmap=newHashMap<>();}
/** 基础层,继承 AbstractGenericImpl */
classBasicGenericImplextendsAbstractGenericImpl>{@Overridepublicvoidtest(Map>data){map.putAll(data);}}
/** 实现层,继承 BasicGenericImpl */
publicclassComplexGenericImplextendsBasicGenericImpl{@Overridepublicvoidtest(Map>data){super.test(data);}}
总结
泛型在当今时代的 Java 开发中已经被广泛应用,如 Spring、MyBatis、Dubbo 等开源框架都使用了大量泛型来实现通用功能。在开发中,既要掌握开源框架的泛型使用方法,也要熟悉泛型的定义和使用,并对泛型有足够深刻的理解。如果你要开发自己的框架,那么泛型一定是标配,需要好好理解和掌握。
相关推荐
- Docker部署 chatgpt-web-midjourney-proxy:开启一站式AI与绘图
-
ChatGPT和Midjourney的结合无疑是创新性的突破。ChatGPT作为强大的语言模型,能够为用户提供智能的对话和文本生成服务。而Midjourney则以其出色的绘画能力,能够根据...
- Cacti监控服务器配置教程(基于CentOS+Nginx+MySQL+PHP环境搭建)
-
具体案例:局域网内有两台主机,一台Linux、一台Windows,现在需要配置一台Cacti监控服务器对这两台主机进行监控...
- 那些少为人知却非常有意思好用的 Chrome 扩展
-
ChromeWebStoreSessionManager要睡觉了,还有网页没看完怎么办?等明天点开歷史记录重新加载?No!有这个保存当前打开的链接,下回直接打开一串网址,好顶赞有木有!!!chr...
- 分享一款轻量级 HTTP(S) 代理 TinyProxy
-
概述众所周知,我们常用的Web服务器Nginx/Apache都可以很方便的用来做为正向或反向代理服务器使用。但是它们都并不支持HTTPS的正向代理。Nginx做为正向代理不支持HT...
- 深入理解 WebSecurityConfigurerAdapter「源码篇」
-
我们继续来撸SpringSecurity源码,今天来撸一个非常重要的WebSecurityConfigurerAdapter。我们的自定义都是继承自WebSecurityConfigurer...
- RPC、Web Service等几种远程监控通信方式对比
-
几种远程监控通信方式的介绍一.RPCRPC使用C/S方式,采用http协议,发送请求到服务器,等待服务器返回结果。这个请求包括一个参数集和一个文本集,通常形成“classname.meth...
- 老酒好喝,5G时代数据中心柴油发电机组以GPRS方式接入动环监控
-
背景:随着手机的普及,电信运营商基站越建越多,网络覆盖范围也越来越广,基本上随时随地都能通过运营商的网络上网冲浪,这给我们传统的通过有线方式实现的动环监控也提带来了新的启发:对于不具备有线传输条件的的...
- 为了春节红包大战,微信做了一次成功的预热!
-
经过上午的预告,微信在今天下午17:00正式推出了微信红包新玩法——红包照片。微信用户在朋友圈点击照片发送按钮,会看到“红包照片”选项,用户在选择发布照片之后,这张照片将被模糊处理后,再发送到朋友...
- Proxy-Lite实战:3步部署+2个案例,轻松掌握轻量级网页自动化
-
大家好,我是何三,80后老猿,独立开发者一、Proxy-Lite模型概述...
- 会Python?那么你一定要试一试mitmproxy
-
mitmproxy是一款工具,也可以说是python的一个包,使用这个工具可以在命令行上进行抓包(现在也可以在web页面上查看上抓的数据包了),还可以对所抓到的包进行脚本处理,非常有用。和...
- 十大Web安全扫描工具,你知道哪些?
-
初入门时,喜欢将目标站点直接丢扫描器,慢慢等扫描结果,极度依赖Web扫描器;而有一些漏洞高手,善于运用运用各种工具但并不依赖工具,经常可以找到扫描工具发现不了的漏洞。一款好用的Web扫描器对于白帽子来...
- 鸿蒙5网页开发神器 ArkWeb:让 Web 和原生手拉手跳舞
-
你有没有想过,在鸿蒙应用里既能用原生代码写高性能界面,又能直接塞进一个网页?这听起来有点像把汉堡和披萨拼在一起,但ArkWeb(方舟Web)真的做到了!今天咱们就聊聊这个神奇的工具,看看它如何让...
- MapReduce过程详解及其性能优化(详细)
-
从JVM的角度看Map和ReduceMap阶段包括:第一读数据:从HDFS读取数据1、问题:读取数据产生多少个Mapper??Mapper数据过大的话,会产生大量的小文件,由于Mapper是基于虚拟...
- 大数据平台建设需要掌握的14个知识
-
Q1、大数据分析中的实时推荐是如何实现的?@rein07某证券系统架构师:实时推荐需要使用实时处理框架结合推荐算法,从而做到对数据的实时处理和推荐。实时处理框架有Storm、Flink、Spark...
- HDFS可视化管理系统设计与实现(hdfs的可靠性设计,主要依靠哪些机制来实现)
-
摘要:Hadoop工具核心模块包括分布式文件系统(HadoopDistributedFileSystem,HDFS)和分布式编程模型MapReduce,其中HDFS是Hadoop数据存储处理的...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
推荐7个模板代码和其他游戏源码下载的网址
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- Docker部署 chatgpt-web-midjourney-proxy:开启一站式AI与绘图
- Cacti监控服务器配置教程(基于CentOS+Nginx+MySQL+PHP环境搭建)
- 那些少为人知却非常有意思好用的 Chrome 扩展
- 分享一款轻量级 HTTP(S) 代理 TinyProxy
- 深入理解 WebSecurityConfigurerAdapter「源码篇」
- RPC、Web Service等几种远程监控通信方式对比
- 老酒好喝,5G时代数据中心柴油发电机组以GPRS方式接入动环监控
- 为了春节红包大战,微信做了一次成功的预热!
- Proxy-Lite实战:3步部署+2个案例,轻松掌握轻量级网页自动化
- 会Python?那么你一定要试一试mitmproxy
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)