作者:qwer1030274531

出自:ITPUB博客

1、为什么我们需要泛型?

通过两段代码就可以知道为什么需要泛型

/***

* 没有泛型的时候实现加法

*/public class NonGeneric {

//我们接到一个需求 两个整数相加 ,然后我们就实现了如下代码

public int addInt(int x,int y){

return x+y;

}

//业务 发展了 需要 两个浮点数 相加

public float addFloat(float x,float y){

return x+y;

}

//业务又又又发展了 需要两个double相加

public double addDouble(double x,double y){

return x+y;

}

//就是应为参数类型不同 重写这个方法 ,

//能不能只写一段代码逻辑相同,传入int folat double 都行呢!

//泛型的应用场景之1,可以传入不同的数据(参数)类型,执行相同的代码

public void listDemo(){

/**

* list没有用泛型的时候 ;add元素的时候 他们都变成了object类型的,

* get的时候 必须加一个强制

* 类型转换,就因为要加强转,所以如果add的是int 他就会报错

* java.lang.ClassCastException:

* java.lang.Integer cannot be cast to java.lang.String

*/

List list =new ArrayList();

list.add("爱码士赵Sir");

list.add("轩轩吖");

list.add(100);

for (int i=0;i

String name =(String)list.get(i);

System.out.println("name:"+name);

}

/***

* 泛型的应用场景之2,定义了泛型之后,

* 使用的过程中直接指定这个list所盛装的类型只能放String,

* 这样我们就能在编译期,找到这个错误,

* 也避免了我们在使用的时候的强制类型转换

*/

List list2 =new ArrayList<>();

list2.add("爱码士赵Sir");

list2.add("轩轩吖");// list2.add(100);

for (int i=0;i

String name =list2.get(i);

System.out.println("name:"+name);

}

}

public static void main(String[] args) {

NonGeneric nonGeneric=new NonGeneric();

System.out.println(nonGeneric.addInt(1,2));

System.out.println(nonGeneric.addFloat(1f,2f));

System.out.println(nonGeneric.addDouble(1d,2d));

nonGeneric.listDemo();

}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061我们使用了泛型之后,在我们编码的过程中就可以指定我们数据类型,而不需要进行强制类型转换

如果我们插入了错误的数据类型,在编译器就能发现这个错误,不至于我们到了运行期才抛这个异常

2、泛型类、泛型接口定义

泛型的定义:参数化的类型,在我们普通的方法中,传入的int x,int y这是参数对吧,调用方法的时候传进去一个实际的值,

参数类型:这个参数在定义的时候,这个参数类型本身,把它参数化,在实际 调用的时候,我们再告诉方法这个参数是什么类型,这就是所谓的泛型泛型类的定义和使用

/***

* 泛型类的定义

* @param

*/public class NormalGeneric {

private T data;

public NormalGeneric() {

}

public NormalGeneric(T data) {

this.data = data;

}

public T getData() {

return data;

}

public void setData(T data) {

this.data = data;

}

public static void main(String[] args) {

NormalGeneric normalGeneric=new NormalGeneric<>();

normalGeneric.setData("OK");

System.out.println(normalGeneric.getData());

}}12345678910111213141516171819202122232425262728泛型接口的定义和使用

public interface Genertor {

public T next();}/***

* 实现方法一,不指定泛型类型,泛型类实现泛型方法,实现类也是一个泛型类

* 在使用的时候和平常的泛型类没有太大的差别

* @param

*/public class ImplGenertor implements Genertor{

@Override

public T next() {

return null;

}}/***

* 实现方法二:指定泛型类型,实现方法的返回值 是指定的类型String,而实现方法一里面

* 返回值还是一个泛型

*/public class ImplGenertor2 implements Genertor {

@Override

public String next() {

return null;

}}1234567891011121314151617181920212223242526

3、泛型方法辨析

泛型方法是独立的,不一定非得再泛型类里,泛型接口里声明

泛型方法的标志就是返回值和权限修饰符中间的

在普通类中可以使用泛型方法,在泛型类里也是可以使用泛型方法的

和泛型类和接口一样 都是在使用它的时候,才告诉编译器我们的数据类型 像下边的

一些高版本的JDK变的比较聪明,会自动推断出类型比如下面的第二行代码

//泛型方法的标志就是返回值和权限修饰符中间的

//在普通类中可以使用泛型方法,在泛型类里也是可以使用泛型方法的

public T genericMethod(T...a){

return a[a.length/2];

}

public void test(int x,int y){

System.out.println(x+y);

}

public static void main(String[] args) {

GenericMethod genericMethod=new GenericMethod();

genericMethod.test(1,1);

//和泛型类和接口一样 都是在使用它的时候,才告诉编译器我们的数据类型 像下边的

//一些高版本的JDK变的比较聪明,会自动推断出类型比如下面的第二行代码

System.out.println(genericMethod.genericMethod("小明","小红","小绿"));

System.out.println(genericMethod.genericMethod(12,34,45));

}12345678910111213141516

public class GenericMethod2 {

public class Generic{

private T key;

public Generic(T key) {

this.key = key;

}

//虽然在方法中使用了泛型,但是这并不是一个泛型方法

//这只是类中的一个普通成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型

//所以在这个方法中才可以继续使用T这个泛型

public T getKey(){

return key;

}

/***

* 这个方法显然是有问题的,在编译器会给我们提示这样的错误消息 cannot reslove symbol “E”

* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别

* @param key

* @return

*/// public E setKey(E key){// this.key=key;// }

}

/***

* 这也不是一个泛型方法,这就是一个普通的方法

* 只是使用了Generic这个泛型类做形参而已

* @param obj

*/

public void show(Generic obj){

}

/***

* 这个方法也是有问题的,编译器会为我们提示错误信息:unknown class E

* 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法

* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道如何处理E这个类型

* @param ab

* @param

* @return

*/// public T show(E ab){ }

/***

* 普通的类中没有泛型的T 所以这个方法也是有问题的

* @param obj

*/// public void show(T obj){ }

}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556不是所有写在泛型方法里的方法都叫泛型方法

泛型方法的标志是

public class GenericMethod3 {

static class Fruit{

@Override

public String toString() {

return "Fruit";

}

}

static class Apple extends Fruit{

@Override

public String toString() {

return "Apple";

}

}

static class Person{

@Override

public String toString() {

return "Person";

}

}

static class GenerateTest{

public void show_1(T t){

System.out.println(t.toString());

}

/***

* 在泛型类中 声明了一个泛型方法,使用泛型E,这种泛型可以为任意类型

* 类型可以与T相同,也可以不同

* 由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中并未声明泛型

* 编译器也能够正确的识别泛型方法中识别的泛型

* @param t

* @param

*/

public void show_3(E t){

System.out.println(t.toString());

}

/***

* 在泛型类中声明了一个泛型方法,使用泛型T

* 注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型

* @param t

* @param

*/

public void show_2(T t){

System.out.println(t.toString());

}

public static void main(String[] args) {

Apple apple=new Apple();

Person person=new Person();

GenerateTest generateTest=new GenerateTest<>();

generateTest.show_1(apple);//apple 是fruit的子类 这没有问题// generateTest.show_1(person);//传person 肯定是不行的

generateTest.show_2(apple);

generateTest.show_2(person);//因为泛型方法 所以 完全可以把person传进去,泛型 方法里的参数类型,不管和泛型类,相同还是不相同,都得看成两个不同的参数类型

generateTest.show_3(apple);

generateTest.show_3(person);//泛型类里的泛型方法的泛型类型以泛型方法为准 泛型类的泛型类型只影响泛型类的普通方法

}

}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960

4、如何限定类型变量

先来一段代码

public class ArrayAlg {

public static T min(T a,T b){

if(a.compareTo(b)>0) return a;

else return b;

}}123456

首先 这段代码是有问题的,这是一个普通的类,写了一个泛型方法,这个方法做的事情是比较两个数,传进两个数返回一个最小值,我们怎么确定传进来的 a和 b 都有compareTo方法呢,这就用到了类型变量的限定

public class ArrayAlg {

public static T main(T a,T b){

if(a.compareTo(b)>0) return a;

else return b;

}

public static T main(T a,T b){

if(a.compareTo(b)>0) return a;

else return b;

}

public static T test(T a,T b){// if(a.compareTo(b)>0) return a;// else return b;

return a;

}

static class Test{}}12345678910111213141516

extends Comparable 就限定了这个泛型,传进的值必须实现了或者继承了Comparable这个接口,如果传进没有实现或继承Comparable的类或者接口,会在编译器就会报错,提示你传入的类型不对,

限定类型是可以传多个接口的,如果是类和接口混用的话,类只能有一个,并且要放到第一个,放到第一个是java语法的规范!多个限定类型用&连接,泛型方法可以这样限定,泛型类和泛型接口接口也可以这样限定

5、泛型使用中的约束性和局限性不能实例化类型变量

public class Restrict {

private T data;// 不能实例化类型变量

public Restrict() {

this.data = new T();//这种是不被允许的

}}123456789静态域或者方法里不能引用类型变量

public class Restrict {

private T data;// 静态域或者方法里不能引用类型变量

private static T instance; //这个也是不被允许的}1234567

这里为什么静态域或者静态方法不能引用类型变量,是因为泛型的类型,只有在创建这个泛型类型的时候才会知道他的类型是什么,而我们的staitc 执行时间是在构造方法前,所以他不允许,但是如果静态方法是泛型方法,是可以引用的!泛型的类型不能是基本类型 ,只能是他的包装类型,因为基本类型不是一个对象

泛型不允许用instanceof 来判定类型打印的结果是true,打印的name也是一样的Restrict

Restrict 是这两个类型的原生类型,getClass的时候也只能获取到原生类型,获取不到泛型类型

可以定义泛型数组,但是不能创建这个数组泛型类不能继承Exception/Throwable不能捕获泛型类 对象

但是可以throw出去

public void doWork(T t) throws T{

try{

}catch (Throwable e){

throw t;

}

}12345678

6、泛型类型的继承规则

public static void main(String[] args) {

Pair employeePair=new Pair<>();

Pair workerPair=new Pair<>();

}1234

1、Worker 是派生自 Employee,Worker是Employee的子类,但是Pair 和 Pair 没有任何继承关系的

在普通的类中,是可以父类声明直接实例化子类的

但是加上泛型类之后,就不可以了

2、泛型类可以继承或者扩展其他泛型类

什么意思呢?

public static void main(String[] args) {

Pair employeePair=new Pair<>();

Pair workerPair=new Pair<>();

Pair employeePair3=new EXtendPair<>();

}

private static class EXtendPair extends Pair{

}123456789

上面这个图片也说明了Pair 和 Pair 没有任何继承关系,但是我也想set进去怎么办!通配符就派上用场了

7、通配符类型

首先我们定义几个类Fruit Apple Orange HongFuShi,几个类的派生关系如下

GenericType是一个很标准的泛型类,没有任何特殊的地方

虽然Fruit和Orange是派生关系,但是print(b)会报错的, GenericType和GenericType是没有关系的,这个上面已经说过

7.1上界通配符

通配符来了? extends Fruit,这个代表什么意思呢?

它表示GenericType 传进来的类型参数可以是Fruit及他的子类

public static void print2(GenericType extends Fruit> p){

System.out.println(p.getData().getColor());

}

public static void user2(){

GenericTypea=new GenericType<>();

print2(a);

GenericTypeb=new GenericType<>();

print2(b);

}123456789

这样你打印b的时候就可以了,通配符只用于方法里,泛型类和泛型接口是不能用的,这个和限定类型是不一样的.

这个通配符规定了传入类型的上界,只能是Fruit本身及子类,

上界通配符有什么限制呢?

我往里面set值的时候回报编译错误,get的时候也只能用Fruit来接收 为什么呢?

上界通配符传入的时候 一定是Fruit的子类及本身,所以我get的时候,不管我传进去的本身是什么,它一定是个Fruit,但是我不能确定它是个苹果,但是set的时候为什么不行呢?是因为set的时候它肯定知道你是一个水果,但是你具体是那个子类,它并不知道,所以是会有问题的

所以这个上界通配符,只用于安全的访问数据,

7.2下界通配符

由上图可知,我们用了? super之后发现 ,我们只能放进Apple及它的父类,? super Apple 表示GenericType的参数类型的下界是Apple

使用下界通配符的时候,你只能set Apple 及它的子类,你不能set它的父类,get的时候只能是Objec接收,其他的都不可以,因为Objct是 所有类的父类,你放进去的时候,编译器肯定能确定的是,它是Apple父类,但是具体是那个父类,不知道 ,但Object 一定是他们的父类,为什么set的时候能set进去Apple的子类,是因为所有apple的子类都能安全的转型为Apple

下界通配符,只能用于安全的写入数据

8、虚拟机是如何实现泛型的?

其实java的泛型是一个假的伪泛型,在JVM里是用类型擦除来实现泛型的,在C#里的泛型才是一个真真正正的泛型

public class GenericType {

private T data;

public T getData() {

return data;

}

public void setData(T data) {

this.data = data;

}}1234567891011

虽然我们定义了一个泛型,在JDK里, data 是一个Object就变成下边的这样

public class GenericType {

private Object data;

public Object getData() {

return data;

}

public void setData(Object data) {

this.data = data;

}}1234567891011

但是如果是用了限定符来限定的泛型类呢,会擦除成什么样子的呢

public class GenericType {

private T data;

public T getData() {

return data;

}

public void setData( T data) {

this.data = data;

}}1234567891011

它会擦除成这样

public class GenericType {

private Comparable data;

public Comparable getData() {

return data;

}

public void setData( Comparable data) {

this.data = data;

}}1234567891011

会以extens 后面的第一个类型来作为擦除类型,但是后边的怎么办呢?后边的 Serializable你在用到它的方法时,编译器会插入一段强制转型的代码

把编译好的class打开看一下,

擦除之后他们的类型 是一样的,所以这种方式在编译器中是不通过的,在字节码 里有一个属性 Signature (弱记忆)会记录 泛型信息 ,原始类型啥的,并不是把类型擦除的很干净,啥都不剩

.net java 泛型_Java高级特性泛型看这一篇就够了相关推荐

  1. 计算机二级excel高级筛选,Excel高级筛选,看这一篇就够了!

    原标题:Excel高级筛选,看这一篇就够了! Excel高级筛选,看这一篇就够了 Excel中的自动筛选很多人都会用,但经常遇到多条件或复杂条件筛选时卡壳的窘境,其实强大的Excel早就为你准备了N多 ...

  2. java优先队列_Java高级特性增强-多线程

    请戳GitHub原文: https://github.com/wangzhiwubigdata/God-Of-BigData 大数据成神之路系列: 请戳GitHub原文: https://github ...

  3. java高级反射_Java高级特性之反射

    老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用 ...

  4. Java中的多线程你只要看这一篇就够了

    如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...

  5. Java 中的多线程你只要看这一篇就够了

    作者丨纳达丶无忌 https://www.jianshu.com/p/40d4c7aebd66 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多 ...

  6. Java高频重点面试题,看这一篇就够了。

    查缺补漏 一.基础篇 1. ==和equals区别? 2. spring 有哪些主要模块? 3. Spring中bean的生命周期 3.1 Spring Bean的生命周期指的是从一个普通的Java类 ...

  7. Unity 调用Java | 调用aar包 | 调用jar包 | Java内部类,附带实例,看我一篇就够啦~

  8. React 五大特性__看这一篇就够了

    React 是 Facebook 在2013年开源的用于构建用户界面的 JavaScript 库. 1. React 独立架构 React 是 MVC 中薄薄的一层 V,把数据变成 DOM 显示出来, ...

  9. Java这个高级特性-泛型,很多人还没用过!

    点击关注公众号,Java干货及时送达 泛型是 Java 的高级特性之一,如果想写出优雅而高扩展性的代码,或是想读得懂一些优秀的源码,泛型是绕不开的槛.本文介绍了什么是泛型.类型擦除的概念及其实现,最后 ...

最新文章

  1. 深度学习模型部署简要介绍
  2. Asp.net Mvc问题索引
  3. linux后台运行命令,nohup
  4. 关于Istio 1.1,你所不知道的细节
  5. [bzoj1191][HNOI2006]超级英雄Hero
  6. 《精通Spring4.X企业应用开发实战》读后感第六章(国际化)
  7. 小甲鱼Python学习笔记之函数(二)
  8. 人工智能十大算法及应用,十大人工智能算法公司
  9. mssql数据库置疑修复
  10. 互联网晚报 | 12月17日 星期五 | 抖音电商独立App“抖音盒子”正式上线;腾讯电子签上线商家版功能;年内首只游戏股上市...
  11. 如何将明细数据自动分类生成汇总报表
  12. wpf实现类似word文档的标尺功能
  13. 文华软件怎样测试交易系统的收益,文华财经交易系统之终极黄金
  14. 处理solr时遇到的问题
  15. 短视频查重机制及去重方法
  16. 蓝鲸RFID固定资产管理系统
  17. 贾俊平《统计学》第七章知识点总结及课后习题答案
  18. (数据库-MySQL)查看表的结构、表的创建过程、表
  19. 美团2021笔试题(第十场)-公司食堂
  20. 常用图标png、ico 图标下载,图片格式转换为ico

热门文章

  1. Final Cut Pro X for Mac(专业视频剪辑工具)
  2. 高速球滑环在监控方面的应用
  3. 多通道采样键精确电力线监测
  4. python catia 接口_Catia 二次开发资料(转)
  5. 软件安装 怎么安装与破解Xmind 8 pro
  6. creo如何更改打开时显示方式_如何修改creo的初始目录
  7. gis map数据包_arcgis mpk 打包地图 (数据管理)
  8. 阿里云矢量图标的使用
  9. 加速计简单使用---迷宫游戏
  10. Metal_入门01_为什么要学习它