1.包装类
(1)回顾数据类型
在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个类型都对应了一个包装类.
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
(2)装箱和拆箱
装箱(装包):把基本类型 转变成 包装类型.
int i = 10;
//装箱操作 新建一个Integer类型对象,将i的值放入对象的某个属性中
Integer ii = new Integer(i);
Integer ii2 = Integer.valueOf(i);//手动装箱 底层都调用了valueOf()方法
拆箱(拆包):把包装类型 转变为 基本数据类型.
int i = 10;
//拆箱操作,将Integer对象中的值取出,放到一个基本数据类型中
Interger ii2 = new Integer(10);
int b = ii2;
int bb = ii.intValue();//调用了intValue()方法 手动显示拆箱
自动拆箱和自动装箱都可以在"反汇编"中显示.
查看以下代码的运行结果:
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);//true
System.out.println(c == d);//false
}
观察Integer类的源码中的valueOf()发现:
当i的值属于[-128,127]范围在,会直接返回Integer缓存数组中相应对象的引用;如果i大于127或小于-128,会重新创建一个Integer实例,并返回.
所以,a和b的值都在缓存范围内,因此他们指向了同一个对象,因此返回true;而c和d的值不在范围内,都是通过new创建出来的,因此不是同一个对象,返回false.
2.什么是泛型
泛型是在JDK1.5后引入的新语法.通俗讲,泛型就是适用于许多许多类型.从代码上讲,就是对类型实现了参数化.(对类型作为参数进行传递).
泛型类的使用
案例代码:
//实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值
//创建泛型类
class Myarray <T> {
public Object[] array = new Object[10];//1
public T getPos(int pos) {
return (T) array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
}
//main方法中
public static void main(String[] args) {
Myarray<Integer> myarray = new Myarray<>();//2
myarray.setVal(0,1);
myarray.setVal(1,2);
int ret = myarray.getPos(0);//3
System.out.println(ret);
mysrray.setVal(2,"hi");//4
Myarray<String> myarray1 = new Myarray<>();
myarray1.setVal(1,"hello");
myarray1.setVal(2,"world");
String ret2 = myarray1.getPos(2);
System.out.println(ret2);
}
代码解析:
类名后的<T> 表占位符,表示当前类是一个泛型类.
(类型形参一般使用大写字母表示,常用的名称有:E,K,V,N,T,S,U,V...).E表示Element,K表示Key,V表示Value,这三个常用.
<>里面的类型,不能是基本数据类型,要是引用类型;<>里面可以写多个.
不能new泛型类型的数组.第一`是因为我们实例化的数组是一个具体的类型,写成这样,我们无法知道是哪种数据类型~第二,更重要的原因是,数组是一种单独的数据类型,数组之间没有 继承关系 的说法.所以此处创建数组正确写法就是注释1处的写法,并把所有的Object用T代替.
注释2处,就是在实例化泛型类.类型后加入<Integer>指定当前类型.<>里面表示 指定当前类以什么样的类型进行参数的传递,后面的第二个<>里面可以省略不写.
注释3处,不需要1进行强制类型转换.
注释4处,代码编译报错.此时因为在注释2处指定类当前类型为Integer类型,代码中为String类型,编译器会在存放元素的时候帮助我们进行类型检查.
总结:
泛型是将数据类型参数化,进行传递.
使用<T>表示当前类是一个泛型类.
泛型存在的意义就是,1在编译时检查数据类型是否正确;2在编译时帮助进行类型转换.
需要注意的是,泛型只能接受类,所有的基本数据类型必须使用包装类!
泛型是如何编译的
擦除机制
在编译的过程当中,将所有的 T 替换成 Object 这种机制,称为 擦除机制.
因为泛型是编译时期的机制,也意味着运行时没有泛型的概念 ==> JVM当中没有泛型的概念.
class Myarray <T> {
public T[] array = (T[])new Object[10];
public T getPos(int pos) {
return this.array[pos];
}
public void setVal(int pos,T val) {
this.array[pos] = val;
}
public T[] getArray(){
return array;
}
}
//main方法中
public static void main(String[] args) {
Myarray<Integer> myarray= new Myarray<>();
Integer[] strings = myarray.getArray();
}
分析:我们可能会认为,擦除机制不是在编译的时候,会将T替换为Object吗?为什么会报类型转换异常呢?
原因:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候直接转给Integer类型的数组,编译器认为是不安全的.()
3.泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束.
泛型类:
语法:
class 泛型类名称<类型形参 extends 类型边界>{...}
(1)示例:
public class MyArray<E extends Number> {..}
E:一定是Number 或 Number的子类
(2)复杂示例
public class MyArray<E extends Comparable<E>> {...}
E:传入的E一定是实现了该接口的.
泛型方法:
语法:
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) {...}
代码实现
泛型类
//写一个泛型类,求一个数组的最大值
class Alg<T extends Comparable<T>> {
public T findMaxVal(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
public class demo1 {
public static void main(String[] args) {
Integer[] array = {7,2,3,6,8,1,20};//定义一个`Integer类型的数组
Alg<Integer> alg = new Alg<>();
System.out.println(alg.findMaxVal(array));//输出结果20
}
}
解析:
<T>表示这是一个泛型类. <T extends Comparable<T>>表示泛型的边界. 擦除机制会将所有的T替换成Object,但是在Object源码中并没有实现Comparable接口.那么当数组进行比较大小是无法调用compareTo()方法的,这时就要设置泛型的边界. extends Comparable<T>代表传入的T一定是实现了Comparable接口,compareTo()方法才可以调用.
实例化一个泛型类,将Integer作为泛型类型的参数进行传递. 那么为什么Integer可以用?👇👇
是因为在Integer源码里面,其本身实现了Comparable接口,重写了compareTo()方法.
- (拓展)因此当我们想传递一个Person作为泛型类型的参数进行传递时,一定要实现Comparable接口,重写compareTo()方法,才不会报错.👇👇
泛型方法
class Alg2 {
public <T extends Comparable<T>> T findMaxVal(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
public class demo1 {
public static void main(String[] args) {
Integer[] array = {66,799,88};
Alg2 alg2 = new Alg2();//实例化对象 不会报错,因为Alg2类是一个普普通通的类
alg2.<Integer>findMaxVal(array);//推导.传递的参数的类型是什么,<T>里面就是什么
alg2.findMaxVal(array);//<Integer>也可以省略不写
}
}
解析:
结构如右图.创建一个普通的类,在类中写一个泛型方法.
设置泛型边界,实现Comparable接口去调用compareTo()方法完成数组比较大小的操作.
<T>代表alg2调用findMaxVal时传了一个Integer类型的参数,通过Integer就可以推导出<T>里面是什么类型. (划重点!!)
静态泛型方法
//泛型方法--静态方法
class Alg22 {
public static <T extends Comparable<T>> T findMaxVal(T[] array) {
T max = array[0];
for (int i = 0; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
public class demo1 {
public static void main(String[] args) {
Integer[] array = {66,799,88};
Alg22.<Integer>findMaxVal(array);
Alg22.findMaxVal(array);//<Integer> 可省略不写
}
}
解析:
![]()
每次调用findMaxVal方法都会实例化一个对象,因此我们可以把上述的泛型写成静态泛型方法,用static修饰,就可以不用new对象了,直接用类名调用~~
4.总结
一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类.如果要编写可以应用于多种类型的代码,我们就用到了泛型.
泛型的主要目的:就是指定当前的容器,要持有什么类型的对象,让编译器去检查.此时,就需要把类型作为参数传递.需要什么类型就传入什么类型.(泛型只能接收类,所有的基本数据类型必须写成包装类!)
寄语:不要忘了你希望的远方