以下是学习笔记,跟上一次的来源一样。

1、为什么引入泛型:

泛型可以给我们带来极高的代码复用性,它可以改变参数的类型,让我们避免了写多个方法重载。泛型适用于多种数据类型执行相同的方法。

2、理解下面的知识点时候一定要清楚的是:Java程序编写时有编译器在检查,运行时是Java虚拟机在执行。这一点一定要清楚,否则在后面理解类型擦除会很困难。

3、泛型可以分为泛型类、泛型接口、泛型方法、泛型的变量。

4、泛型类:

class Point<T>{         // 此处可以随便写标识符号,T是type的简称  
    private T var ;     // var的类型由T指定,即:由外部指定  
    public T getVar(){  // 返回值的类型由外部决定  
        return var ;  
    }  
    public void setVar(T var){  // 设置的类型也由外部决定  
        this.var = var ;  
    }  
}  
public class GenericsDemo06{  
    public static void main(String args[]){  
        Point<String> p = new Point<String>() ;     // 里面的var类型为String类型  
        p.setVar("it") ;                            // 设置字符串  
        System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
    }  
}

多元泛型:即有两个或以上的泛型类标识。

2、泛型接口:

不太好意会,看代码吧:

interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    private T var ;             // 定义属性  
    public InfoImpl(T var){     // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
} 
public class GenericsDemo24{  
    public static void main(String arsg[]){  
        Info<String> i = null;        // 声明接口对象  
        i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象  
        System.out.println("内容:" + i.getVar()) ;  
    }  
}

3.泛型方法:首先它不同于泛型类里面定义的那些参数是泛型的方法,它的返回值也是泛型。

泛型方法是一种在不是泛型的类里面也能使用的泛型方法,而在泛型类里面定义的方法会比较容易。

4、泛型的上下限:泛型的上下限就是对泛型的泛型范围做的限制,泛型的上限就是通过extends或者super关键字来限定范围。

上限extends:

class Info<T extends Number>{    // 此处泛型只能是数字类型
   
}
public class demo1{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象
    }
}

这上面的T是继承自Number的子类,即所填的泛型都是Number的子类;所以Number就称之为此泛型的上限。

下限super:

<? super E>

问号部分的泛型是E的父类,即所有泛型都要比E的等级高,所以E是该泛型范围的下限。

限制也可以是多个,使用一个&号连接。

5、泛型数组:泛型数组不能直接声明使用泛型实例化初始化,而要使用类型通配符<?>,使用具体的实例化初始化,在最后取出值的时候会报出类型转换错误的提示。因为当实例化后,在后面运行时编译器对该泛型执行类型擦除,使之变成了object类型,而存储的也是object类型,如果想以String类型取出,那么就要强转,多余了。但是使用类型通配符,本来就没有固定使用哪个实例,所以取出来的时候肯定是要转型的,也就不多余了。

6、泛型擦除:

Java的jvm中本就没有泛型类,所以在程序执行时,jvm对待泛型类其实就相当于对待一个普通类。原理就是泛型擦除:当编译时,将所有的泛型都转换成一个顶级的类,如果该泛型没有上下限的限制,那么编译时,就会以Object类来替换泛型。如果有上下限,那么就用第一个边界的类型来替换。<T extends Comparable>,那么就要用Comparable来替代。

2.在一个泛型方法中,如果不指定泛型,那么就以输入的值的同一类型的父类的最小级。直到Object,若是指定了类型:那么输入的值就要以该类型为准,只能小于等于该类型,不能超过。

7、重要问题:编译时已经对泛型进行了擦除,变成了Object,或者最高父类等等,那么还能只存储对应的数据类型吗?

答:Java编译器是通过先检查对应的泛型的数据类型,然后再进行擦除,最后再编译。也就是过程类似于:先在代码中查看一下所填的数据类型,然后在进行一下类似于标记的过程,告诉编译系统,这里要填写指定的类型,如果填写其他类型就会报错,之后再对泛型进行擦除,然后当填写数据时,会对填写的数据进行监控,看是否是指定类型,因为这时已经擦除了泛型,所以为最高的父类,填写的信息也一定不会报错,因为填写信息的数据类型都是继承自该类,最后填写完编译执行时,若后面需要使用该泛型的数据或者有泛型方法返回值等等使用时,编译系统会自动根据上下文来推理这里需要什么,来自动插入类型转换语句。最后编译执行。

所以整个从开始写泛型类,到最后使用,程序执行。整个过程的jvm一直是只对泛型的原始类型,也就是相当于对应了一个普通类来看。整个过程发挥泛型作用的其实是编译系统。

8、泛型类不能使用基本类型:
这与前面的泛型擦除有关,因为泛型擦除后会变为Object或者其他类,他们都不能接收一个基本数据类型做对象,所以必须使用包装类来存储,因为包装类也是继承自Object,所以就可以存储。也可以进行相应的强制转换等等。

9、泛型数据类型不能实例化:

因为在实例化时并没有对应的类字节码信息,另外就算有,也是擦除后的类型,所以也就没有必要去实例化一个擦除后的类型。

10、重要问题:泛型的多态实现和桥接方法

泛型的多态情况是:当某个类继承了一个泛型时,该泛型已经确定了某个数据类型,如果确定了某个类型后,那么要重写这个泛型类的方法,方法的返回值要变成该数据类型,当我们使用该子类去实例化后调用某些重写的方法时,我们知道编译后,泛型就已经擦除,已经不是原来泛型类里面的数据类型了,而且这时父类泛型类里面的参数类型和返回值和子类里面的已经不是相同的。当你用子类给父类实例化时,这时会使用桥接方法,即编译器会在子类里面生成一个桥方法,该方法和父类泛型擦除后的方法相同,在jvm看来这才是真正意义上的方法重写的函数。jvm会先执行多态的方法,编译看左边执行看右边,执行子类的桥方法,

class Pair<T> {  

    private T value;  

    public T getValue() {  
        return value;  
    }  

    public void setValue(T value) {  
        this.value = value;  
    }  
}
class DateInter extends Pair<Date> {  

    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  

    //这里会有一个桥方法:
    
    public void setValue(Object Second){ setValue((Date) value) };
}

所以先调用桥方法,然后桥方法强制转换后调用子类中的重写方法。

11、泛型数组:

没有必要使用泛型数组,使用List等等集合恒方便。

12、泛型中的静态变量和静态方法:

静态变量和静态方法不能使用泛型符修饰,因为泛型是通过实例化时定义对象传入的,而静态的东西属于类而不属于对象,静态的东西随着类加载一起加载,所以不能修饰。

13、但是泛型的泛型方法不一样。其中的泛型是在调用方法时创建的。

14、注意:静态方法和泛型方法都有static修饰。但是静态方法没有自己创建的泛型变量。

15、异常中的泛型:

因为异常捕获的原则,一定是子类在前面,父类在后面

public static <T extends Throwable> void doWork(Class<T> t){
    try {

    } catch(T e) { //编译错误

    } catch(IndexOutOfBounds e) {

    }                         
}

那么这个就不对了。所以不能用,但是异常声明还是可以用的。