泛型的使用与内部类的刨析

泛型的使用与内部类的刨析
一、泛型(generic)的定义
 
泛型是能够将一个类型转变为多个类型使用。例如一个单链表中不用泛型则我们实现的时候只能放入一种类型的数据,但是用泛型后能够将多种类型的数据都能放入。
 
1.泛型的语法
 
class 泛型类名称<类型形参列表> {undefined
 
// 这里可以使用类型参数
 
}
 
class ClassName<T1, T2, …, Tn> {undefined
 
}
 
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {undefined
 
// 这里可以使用类型参数
 
}
 
class ClassName<T1, T2, …, Tn> extends ParentClass<T 1> {undefined
 
// 可以只使用部分类型参数
 
}
 
尖括号当中也可以定义多个类型实参。意义是能够指定多种类型的存储。
 
类型形参一般使用一个大写字母表示,常用的名称有:
 
E 表示 Element
 
K 表示 Key
 
V 表示 Value
 
N 表示 Number
 
T 表示 Type
 
S, U, V 等等 - 第二、第三、第四个类型
 
2.泛型的简单使用示例
 
class Stack<T> {
 
    public T[] objects;
 
    public int top;
 
    public Stack() {
 
        this.objects = (T[])new Object[10];
 
    }
 
    public void push(T obj) {
 
        objects[this.top++] = obj;
 
    }
 
    public T get() {
 
        return objects[this.top-1];
 
    }
 
}
 
二、泛型类的使用
 
1.含有泛型类的泛型对象的创建
 
泛型类<类型实参> 变量名; // 定义一个泛型类引用
 
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
 
示例:MyArrayList<String> list = new MyArrayList<String>();
 
不过后面尖括号当中的类型实参可以省略,但是必须要加上;省略里面的类型实参编译器会自动推导。如:MyArrayList<String> list = new MyArrayList<>(); // 可以推导出实例化需要的类型实参为 String
 
2.裸类型(Raw Type)
 
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型。如:MyArrayList list = new MyArrayList();
 
3.泛型的类型边界
 
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
 
语法:
 
class 泛型类名称<类型形参 extends 类型边界> { … 
 
}
 
示例:
 
public class MyArrayList<E extends Number> {
 
  …
 
}
 
extends后面的称为泛型的上界,即传入的实参类型只能是泛型上界它的子类或者泛型上界它本身。
 
在上面示例的基础上,在创建泛型类的对象时,只能创建Number类的子类或者它本身。如:
 
MyArrayList<Integer> l1; // 正常,因为 Integer 是 Number 的子类型 MyArrayList<String> l2; // 编译错误,因为 String 不是 Number 的子类型
 
1
 
了解: 没有指定类型边界 E,可以视为 E extends Object 。
 
泛型是没有下界的,只有上界;而通配符是存在上界和下界的,下面会讲到。
 
4.泛型的类型擦除机制
 
**泛型是在编译时期的一种机制–擦除机制。编译的时候,会因为擦除机制泛型参数全部被擦除为Object类型,因此它能够将泛型里面的引用类型参数化了。**因此,我们在一个泛型类当中定义一个泛型数组时,创建的泛型数组实例化要用Object[]的数组强制转换为泛型类型。如:T[] objects = (T[])new Object[10];。
 
注:我们不能直接new 泛型类型的数组,如T[] t = new T[];,因为泛型是先检查后编译的,检查的时候不知这个T是哪个类型,而编译的时候直接将其擦除为Object。因此有T[] t = (T[])new Object[]。
 
泛型的<>里面的内容,不构成类型的组成。
 
如:
 
public class Test {
 
    public static void main(String[] args) {
 
        Algorithm<Integer> algorithm = new Algorithm<>();
 
        System.out.println(algorithm);
 
    }
 
}
 
打印结果:Algorithm后面<>的参数类型不见了,可见它不构成类型的组成
 
5.泛型E extend 接口使用实例
 
如:定义一个泛型类搜索树。
 
public class BSTree<K extends Comparable<K>> { 
 
   … 
 
}
 
传入的K必须是实现了Comparable接口的引用类型,并且Comparable里面的K是比较的类型。
 
它的意思是:将泛型传入的参数擦除到实现Comparable接口的类型当中。又因为Object类中没有实现Comparable接口,因此我们要实现引用的比较时要定义上界的类有实现Comparable接口。
 
6.泛型的意义
 
泛型的意义:
 
存数据的时候,会自动进行类型的检查。
 
取数据的时候,进行类型的自动转换。
 
例如:
 
Stack当中的objects数组的类型为Object[],里面能够放入所有类型的数据,若取出某一个数据时,需要强制类型转换。
 
class Stack {
 
    public Object[] objects;
 
    public int top;
 
    public Stack() {
 
        this.objects = new Object[10];
 
    }
 
    public void push(Object obj) {
 
        objects[this.top++] = obj;
 
    }
 
    public Object get() {
 
        return objects[this.top-1];
 
    }
 
}
 
public class TestDemo {
 
    public static void main(String[] args) {
 
        Stack stack = new Stack();
 
        stack.push(1);
 
        stack.push(2);
 
        stack.push("zjr");
 
        String str = (String)stack.get();
 
    }
 
而泛型能够完美地解决这一问题。它能够指定一个数组当中放入的是什么类型(可以指定一种类型,也可以指定多种类型)的数据,因此它能够自动帮我们做类型的检查。当我们取出某个数据时,如上面例子中的String str = (String)stack.get();,在泛型下不需要强制类型转换,直接String str =stack.get();即可。

推荐阅读