Java数据结构(三):包装类和泛型 雨落在了我的手上个人主页个人仓库Gitee仓库❄️个人专栏JaveSe C语言 C语言数据结构人生格言无人扶我青云志我自踏雪至山巅博主简介目录一包装类1.基本数据类型和对应的包装类2.装箱和拆箱3.⾃动装箱和⾃动拆箱二什么是泛型三引出泛型1.语法四泛型类的使⽤1.语法2.⽰例3.类型推导(TypeInference)五裸类型(RawType)了解1.说明六泛型如何编译的1.擦除机制2.关于桥接⽅法七泛型的上界1.语法2.⽰例八泛型⽅法1.定义语法2.⽰例内容大纲Java包装类与泛型机制摘要包装类Java为基本类型提供对应的包装类如Integer对应int用于泛型支持自动装箱/拆箱机制简化了基本类型与包装类的转换Integer缓存机制导致-128~127范围内的值使用比较为true泛型通过类型参数化实现代码复用编译时进行类型检查语法class ClassNameT常用类型参数E、K、V等类型擦除机制编译后泛型信息被擦除替换为Object或指定边界类型支持泛型上界约束extends和泛型方法泛型优点增强类型安全性减少强制类型转换提高代码复用性。一包装类在Java中由于基本类型不是继承⾃Object为了在泛型代码中可以⽀持基本类型Java给每个基本 类型都对应了⼀个包装类型1.基本数据类型和对应的包装类除了Integer和Character其余基本类型的包装类都是⾸字⺟⼤写。2.装箱和拆箱public class Test { int i 10; //装箱操作新建⼀个Integer类型对象将i的值放⼊对象的某个属性中 Integer i1 Integer.valueOf(i); //拆箱操作将Integer对象中的值取出放到⼀个基本数据类型中 int j i1.intValue(); }3.⾃动装箱和⾃动拆箱可以看到在使⽤过程中装箱和拆箱带来不少的代码量所以为了减少开发者的负担java提供了⾃ 动机制public static void main(String[] args) { int i 10; //自动装箱两种写法 Integer j1 i; Integer j2 (Integer) i; //自动拆箱两种写法 int o1 j1; int o2 (int) j2; }我们可以通过javap-c查看字节码⽂件内容观察装箱和拆箱的操作【⾯试题】下列代码输出什么为什么public static void main(String[] args) { Integer a 127; Integer b 127; Integer c 128; Integer d 128; System.out.println(a b); System.out.println(c d); }输出输出结果分析第一行输出true因为变量a和b的值都是 127处于 Java 整数缓存的默认范围内-128 到 127。在自动装箱时它们指向的是同一个缓存对象因此使用比较引用地址时返回true。第二行输出false因为变量c和d的值是 128超出了默认缓存范围。此时每次赋值都会创建一个新的Integer对象c和d指向不同的内存地址所以比较结果为false。二什么是泛型⼀般的类和⽅法只能使⽤具体的类型:要么是基本类型要么是⾃定义的类。如果要编写可以应⽤于 多种类型的代码这种刻板的限制对代码的束缚就会很⼤。-----来源《Java编程思想》对泛型的介 绍。泛型是在JDK1.5引⼊的新的语法通俗讲泛型就是适⽤于许多许多类型。从代码上讲就是对类 型实现了参数化。三引出泛型实现⼀个类类中包含⼀个数组成员使得数组中可以存放任何类型的数据也可以根据成员⽅法返 回数组中某个下标的值思路1. 我们以前学过的数组只能存放指定类型的元素例如int[] array new int[10]; String[] strs new String[10];2. 所有类的⽗类默认为Object类。数组是否可以创建为Object?代码⽰例class MyArray{ public Object[] array new Object[10]; public Object getPos(int pos) { return this.array[pos]; } public void setVal(int pos,Object val) { this.array[pos] val; } public static void main(String[] args) { MyArray myArray new MyArray(); myArray.setVal(0,10); myArray.setVal(1,hello);//字符串也可以传 Object result myArray.getPos(0); String ret (String) myArray.getPos(1); System.out.println(ret); System.out.println(result); } }问题以上代码实现后发现1. 任何类型数据都可以存放2. 1号下标本⾝就是字符串但是确编译报错。必须进⾏强制类型转换虽然在这种情况下当前数组任何数据都可以存放但是更多情况下我们还是希望他只能够持有 ⼀种数据类型。⽽不是同时持有这么多类型。所以泛型的主要⽬的就是指定当前的容器要持有什么类型的对象。让编译器去做检查。此时就需要把类型作为参数传递。需要什么类型就传⼊ 什么类型。1.语法基础写法class 泛型类名称 类型形参列表 {// 这⾥可以使⽤类型参数}class ClassName T1,T2.....{}其他写法class 泛型类名称 类型形参列表 extends 继承类 /*这⾥可以使⽤类型参数 */ {// 这⾥可以使⽤类型参数}class ClassName T1,T2...... extends ParentClassT1{// 可以只使⽤部分类型参数}上述代码进⾏改写如下class MyArrayT{ public Object[] array new Object[10]; public T getPos(int pos) { return (T)this.array[pos]; } public void setVal(int pos,Object val) { this.array[pos] val; } public static void main(String[] args) { MyArrayInteger myArray new MyArray();//1 myArray.setVal(1,12); int ret myArray.getPos(1);//2 System.out.println(ret); myArray.setVal(2,bit);//3 } }输出代码解释1.类名后的 T 代表占位符表⽰当前类是⼀个泛型类2.了解【规范】类型形参⼀般使⽤⼀个⼤写字⺟表⽰常⽤的名称有• E表⽰Element• K表⽰Key• V表⽰Value• N表⽰Number• T表⽰Type• S,U,V等等-第⼆、第三、第四个类型3.注释1处类型后加⼊ Integer 指定当前类型4.注释2处不需要进⾏强制类型转换5.注释3处代码编译报错此时因为在注释2处指定类当前的类型此时在注释4处编译器会在存 放元素的时候帮助我们进⾏类型检查。四泛型类的使⽤1.语法泛型类 类型实参 变量名 ; // 定义⼀个泛型类引⽤new 泛型类 类型实参 ( 构造⽅法实参 ); // 实例化⼀个泛型类对象2.⽰例MyArrayInteger list new MyArrayInteger();注意泛型只能接受类所有的基本数据类型必须使⽤包装类3.类型推导(TypeInference)当编译器可以根据上下⽂推导出类型实参时可以省略类型实参的填写MyArrayInteger list new MyArray();五裸类型(RawType)了解1.说明裸类型是⼀个泛型类但没有带着类型实参例如MyArrayList就是⼀个裸类型MyArray list new MyArray();注意我们不要⾃⼰去使⽤裸类型裸类型是为了兼容⽼版本的API保留的机制下⾯的类型擦除部分我们也会讲到编译器是如何使⽤裸类型的。⼩结1. 泛型是将数据类型参数化进⾏传递2. 使⽤ T 表⽰当前类是⼀个泛型类。3. 泛型⽬前为⽌的优点数据类型参数化编译时⾃动进⾏类型检查和转换六泛型如何编译的1.擦除机制那么泛型到底是怎么编译的1. 基本概念• 在编译时Java编译器会将泛型类型信息从代码中移除这个过程就叫做类型擦除。• 擦除后泛型类型会被替换为其边界类型通常是Object或者指定的类型。2. 擦除过程• 将泛型参数替换为其边界或Object。• 在必要的地⽅插⼊类型转换以保持类型安全。• ⽣成桥接⽅法以保持多态性。3. ⽰例上述代码在类型擦除后为如下代码擦除前class MyArrayT { public Object[] array new Object[10]; public T getPos(int pos) { return (T)this.array[pos]; } public void setVal(int pos,T val) { this.array[pos] val; } }擦除后class MyArray { public Object[] array new Object[10]; public Object getPos(int pos) { return this.array[pos]; } public void setVal(int pos, Object val) { this.array[pos] val; } }2.关于桥接⽅法• 泛型类型擦除可能导致⼦类⽅法和⽗类⽅法的签名不⼀致。• 为了维护Java的多态性需要桥接⽅法来确保⼦类⽅法能够正确覆盖⽗类⽅法。class NodeT { T data; public void setData(T data) { this.data data; } } class StringNode extends NodeString { Override public void setData(String data) { super.setData(data); } }• 类型擦除后代码变为class Node { Object data; public void setData(Object data) { this.data data; } } class StringNode extends Node { Override public void setData(String data) { super.setData(data); } }此时 StringNode 的 setData ⽅法并没有真正覆盖⽗类的setData ⽅法参数类型不同。为了解决这个问题编译器会在 StringNode 中⽣成⼀个桥接⽅法// 编译器⽣成的桥接⽅法 public void setData(Object data) { setData((String) data); }七泛型的上界在定义泛型类时有时需要对传⼊的类型变量做⼀定的约束可以通过类型边界来约束。1.语法class 泛型类名称 类型形参 extends 类型边界 {...}2.⽰例public class MyArray E{...}只接受Number的⼦类型作为E的类型实参MyArrayInteger l1; //正常因为 Integer 是 Number 的⼦类型MyArrayString l2; //编译错误因为 String 不是 Number 的⼦类型了解没有指定类型边界E可以视为EextendsObject八泛型⽅法1.定义语法⽅法限定符 类型形参列表 返回值类型 ⽅法名称 ( 形参列表 ) {...}2.⽰例public class Util { //静态的泛型⽅法需要在static后⽤声明泛型类型参数 public static E void swap(E[] array, int i, int j) { E t array[i]; array[i] array[j]; array[j] t; } }以上就是我们的全部内容了