目录
一、包装类
基本数据类型和对应的包装类
装箱和拆箱
【思考题】
二、泛型
什么是泛型
引出泛型
怎么定义泛型和使用泛型
裸类型(Raw Type)
擦除机制
额外,注意下列代码:
泛型的上界
泛型的接口应用
泛型方法
一、包装类
简单来说:为了服务泛型,把基本类型做成一个引用类型。
基本数据类型和对应的包装类
基本数据类型 | 包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
总结:除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。
装箱和拆箱
public class Type {public static void main(String[] args) {int i=10;double d=1.0;//显式装箱Integer i1=Integer.valueOf(i);Double d1=Double.valueOf(d);//隐式装箱Integer i2=i;Double d2=d;//显式拆箱int i3=i1.intValue();double d3=d2.doubleValue();//隐式拆箱int i4=i1;double d4=d1;System.out.println(i1);System.out.println(d1);}
}
//执行结果
10
1.0
从上面代码中,可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。 也就是自动(隐式) 装 / 拆箱
小小的思考:
对于Integer来说,这是个类(引用类型),为什么能直接打印值呢?而不是地址呢?
答:在Integer源码里面,它自己实现了重写toString()方法,可以直接输出值。
【思考题】
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
false
为什么两次打印会不一样呢?
答:在Integer这个包装类中,Integer可以允许的值是-128~127,如果输入的值在这个区间以内,就直接返回区间的值;也就是说128超出了这个范围,这时候会new一个新的Integer出来,那让我们看看下面源码是怎么写的:
所以说上述的思考题中,128超出了这个区间,new出的两个空间,这时候他们的地址都是不一样的,==比较的是地址,所以返回false。
二、泛型
什么是泛型
泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
引出泛型
实现一个类:
类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?
思路:
1. 我们以前学过的数组,只能存放指定类型的元素,
例如:int[] array = new int[10]; String[] strs = new String[10];
2. 所有类的父类,默认为Object类。数组是否可以创建为Object?
代码如下:
class MyArray{Object[] array=new Object[10];public Object getPos(int pos){return this.array[pos];}public void setPos(int pos,Object x){this.array[pos]=x;}
}
public class Test {public static void main(String[] args) {MyArray myArray=new MyArray();myArray.setPos(0,10);myArray.setPos(1,2);myArray.setPos(2,"hello");String s1=myArray.getPos(2);//编译报错String s2=(String)myArray.getPos(2);//需要强转}
}
//执行结果
java: 不兼容的类型: java.lang.Object无法转换为java.lang.String
这时我们会有两个问题:
1.这个数组中又有String又有int是不是太混乱了;
2.强转可行,那每次我都需要强转是不是太麻烦了
我期望的是:
你能接收我指定类型的数据,传什么你能放什么,还可以直接使用。
你上面的代码实现的只是:
Object不能把类型作为参数传递,它只是能接收任何类型的数据,毕竟不管是什么类型,都需要强转。
虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望他只能够持有一种数据类型。而不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。意义:数据类型参数化,编译时自动进行类型检查和转换
简单来说,泛型就是来优化上面那个代码的,让它能统一放进你想放的类型,还不需要强制类型转化。解决了上面两个问题。
怎么定义泛型和使用泛型
class 泛型类名称<类型形参列表> {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
// 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分类型参数
}
所以根据语法,我们可以写出下列代码:
package demo2;//<T>代表的这是一个泛型类(有<>都是泛型)
class MyArray<T>{Object[] array=new Object[10];//1//除了上面这一条代码,把下列所有Object替换成Tpublic T getPos(int pos){return (T)this.array[pos];}public void setPos(int pos,T x){this.array[pos]=x;}
}
public class Test {public static void main(String[] args) {/* MyArray后面加个<>,里面放的是你要存放的类型,而后面MyArray的<>,里面可以不写,编译器可以自动判断 */MyArray<Integer> myArray=new MyArray<>();//2myArray.setPos(1,2);myArray.setPos(2,3);\int ret=myArray.getPos(1);//3myArray.setPos(3,"hello");//4,这时候会编译报错String s=(String) myArray.getPos(3);//强转都不行}
}
//执行结果
java: 不兼容的类型: java.lang.String无法转换为java.lang.Integer
1. 类名后的代表占位符,表示当前类是一个泛型类
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:
- E 表示 Element
- K 表示 Key
- V 表示 Value
- N 表示 Number
- T 表示 Type
- S, U, V 等等 - 第二、第三、第四个类型
2. 注释1处,不能new泛型类型的数组
T[] ts = new T[5];//是不对的
3. 注释2处,类型后加入指定当前类型。注意:泛型只能接受类,所有的基本数据类型必须使用包装类!
4. 注释3处,不需要进行强制类型转换
5. 注释4处,代码编译报错,此时因为在注释2处指定类当前的类型,此时在注释4处,编译器会在存放元素的时候帮助我们进行类型检查。
裸类型(Raw Type)
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型。
MyArray list = new MyArray();
注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制。
擦除机制
那么,泛型到底是怎么编译的?
通过命令:javap -c 查看字节码文件,所有的T都是Object。
在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
提出问题:
1、那为什么,T[] ts = new T[5]; 是不对的,编译的时候,替换为Object,不是相当于:Object[] ts = new Object[5]吗?
答:我编译的时候不知道你的T是谁,我是在擦除机制擦完之后才知道你的T是谁。所以当我new一个对象时,我必须知道T是谁,它必须有一个具体的类型。否则它语法都无法通过。
2、类型擦除,一定是把T变成Object吗?
答:在Java中,类型擦除是指在泛型代码编译过程中,编译器将所有泛型类型参数替换为其上界或Object类型。(下个知识点)
额外,注意下列代码:
class MyArray<T>{public T[] array=(T[])new Object[10];public T getPos(int pos){return (T)this.array[pos];}public void setPos(int pos,T x){this.array[pos]=x;}
//返回这个数组public T[] getArray(){return array;}
}
public class Test {public static void main(String[] args) {MyArray<String> myArray=new MyArray<>();myArray.setPos(1,"hello");String[] s=myArray.getArray();//运行出错}
}
//执行结果
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String; ([Ljava.lang.Object; and [Ljava.lang.String; are in module java.base of loader 'bootstrap')
为什么这里会报错呢,报的类型转换异常(强转都不行)
问题:既然T的本质是Object,Object又是所有类的父类,那Object给到子类,为什么不行呢?
答:Object是String的父类没错,但是这里是个数组,Object[]就不是String[]的父类了。
泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名称<类型形参 extends 类型边界> {
...
}
//示例
public class MyArray<E extends Number> {
...
}
只接受 Number 的子类(或者Number自己)型作为 E 的类型实参
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型error: type argument String is not within bounds of type-variable E
MyArrayList<String> l2;
^
where E is a type-variable:
E extends Number declared in class MyArrayList
了解: 没有指定类型边界 E,可以视为 E extends Object
泛型的接口应用
class findMax<T extends Comparable<T>>{public T find(T[] arr){T max=arr[0];for (int i = 1; i < arr.length; i++) {if(arr[i].compareTo(max)>0){max=arr[i];}}return max;}
}
public class Test {public static void main(String[] args) {findMax<Integer> findMax=new findMax<>();Integer[] arr={1,4,6,2,1,3};System.out.println(findMax.find(arr));}
}
T必须是实现了Comparable接口的,而<T>是Comparable也要是泛型。
泛型方法
class method{public<T extends Comparable> T find(T[] arr){T max=arr[0];for (int i = 1; i < arr.length; i++) {if(arr[i].compareTo(max)>0){max=arr[i];}}return max;}
}
public class Test {public static void main(String[] args) {findMax<Integer> findMax=new findMax<>();Integer[] arr={1,4,6,2,1,3};System.out.println(findMax.find(arr));}
}
相当于:把接口应用那个方法直接拿起来用,但是要在方法的前面加上<T extends Comparable>
在方法那里加上Static就是静态的泛型方法,使用的时候用类名调用就好了。