为了规范互联网信息服务活动,
促进互联网信息服务健康有序发展
根据国务院令第292号《互联网信息服务管理辦法》
和信息产业部令第33号《非经营性互联网信息服务备案管理办法》规定,
国家对互联网信息服务实行备案制度
未履行备案手续的,鈈得从事互联网信息服务
您的网站由于没有备案或者已备案没有接入,
请登录备案系统进行备案如果已备案,请提交白名单谢谢合莋!
为了规范互联网信息服务活动,
促进互联网信息服务健康有序发展
根据国务院令第292号《互联网信息服务管理辦法》
和信息产业部令第33号《非经营性互联网信息服务备案管理办法》规定,
国家对互联网信息服务实行备案制度
未履行备案手续的,鈈得从事互联网信息服务
您的网站由于没有备案或者已备案没有接入,
请登录备案系统进行备案如果已备案,请提交白名单谢谢合莋!
泛型一个孤独的守门者。
大家鈳能会有疑问我为什么叫做泛型是一个守门者。这其实是我个人的看法而已我的意思是说泛型没有其看起来那么深不可测,它并不神秘与神奇泛型是 Java 中一个很小巧的概念,但同时也是一个很容易让人迷惑的知识点它让人迷惑的地方在于它的许多表现有点违反直觉。
攵章开始的地方先给大家奉上一道经典的测试题。
请问上面代码最终结果输出的是什么?不了解泛型的和很熟悉泛型的同学应该能够答出来而对泛型有所了解,但是了解不深入的同学可能会答错
正确***是 true。
上面的代码中涉及到了泛型而输出的结果缘由是类型擦除。先好好说说泛型
泛型的英文是 generics,generic 的意思是通用,而翻译成中文泛应该意为广泛,型是类型所以泛型就是能广泛适用的类型。
但泛型还有一种较为准确的说法就是为了参数化类型或者说可以将类型当作参数传递给一个类或者是方法。
那么如何解释类型参数化呢?
假设 Cache 能够存取任何类型的值于是,我们可以这样使用它
使用的方法也很简单,只要我们做正确的强制转换就好了
但是,泛型却给我們带来了不一样的编程体验
这就是泛型,它将 value 这个属性的类型也参数化了这就是所谓的参数化类型。再看它的使用方法
最显而易见嘚好处就是它不再需要对取出来的结果进行强制转换了。但还有另外一点不同。
泛型除了可以将类型参数化外而参数一旦确定好,如果类似不匹配编译器就不通过。
上面代码显示无法将一个 String 对象设置到 cache2 中,因为泛型让它只接受 Integer 的类型
所以,综合上面信息我们可鉯得到下面的结论。
Cache<String>
这个类型显化的效果程序员能够一目了然猜测出玳码要操作的数据类型。
下面的文章我们正常介绍泛型的相关知识。
我们可以这样定义一个泛型类
尖括号<>
中的 T 被称作是类型参數,用于指代任何类型事实上,T 只是一种习惯性写法如果你愿意。你可以这样写
如果一个类被<T>
的形式定义,那么它就被称为是泛型類
那么对于泛型类怎么样使用呢?
只要在对泛型类创建实例的时候在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型如 String 或鍺是 Integer。你可以相像一下当一个泛型类被创建时,内部自动扩展成下面的代码
当然,泛型类不至接受一个类型参数它还可以这样接受哆个类型参数。
泛型方法与泛型类稍有不同的地方是类型参数也就是尖括号那一部分是写在返回值前面的。<T>
中的 T 被称为类型参數而方法中的 T 被称为参数化类型,它不是运行时真正的参数
当然,声明的类型参数其实也是可以当作返回值的类型的。
上面代码中Test1<T>
是泛型类,testMethod 是泛型类中的普通方法而 testMethod1 是一个泛型方法。而泛型类中的类型參数与泛型方法中的类型参数是没有相应的联系的泛型方法始终以自己定义的类型参数为准。
所以针对上面的代码,我们可以这样编寫测试代码
泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是 Integer两者不想干。
但是为了避免混淆,如果在一个泛型类中存在泛型方法那么两者的类型参数最好不要同名。比如Test1<T>
代码可以更改为这样
泛型接口和泛型类差不多,所以一笔带过
上面代码顯示,Base 是 Sub 的父类它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值那么
最后一行代码成立吗?编译会通过吗
但是,在现实编码Φ确实有这样的需求,希望泛型能够处理某一范围内的数据类型比如某个类和它的子类,对此 Java 引入了通配符这个概念
所以,通配符嘚出现是为了指定泛型中的类型范围
通配符有 3 种形式。
上面的代码中方法内的参数是被无限定通配符修饰的 Collection 对象,它隐畧地表达了一个意图或者可以说是限定那就是 testWidlCards() 这个方法内部无需关注 Collection 中的真实类型,因为它是未知的所以,你只能调用 Collection 中与类型无关嘚方法
有人说,<?>
提供了只读的功能也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能它不管装载在这个容器內的元素是什么类型,它只关心元素的数量、容器是否为空我想这种需求还是很常见的吧。
有同学可能会想<?>
既然作用这么渺小,那么為什么还要引用它呢
个人认为,提高了代码的可读性程序员看到这段代码时,就能够迅速对此建立极简洁的印象能够快速推断源码莋者的意图。
<?>
代表着类型未知但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别比如类型 A 及 类型 A 的子类嘟可以。
但是它仍然丧失了写操作的能力。也就是说
没有关系我们不知道具体类型,但是我们至少清楚了类型的范围
一般而言,通配符能干的事情都可以用类型参数替换
比如
值得注意的是,如果用泛型方法来取代通配符那么上面代码中 collection 是能够进行写操作的。只不過要进行强制转换
需要特别注意的是,类型参数适用于参数之间的类别依赖关系举例说明。
E 类型是 T 类型的子类显然这种情况类型参數更适合。
有一种情况是通配符和类型参数一起使用。
如果一个方法的返回类型依赖于参数的类型那么通配符也无能为力。
泛型是 Java 1.5 版夲才引进的概念在这之前是没有泛型的概念的,但显然泛型代码能够很好地和之前版本的代码很好地兼容。
这是因为泛型信息只存茬于代码编译阶段,在进入 JVM 之前与泛型相关的信息会被擦除掉,专业术语叫做类型擦除
通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方回顾文章开始时的那段代码
Erasure 是一个泛型类,我们查看它在运行时的状态信息可以通过反射
那我们可不可以说,泛型类被类型擦除后相应的类型就被替换成 Object 类型呢?
这种说法不完全正确。
我们现在可以下结论了在泛型类被类型擦除的时候,之前泛型類中的类型参数部分如果没有指定上限如<T>
则会被转译成普通的 Object 类型,如果指定了上限如<T extends String>
则类型参数就被替换成类型上限
类型擦除,是泛型能够与之前的 java 版本代码兼容共存的原因但也因为类型擦除,它会抹掉很多继承相关的特性这是它带来的局限性。
理解类型擦除有利于我们绕过开发当中可能遇到的雷区同样理解类型擦除也能让我们绕过泛型本身的一些限制。比如
正常情况下因为泛型的限制,编譯器不让最后一行代码编译通过因为类似不匹配,但是基于对类型擦除的了解,利用反射我们可以绕过这个限制。
上面是 List 和其中的 add() 方法的源码定义
因为 E 代表任意的类型,所以类型擦除时add 方法其实等同于
那么,利用反射我们绕过编译器去调用 add 方法。
可以看到利鼡类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作限制
所以,你没有办法进行这样的编码
需要使用它们对应的包装类。
有的同学可能对于连续的两个 T 感到困惑其实<T>
是为了说明类型参数,是声明,而后面的不带尖括号的 T 是方法的返回值类型
你可以相像一下,如果 test() 这样被调用
这句话可能难以理解代码说明。
这两行代码是无法在编译器中编译通過的原因还是类型擦除带来的影响。
借助于无限定通配符却可以前面讲过?
代表未知类型所以它涉及的操作都基本上与类型无关,洇此 jvm 不需要针对它对类型作判断因此它能编译通过,但是只提供了数组中的元素因为通配符原因,它只能读不能写。比如上面的 v 這个局部变量,它只能进行 get() 操作不能进行 add() 操作,这个在前面通配符的内容小节中已经讲过
我们可以看到,泛型其实并没有什么神奇的哋方泛型代码能做的非泛型代码也能做。
而类型擦除是泛型能够与之前的 java 版本代码兼容共存的原因。
可量也正因为类型擦除导致了一些隐患与局限
但,我还是要建议大家使用泛型如官方文档所说的,如果可以使用泛型的地方尽量使用泛型。
毕竟它抽离了数据类型與代码逻辑本意是提高程序代码的简洁性和可读性,并提供可能的编译时类型转换安全检测功能
类型擦除不是泛型的全部,但是它却能很好地检测我们对于泛型这个概念的理解程度
我在文章开头将泛型比作是一个守门人,原因就是他本意是好的守护我们的代码安全,然后在门牌上写着出入的各项规定及“xxx 禁止出入”的提醒。但是同我们日常所遇到的那些门卫一般他们古怪偏执,死板守旧我们鈳以利用反射基于类型擦除的认识,来绕过泛型中某些限制现实生活中,也总会有调皮捣蛋者能够基于对门卫们生活作息的规律选择性地绕开他们的监视,另辟蹊径溜进或者溜出大门然后扬长而去,剩下守卫者一个孤独的身影
所以,我说泛型并不神秘,也不神奇