【Java】泛型
创始人
2024-01-16 19:03:25
0

当你觉得这条路很难走的时候,一定是上坡路


目录

1.初识泛型

1.1 什么是泛型

1.2泛型类语法

1.2.1泛型类定义

1.2.2泛型类使用语法 

 1.2.3泛型类的使用

1.2.4裸类型

2.泛型如何编译 

2.1擦除机制 

3.泛型的上界

3.1语法 

3.2示范 

4.泛型方法

4.1 语法 

4.2示例

 4.2.1使用示例-可以类型推导

4.2.2使用示例-不使用类型推导

5.通配符 

5.1通配符的作用 

5.2子通配符 

5.2.1通配符上界

5.2.2通配符的下界 

6.包装类 

6.1基本类型对应的包装类 

6.2装箱和拆箱

 6.2.1装箱

6.2.2拆箱 

6.2.3自动装箱和自动拆箱 


1.初识泛型

1.1 什么是泛型

《Java编程思想》这本书中对泛型的介绍中有这样一段话:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大

如下代码: 

class Array {public int[] arr = new int[10];public void setArr(int val,int pos) {this.arr[pos] = val;}public int getArr(int pos) {return this.arr[pos];}
}
public class Test {public static void main(String[] args) {Array array = new Array();array.setArr(1,0);int ret = array.getArr(0);System.out.println(ret);}
}

一般在方法的形参和返回值都会用到具体的类型,这样就限制了代码。如:我们实例化了一个Array 类型的对象,当我们用这个对象调用 setArr 方法设置数组值时,因为形参是整型那么只能传入整型的实参。当我们用这个对象调用 getArr 方法去获取数组某个位置值时,因为返回值是整型所以只能返回整型的值,那么这种刻板的限制对代码的束缚就会很大

为了避免如上的限制在 JDK1.5 版本中引入了泛型。

泛型就是参数化类型 ,说起参数大家第一时间就会想起方法的形参,当我们要调用方法时就需要传入实参,那什么叫做参数化类型呢?顾名思义也就是根据实参的类型来决定形参的类型

那我们现在需要将上述代码数组中可以存放任何类型的数据,那需要如何进行修改代码呢?

思路:所有类的父类,默认为Object类。所以类型都有包装类,那么它们的包装类都继承了Object类,那么我们就可以将数组设置为 Object 类。所以的子类都都可以用父类接收,那么我们将数组设置为 Object 类型之后就可以接收任何类型的值

class Array {public Object[] arr = new Object[10];public void setArr(Object val,int pos) {this.arr[pos] = val;}public Object getArr(int pos) {return this.arr[pos];}
}
public class Test {public static void main(String[] args) {Array array = new Array();array.setArr(1,0);array.setArr("obj",1);//int ret = (int)array.getArr(0);}
}

将数组设置为 Object 类型之后,就可以将数组中的不同位置放不同类型的值,但是当我们获取一个数组中指定位置的值时,需要强制类型转换。在大多数的情况下我们想要的是它只能够持有一种数据类型,而不是同时持有这么多类型。泛型的主要目的就是指定当前的容器,要持有什么类型的对象,让编译器去做检查。此时就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

1.2泛型类语法

1.2.1泛型类定义

class 泛型类名称<类型形参列表> {}
class Array {}

注:一个泛型的类型形参列表可以指定多个类型 

类型形参一般使用一个大写字母表示,类型形参命名: 

  • E 表示 Element
  • K 表示 Key
  • V 表示 Value
  • N 表示 Number
  • T 表示 Type
  • S, U, V 等等 - 第二、第三、第四个类型

1.2.2泛型类使用语法 

泛型类<类型实参> 变量名 = new 泛型类<类型实参>(构造方法实参); 

泛型类<类型实参> 变量名:定义一个泛型类引用

 new 泛型类<类型实参>(构造方法实参):实例化一个泛型类对象 

Array array = new Array();

类型推导:

Array array = new Array<>();

 当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

 1.2.3泛型类的使用

class Array {public T[] arr = (T[])(new Object[10]);public void setArr(T val,int pos) {this.arr[pos] = val;}public T getArr(int pos) {return this.arr[pos];}
}
public class Test {public static void main(String[] args) {Array array = new Array();array.setArr(1,0);array.setArr(1,1);Integer ret = array.getArr(0);System.out.println(ret);}
}
  • :占位符,相当于当前类是一个泛型类
  • 不能 new 泛型类型的数组
  • 类型后加入指定当前类型  
  • 不需要进行强制类型转换 
  • 编译器会在存放元素的时候帮助我们进行类型检查  
  • 泛型只能接受类,所有的基本数据类型必须使用包装类

泛型存在的意义: 

  • 存数据是会进行类型检查 
  • 取数据的时候会自动帮你类型转换,也就不需要我们手动强制类型转换了 

1.2.4裸类型

裸类型:在定义和实例化泛型类的时候不给类型实参

泛型类 变量名 = new 泛型类(构造方法实参); 
Array array = new Array();

裸类型存在的意义:是为了兼容老版本的 API 保留的机制

2.泛型如何编译 

2.1擦除机制 

class Array {public T[] arr = (T[])(new Object[10]);public void setArr(T val,int pos) {this.arr[pos] = val;}public T getArr(int pos) {return this.arr[pos];}
}

接下来我们就通过DOS窗口查看一下上述这个泛型类的字节码文件:

我们在Dos窗口中用命令 javap -c 查看字节码文件,此时所有的 T 都是 Object 。那么在编译的过程当中,将所有的 T 替换为 Object 这种机制,我们称为:擦除机制

Java的泛型机制是在编译级别实现的,编译器生成的字节码在运行期间并不包含泛型的类型信息 

问题:为什么不能 new 泛型类型的数组呢?

答:T[] arr = new T[],在编译过程当中不会将 new 的这个 T 擦除为Object,Java 规定擦除机制只针对定义变量时的类型和返回值的返回类型,不会擦除实例化时的泛型,所以 new 一个 T 数组,不会将 T 擦除为 Object 时,那么编译器就不认识 T,那么编译器在编译期间就会报错。

3.泛型的上界

泛型的上界:在定义泛型的时候,有时候需要给传过来的类型做一个约束,此时就可以用泛型的上界来进行约束。比如:我们希望传过来的类型是继承 School 类,那我们就可以用泛型的上界来约束,如果传过来的类型没有继承 School 这个类,则会报错

3.1语法 

class 泛型类名称<类型形参 extends 类型上界> {}

3.2示范 

class Student {}

此时只接受 School 的子类型作为 T 的类型实参,在编译时期擦除时会被擦除为其父类也就是擦除为School

注:如果没有指定泛型类型的上界,默认视为 T extends Object 

public class MyArray> {}

 此时传过来的实参类型必须是实现了 Comparable 接口

4.泛型方法

4.1 语法 

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {}

4.2示例

public class Test {public static  void swap(T[] array,int i,int j) {T t = array[i];array[i] = array[j];array[j] = t;}
}

 4.2.1使用示例-可以类型推导

public static void main(String[] args) {Integer[] arr = {1,2,3,4,5,6};swap(arr,0,1);}

 当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

4.2.2使用示例-不使用类型推导

public static void main(String[] args) {Integer[] arr = {1,2,3,4,5,6};Test.swap(arr,0,1);}

手动将类型实参传给类型形参

注: 给泛型传类型的时候必须传类,不能传基本数据类型但是可以传它们的包装类

5.通配符 

通配符:?用于在泛型的使用,这个?就是通配符

5.1通配符的作用 

class Array {public T[] arr = (T[])(new Object[10]);public void setArr(T val,int pos) {this.arr[pos] = val;}public T getArr(int pos) {return this.arr[pos];}
}
public class Test {public static void main(String[] args) {Array array = new Array();fun(array);}public static void fun(Array arr) {arr.setArr(1,0);System.out.println(arr.getArr(0));}
}

在主函数中实例化了一个 Array 泛型类,传的类型是 Integer。在 fun 方法形参中只能接收 Array 类的类型为 Integer 的对象,调用 fun 方法可以使用类型推导,如果传入的不是 Array 类的类型为Integer 的对象则会报错。Array 是一个泛型类,实例化的时候可以传入不同类型的实参,那么 fun这样就有了一定的局限性

那么此时我们就可以将fun方法形参中的Array<>里面类型设置为通配符,这样就可以接收 Array 类的任何类型的对象了

class Array {public T[] arr = (T[])(new Object[10]);public void setArr(T val,int pos) {this.arr[pos] = val;}public T getArr(int pos) {return this.arr[pos];}
}
public class Test {public static void main(String[] args) {Array array = new Array();array.setArr(1,0);fun(array);}public static void fun(Array arr) {System.out.println(arr.getArr(0));}
}

 使用通配符"?"说明它可以接收任意类型的Array对象,但是由于不确定类型,所以无法修改

5.2子通配符 

在通配符的基础上又产生了两个子通配符:

  • ?extends 类:设置泛型上界
  • ?super 类:设置泛型下界 

5.2.1通配符上界

①语法

传入的实参类型必须是 Number 本身类或者是 Number 的子类 

②示例 

class Fruits {}
class Apple extends Fruits {}class Message {private T message;public void setMessage(T message) {this.message = message;}public T getMessage() {return message;}
}
public class Main {public static void main(String[] args) {Message message = new Message<>();message.setMessage(new Apple());fun(message);}public static void fun(Message tmp) {//tmp.setMessage(new Apple());Fruits fruits = tmp.getMessage();}
}

fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits 子类类型。在fun里面用通配符接收类型,那么我们也就不清楚是具体的哪种类型,只知道是Fruits 类型 或者是 Fruits 子类类型,那么我们就无法调用 setMessage 去设置 message 的值,因为子类类型无法存储父类对象。我们知道主函数传给fun方法的对象类型要么是 Fruits 类型 要么是 Fruits 子类类型,那么我们就可以调用 getMessage 去获取 message 的值,用Fruits 接收即可。

通配符的上界,不能进行写入数据,只能进行读取数据 

5.2.2通配符的下界 

①语法 

传过来的类型必须是 Integer 本身类 或者是 Integer 的父类 

②示例 

class Fruits {}
class Apple extends Fruits {}class Message {private T message;public void setMessage(T message) {this.message = message;}public T getMessage() {return message;}
}
public class Main {public static void main(String[] args) {Message message = new Message<>();message.setMessage(new Fruits());fun(message);}public static void fun(Message tmp) {tmp.setMessage(new Apple());//Fruits fruits = tmp.getMessage();}
}

fun 形参类型传过来的实参类型必须是 Fruits 类型 或者是 Fruits类型的父类,那我们在fun方法中通过 setMessage 去设置 message 的值时,我们知道主函数传给 fun 方法的对象类型最低也得是Fruits 方法,通过 setMessage 去设置 message 值是我们可以设置为 Fruits 子类对象,因为 Fruits子类肯定也是Fruits父类的子类,所以可以设置 message 的值。但是不能通过 getMessge 去获取messge的值,原因就在于我们只知道主函数传给fun实参类型是 Fruits 类型 或者是 Fruits类型的父类,如果传过来的实参类型是 Fruits 父类那我们去获取message的值时我们就不知道用什么接收了。

通配符的下界,不能进行读取数据,只能写入数据 

6.包装类 

在 Java 中,基本类型是不会继承 Object 类的,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型 

6.1基本类型对应的包装类 

基本数据类型包装类
byteByte
shoutShout
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

6.2装箱和拆箱

 6.2.1装箱

int a = 10;
Integer i = Integer.valueOf(a);

Integer.valueof:主要将一个基本数据类型的值存放到 Integer 类的对象里面

上述代码装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中 

Integer 类中的 valueOf 方法中,如果你存储的值在 -128(IntegerCache.low:-128)到127(IntegerCache:127)范围中就存储到 i +(-IntegerCache.low)这个位置的数组当中。如果超出了这个范围就 new 一个 Integer 对象,通过构造方法存储在 value 属性中

6.2.2拆箱 

int a = 10;
Integer i = Integer.valueOf(a);//装箱
int b = i.intValue();//拆箱

 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中

6.2.3自动装箱和自动拆箱 

从上述手动装箱和拆箱中我们可以看出手动装箱和拆箱给开发者带来了不少代码量,为了减少开发者的负担,Java提供了自动装箱拆箱机制

public class Demo {public static void main(String[] args) {int i = 10;Integer ii = i; // 自动装箱Integer ij = (Integer)i; // 自动装箱int j = ii; // 自动拆箱int k = (int)ii; // 自动拆箱}
}

通过字节码文件可以看出在编译时期会自动的装箱拆箱,还原为装箱和拆箱 

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...