百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

深入浅出泛型,框架设计的基础 什么是框架思路

yuyutoo 2024-10-20 13:08 4 浏览 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 等开源框架都使用了大量泛型来实现通用功能。在开发中,既要掌握开源框架的泛型使用方法,也要熟悉泛型的定义和使用,并对泛型有足够深刻的理解。如果你要开发自己的框架,那么泛型一定是标配,需要好好理解和掌握。

相关推荐

YAML配置文件简介及使用(yaml 配置)

简介YAML是"YAMLAin'taMarkupLanguage"(YAML不是一种标记语言)的缩写。相比JSON格式的方便。...

教你如何解决最常见的58种网络故障排除方法

1.故障现象:网络适配器(网卡)设置与计算机资源有冲突。分析、排除:通过调整网卡资源中的IRQ和I/O值来避开与计算机其它资源的冲突。有些情况还需要通过设置主板的跳线来调整与其它资源的冲突。2.故障现...

一分钟带你了解服务器网卡(服务器网卡怎么用)

今天小编和大家聊一下服务器的网卡。什么是网卡?简单说网卡就是计算机与局域网互连的设备。计算机主要通过网卡接入网络。网卡又称为网络适配器或网络接口卡NIC(NetworkinterfaceCard)...

linux文件之ssh配置文件的含义与作用

ssh远程登录命令是操作系统(包括linux和window系统)下常用的操作命令,可以帮助用户,远程登录服务器系统,查看,操作系统相关信息。linux系统对于ssh命令有专门保存其相关配置的目录和文件...

Cilium 官方文档翻译 - IPAM(二)Kubernetes Host模式

KubernetesHostScopeciliumIPAM的kuberneteshost-scope模式通过选项ipam:kubernetes开启,将集群IP地址分配委托给每个独立的节点,并...

域名劫持跳转,域名劫持跳转的解决办法只需5步

简单来说,域名劫持就是把原本准备访问某网站的用户,在不知不觉中,劫持到仿冒的网站上,例如用户准备访问某家知名品牌的网上商店,黑客就可以通过域名劫持的手段,把其带到假的网上商店,同时收集用户的ID信息和...

Linux基本命令(linux基本命令总结)

...

Linux 磁盘和文件系统管理(linux磁盘管理fdisk)

1检测并确认新硬盘...

windows host文件怎么恢复?局域网访问全靠这些!

windowshost文件怎么恢复?windowshost文件是常用网址域名及其相应IP地址建立一个关联文件,通过这个host文件配置域名和IP的映射关系,以提高域名解析的速度,方便局域网用户使用...

Nginx配置文件详解与优化建议(nginx 配置详解)

1、概述今天来详解一下Nginx的配置文件,以及给出一些配置建议,希望能对大家有所帮助。...

Mac电脑hosts文件锁定,如何修改hosts文件权限

有时候我们需要修改hosts文件,但是网上很多教程都行不通,使用sudo命令也不行。其实有一个很简单的方法。打开终端命令行,使用如下命令即可:sudochflags-hvnoschg/etc/...

windows电脑如何修改hosts文件?(windows 修改hosts文件)

先来简单说下电脑host的作用hosts文件的作用:hosts文件是一个用于储存计算机网络中各节点信息的计算机文件;作用是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中...

Vigilante恶意软件行为怪异:修改Hosts文件以阻止受害者访问盗版网站

Sophos刚刚报道了一款名叫Vigilante的恶意软件,但其行为却让许多受害者感到不解。与其它专注于偷密码、搞破坏、或勒索赎金的恶意软件不同,Vigilante会通过修改Hosts文件...

hosts文件无法修改几种现象和解决方法

第一种、hosts文件修改完不是直接保存而是弹出另存为窗口解决:1、右击hosts文件——属性——把“只读”前面勾去掉。第二种、打开hosts文件时提示“你没有权限打开该文件,请向文件的所有者或管理员...

hosts文件位置在哪里,教你hosts文件位置在哪里

Hosts是一个没有扩展名的系统文件,其基本作用就是将一些常用的网址域名与其对应的IP地址建立一个关联"数据库",当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的I...

取消回复欢迎 发表评论: