泛型俗称“标签”,使用
泛型意味着编写的代码可以被很多不同类型的对象重用。例如集合ArrayList,如果集合不添加泛型,里面可以存储任何类型也就是Object,当添加泛型的时候,提高了代码的重用。
泛型提供了类型参数,比如ArrayList类有一个类型参数来指示元素的类型,使得代码具有更好的可读性,一看就知道数组列表中包含的是String对象。
ArrayList list = new ArrayList();
在Java SE 7 以后的版本中,构造函数中可以省略泛型。
ArrayList list = new ArrayList<>();
当代码进行编译的时候,编译器知道ArrayList
泛型类的表示方式在类后面添加
一个泛型类就是具有一个或者多个类型变量的类。
举例:
public class Pair {
private T first;
private T second;
public Pair(){}
public Pair(T first,T second){
this.first = first;
this.second = second;
}
public void setFirst(T first){
this.first = first;
}
public T getFirst(){
return first;
}
public void setSecond(T second){
this.second = second;
}
public T getSecond(){
return second;
}
}
Pair类引入了一个类型遍历的变量T,放在类名的后面。泛型类也可以引用多个变量。例如:
public class Pair{}
类定义的类型变量T,可以指定方法和局部变量的返回类型。
private T first;
public T getFirst(){
return first;
}
假如T的类型是String,那就是:
private String first;
public String getFirst(){
return first;
}
补充:在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字和值的类型。T表示任意类型。
总结:泛型类可以看成普通类的工厂。需要什么类型的,泛型类就会类的后面添加什么类型的属性和方法。
泛型方法的定义:主要还是看的返回值类型是一个泛型
全选修饰符 返回值类型 方法名
pulic T getName(){
}
举个简单的案例说明泛型方法的使用:
泛型的意思就是说类型可以在后面指定,但是仍然需要告诉编译器,我需要某个特定类型作为占位符。比如T:
public List f(T a){}
编译器会问,T是什么,我怎么不认识?我并不知道这个T是类还是泛型的方法?程序员需要声明一下。
public List f(T a){}
共有三个T,第一个T用来声明类型参数的,后面的两个T才是泛型的实现。
看下
/*
T可以传入任何类型的list
关于参数T的说明:
第一个T表示是一个泛型
第二个T表示方法返回的是T类型的数据
第三个T表示集合List传入的数据是T类型
*/
private T getStudent(List list){
return ;
}
虚拟机没有泛型类型的对象——所有对象都是属于普通类。
类型擦除:
①原始类型相等
Java泛型是一个伪泛型,这是因为Java在编译期间所有泛型信息都会被擦除掉。Java泛型基本上都是在编译器这个层次上实现的,在生成字节码中是不包括泛型中的类型信息,使用泛型的时候添加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
如在List
下面来举例理解泛型擦除:
以下我们定义了两个ArrayList集合,一个是ArrayList
public class Test {
public static void main(String[] args) {
ArrayList list1 = new ArrayList();
list1.add("abc");
ArrayList list2 = new ArrayList();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}
②通过反射添加其他类型的元素
在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,当我们利用反射调用的时候,却可以存储字符串,这说明Integer泛型实例在编译之后被擦除了,只保留了原始类型。
public class Test {
public static void main(String[] args) throws Exception {
ArrayList list = new ArrayList();
list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
list.getClass().getMethod("add", Object.class).invoke(list, "asd");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
③类型擦除后保留的原始类型
原始类型就是擦除后的泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,响应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定类型使用的是Object进行替换)替换。下面的T是无限定类型,使用的是Object进行替换。因为在Pair中,T是一个无限定的类型,所以使用的是Object进行替换
class Pair {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
④翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器将进行强制类型转换。例如下面语句:
Pair buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst()返回类型后将返回Object类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
⑤翻译泛型方法
类型擦除也会出现在泛型方法中,T被擦除,留下的只是限定了下,比如
public static T min(T[] a)
// 擦除方法后变成
public static Comparable min(Comparable[] a)
小结:
通配符说白了就是占位符,这个位置我先占着,等用了再说。
①上界通配符:
上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
类型参数中如果有多个类型的参数,用逗号分开。
Pair extends Employee>
private E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
②下界通配符:
用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
private void test(List super T> dst, List src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List dogs = new ArrayList<>();
List animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
上界通配符主要用于读数据,下界通配符主要用于写数据。
③?和 T 的区别
T:指定集合元素只能是T类型
List list = new ArrayList();
?:集合元素可以是任意类型,没有任何意义,就是个占位符,一般方法中,只是为了说明用法
List> list = new ArrayList>();
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :
// 可以
T t = operate();
// 不可以
?car = operate();
反射允许你在运行时分析任何对象。Class
在实例化的时候,T 要替换成具体类。Class>
它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。
应用,举例:
比如我们常用的数据访问对象DAO层,当我们对数据库表进行操作的时候,不知道是哪一张表的操作,所以可以定义一个泛型类来实现
然后再定义一个方法继承DAO,比如以下的继承,类CustomerDAO可以直接使用DAO
泛型在继承方面的体现,类A是类B的父类,G 和G二者不具备子父类的关系,二者是并列的关系
List