目录
1. 什么是泛型
2. 引出泛型
2.1 泛型的语法
3. 泛型类的使用
3.1 泛型类的语法和示例
3.2类型推导
4. 擦除机制(编译)
5. 泛型的上界
5.1 语法-示例
6. 泛型方法
6.1 泛型的静态方法
6.2泛型的非静态方法
6.3 应用---比较
6.4 小结
7. 通配符
7.1 通配符解决问题
7.2 通配符上界
7.3 通配符下界
8. 包装类
8.1 基本数据类型和对应的包装类
一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的 代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
以前的学习中,我们都是通过指定的数据类型来存储数据的,比如数组,只能存放指定类型的元素。通过学习,我们知道所有类的父类,默认为Object类。数组就可以创建为Object,来实现接受各种弄个类型。
所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。
所以我们就可以理解为泛型,实际上就是将数据类型参数化,进行传递。
1.首先,我们先用object类型类替代我们使用的数据类型。
从代码运行结果来看,我们发现定义object类的话,我们可以在object数组里同时存放多种数据类型,并且可以正常输出,但是要进行强制类型转化!
2.这个时候,我们在源代码的基础上进行改写。
(除了注释1处,将object都改为T。)
(main方法中添加类型<>)
class MyArray{
public T[] obj = (T[]) new Object[3]; //注释1
public void setObj(int a,T val){
obj[a] = val;
}
public T getObj(int a){
return obj[a];
}
}
public class Test {
public static void main(String[] args) {
MyArray myArray = new MyArray<>(); //注释2
myArray.setObj(0,123);
myArray.setObj(1,3);
myArray.setObj(2,55);
System.out.println( myArray.getObj(0)); //注释3
System.out.println( myArray.getObj(1));
System.out.println( myArray.getObj(2));
}
}
1.类名后的
代表占位符,表示当前类是一个泛型类。 了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:E 表示 ElementK 表示 KeyV 表示 ValueN 表示 NumberT 表示 TypeS, U, V 等等 - 第二、第三、第四个类型 暂时理解即可2.注释1处,不可以new泛型类型的数组!!!T [] ts = new T [ 3 ]; //这样是错误的!!!3. 注释 2 处,类型后加入指定当前类型。 3.注释3处,不再需要强制类型转化。
3.在2的基础上修改一下代码如下。
这里就代码编译错误了,这是因为在注释2处指定类当前的类型,此时编译器会在存放元素的时候帮助我们进行类型检查。
需要提示的是: 在这里使用如下代码并不是足够好的方式,这个后期会进行讲解。
T[] array = (T[])new Object[3];
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
MyArray < Integer > list = new MyArray < Integer > ();
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写。
MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String
在编译的过程当中,将所有的 T替换为Object 这种机制,我们称为:擦除机制。
编译之后的字节码文件当中,都变成了Object。
Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。
所以要注意:运行的时候就没有泛型这个概念了,只有object了!!!
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
class 泛型类名称 < 类型形参 extends 类型边界 > {...}public class MyArray < E extends Number > {...}此时,只会接受Number或者Number的子类类型作为 E 的类型实参。
当我们运行MyArray<String>时 ,就会发生编译错误,因为String不是Number的子类型。
当我们没有指定类型边界的时候,就可以视为:E extends Object
稍微复杂一点的示例:
public class MyArray < E extends Comparable < E >> {...}
这个是时候E必须是实现了Comparable接口的!!!
例如如下代码中Person类是自定义的,要使用泛型类则需要实现Comparable接口。
语法格式:static
public static
T findMax (T[] array) 在main方法中使用时:Integer 变量名 = 类名.
方法名(参数) // 可省略
示例:
注意:static静态方法可直接通过类名来调用!!!
与静态方法相类似。
要注意:引用类型是不可以进行比较的,因为不确定类型。所以要实现comparable接口。
class Alg>
同时注意:T必须是实现了Comparable接口的!!!
1. 泛型是将数据类型数据化,进行传递。
2. 使用
表示当前类是一个泛型类。 3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转化。
4. <>中的类型必须是类类型,而不能是基本类型!!!
? 用于在泛型的使用,即为通配符。泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。
如下代码:
class Man{
private T man;
public T getMan(){
return this.man;
}
public void setMan(T man){
this.man = man;
}
}
public class Test5 {
public static void main(String[] args) {
Man man = new Man<>();
man.setMan("person");
func(man);
Man man1 = new Man<>();
man1.setMan(111);
func1(man1);
}
public static void func(Man man){
System.out.println(man.getMan());
}
public static void func1(Man man){
System.out.println(man.getMan());
}
}
我们可以看出我们因为func和func1的参数类型不同,定义了两个函数,此时我们就可以用通配符来进行优化。
class Man{
private T man;
public T getMan(){
return this.man;
}
public void setMan(T man){
this.man = man;
}
}
public class Test5 {
public static void main(String[] args) {
Man man = new Man<>();
man.setMan("person");
func(man);
Man man1 = new Man<>();
man1.setMan(111);
func(man1);
}
public static void func(Man man){
System.out.println(man.getMan());
}
}
易错点 :使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
注意点:在编译过程中,他们的类型都是一样的。
验证一下:
我们可以看出,输出的类型是一致的。其中
所以如下代码就会发生报错,同时也需注意到的是,这个跟重载是不一样的概念!!!
? extends 类:设置泛型上限
语法:
extends 上界 >extends Number > // 可以传入的实参类型是 Number 或者 Number 的子类
注意点:
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Fruit{
}
class Plate {
public T plate;
public void setPlate(T plate){
this.plate = plate;
}
public T getPlate(){
return plate;
}
}
public class Test {
public static void main(String[] args) {
Plate plate1 = new Plate<>();
plate1.setPlate(new Food());
func(plate1);
}
public static void func(Plate temp){
//temp.setPlate(new Food()); //错误的!!!
System.out.println(temp.getPlate()); //注释1
Food food = temp.getPlate(); //注释2
}
}
从上诉代码中,可以知道能传入的实参类型是Food或者Food的子类(Fruit,Apple,Banana)
需要注意的是:
1. 图中错误点表明了通配符的上界,不能进行写入数据(添加元素),因为temp接受的是Food或者Food的子类,这就导致了容易发生向下转型,这很显然就是错误的。
例如:实参是Fruit,然后temp.setPlate( new Apple() ) 这很显然就是错误的!!!
2. 图中注释1和注释2也说明了通配符的上界是可以进行读取数据(获取元素)的!!!
总结:通配符的上界,不能进行写入数据,只能进行读取数据。
? super 类:设置泛型下限
语法:
super 下界 >super Integer > // 代表 可以传入的实参的类型是 Integer 或者 Integer 的父类类型
注意点:
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
}
class Banana extends Fruit{
}
class Plate {
public T plate;
public void setPlate(T plate){
this.plate = plate;
}
public T getPlate(){
return plate;
}
}
public class Test {
public static void main(String[] args) {
Plate plate1 = new Plate<>();
plate1.setPlate(new Food());
func(plate1);
}
public static void func(Plate temp){
System.out.println(temp.getPlate()); //只能直接输出
//Fruit fruit = temp.getPlate(); //注释1,错误点,不能接收
temp.setPlate(new Apple()); //注释3
temp.setPlate(new Banana()); //注释4
//temp.setPlate(new Food()); //注释2,错误点
}
}
从上诉代码中,可以知道能传入的实参类型是Fruit或者Fruit的父类(Food)
需要注意的是:
1. 从上诉代码中的错误点注释1,我们可以看出通配符的下界不能用来接收(读取数据),因为Fruit是下界,可能会导致向下转型。但是可以用来输出。
2. 从错误点注释2和注释3和注释4,我们可以看出可以进行修改,可以添加形参(Fruit)本身或者它的子类(写入数据),但是不可以添加形参的父类!!!。
总结: 通配符的下界,不能进行读取数据,只能写入数据。
在 Java 中,由于基本类型不是继承自 Object ,为了在泛型代码中可以支持基本类型, Java 给每个基本类型都对应了 一个包装类型。
其中需要特殊记忆的就是Integer和Character。