一、为什么要使用泛型?
泛型俗称“标签”,使用<E>表示。泛型就是在允许定义类,接口时通过一个标识表示某个属性的类型或者是某个方法的返回值或者是参数类型,参数类型在具体使用的时候确定,在使用之前对类型进行检查。
泛型意味着编写的代码可以被很多不同类型的对象重用。例如集合ArrayList,如果集合不添加泛型,里面可以存储任何类型也就是Object,当添加泛型的时候,提高了代码的重用。
泛型提供了类型参数,比如ArrayList类有一个类型参数来指示元素的类型,使得代码具有更好的可读性,一看就知道数组列表中包含的是String对象。
ArrayList<String> list = new ArrayList<String>();
在Java SE 7 以后的版本中,构造函数中可以省略泛型。
ArrayList<String> list = new ArrayList<>();
当代码进行编译的时候,编译器知道ArrayList<String>添加元素的类型是String类型。当编译的时候,编译器可以对调剂的元素进行检查,避免错误类型的对象,出现和集合中的泛型不匹配的对象是无法通过编译。泛型的好处在于使得程序具有更多的可读性和安全性。
二、定义简单的泛型类
泛型类的表示方式在类后面添加<T>,T是占位符。
一个泛型类就是具有一个或者多个类型变量的类。
举例:
public class Pair<T> { 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,U>{}
类定义的类型变量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> T getName(){ }
举个简单的案例说明泛型方法的使用:
泛型的意思就是说类型可以在后面指定,但是仍然需要告诉编译器,我需要某个特定类型作为占位符。比如T:
public List<T> f(T a){}
编译器会问,T是什么,我怎么不认识?我并不知道这个T是类还是泛型的方法?程序员需要声明一下。
public <T> List<T> f(T a){}
共有三个T,第一个T用来声明类型参数的,后面的两个T才是泛型的实现。
看下<T> T 和 T的用法和区别
<T> T 表示返回值是一个泛型,传递什么,就返回什么类型的数据。 T 表示传递的参数类型。下面依次举例:
<T> T返回值是一个T类型,这个T是<T> T中的第二个T,告诉编辑器,我传递什么你就给我返回什么样的数据类型。
/* <T> T可以传入任何类型的list 关于参数T的说明: 第一个T表示<T>是一个泛型 第二个T表示方法返回的是T类型的数据 第三个T表示集合List传入的数据是T类型 */ private <T> T getStudent(List<T> list){ return ; }
四、泛型代码和虚拟机
虚拟机没有泛型类型的对象——所有对象都是属于普通类。
类型擦除:
①原始类型相等
Java泛型是一个伪泛型,这是因为Java在编译期间所有泛型信息都会被擦除掉。Java泛型基本上都是在编译器这个层次上实现的,在生成字节码中是不包括泛型中的类型信息,使用泛型的时候添加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
如在List<Object>,List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型的附加信息对JVM是看不到的。Java编译器会在编译期间尽可能的发现出错的地方,但是无法在运行时刻出现类型转换和类型转换异常的情况。
下面来举例理解泛型擦除:
以下我们定义了两个ArrayList集合,一个是ArrayList<String>,一个是ArrayList<Integer>的泛型类型,最后我们通过获取他们类对象的getClass()的信息,结果为true。说明String和Integer都背擦除了,剩下的只是泛型类型。无论何时定义一个泛型,都自动提供一个相应的原始类型。原始类型的名字就是删去类型数后的泛型类型名。擦除类型变量,并替换成限定类型。
public class Test { public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<String>(); list1.add("abc"); ArrayList<Integer> list2 = new ArrayList<Integer>(); 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<Integer> list = new ArrayList<Integer>(); 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<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
④翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器将进行强制类型转换。例如下面语句:
Pair<Employee> buddies = ...; Employee buddy = buddies.getFirst();
擦除getFirst()返回类型后将返回Object类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
- 对原始方法Pair。getFirst()调用
- 将返回的Object类强制转换为Employee类型
⑤翻译泛型方法
类型擦除也会出现在泛型方法中,T被擦除,留下的只是限定了下,比如
public static <T extends Comparable> T min(T[] a) // 擦除方法后变成 public static Comparable min(Comparable[] a)
小结:
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都用他们的限定类型进行替换
- 为了保持类型的安全性,必要时插入强制类型转换
五、通配符
通配符说白了就是占位符,这个位置我先占着,等用了再说。
①上界通配符:
上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
类型参数中如果有多个类型的参数,用逗号分开。
Pair<? extends Employee>
private <K extends A, E extends B> E test(K arg1, E arg2){ E result = arg2; arg2.compareTo(arg1); //..... return result; }
②下界通配符:
用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
private <T> void test(List<? super T> dst, List<T> src){ for (T t : src) { dst.add(t); } } public static void main(String[] args) { List<Dog> dogs = new ArrayList<>(); List<Animal> animals = new ArrayList<>(); new Test3().test(animals,dogs); }
上界通配符主要用于读数据,下界通配符主要用于写数据。
③?和 T 的区别
T:指定集合元素只能是T类型
List<T> list = new ArrayList<T>();
?:集合元素可以是任意类型,没有任何意义,就是个占位符,一般方法中,只是为了说明用法
List<?> list = new ArrayList<?>();
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :
// 可以 T t = operate(); // 不可以 ?car = operate();
六、反射和泛型,Class<T>
反射允许你在运行时分析任何对象。Class<T>
在实例化的时候,T 要替换成具体类。Class<?>
它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。
应用,举例:
比如我们常用的数据访问对象DAO层,当我们对数据库表进行操作的时候,不知道是哪一张表的操作,所以可以定义一个泛型类来实现
然后再定义一个方法继承DAO,比如以下的继承,类CustomerDAO可以直接使用DAO<Customer>,对应的是数据库中的Customer的表
七、泛型之间的继承关系
泛型在继承方面的体现,类A是类B的父类,G<A> 和G<B>二者不具备子父类的关系,二者是并列的关系
List<Object> list1 = null; List<Integer> list2 = null; // 二者不具备子父类关系,二者的关系是并列的 list1 = list2;
标签:ArrayList,List,类型,详解,擦除,泛型,public From: https://www.cnblogs.com/yitongtianxia666/p/17875776.html