泛型
1、什么是泛型?
所谓的泛型就是指类型参数化。
通俗的来说,就是将类型定义成一个变量并进行参数声明,在使用时传入类型实参来明确具体的类型。
public class Demo {
public <E> void method(E e) {
//这是一个泛型方法,它包含一个类型变量E
System.out.println(e);
}
}
Demo demo = new Demo();
//我们调用method的时候必须传入类型实参来明确变量E
demo.<String>method("hello");
2、为什么要定义泛型?
在JDK1.5以前,想要接收或返回多种类型,只能接收或返回它们的父类(比如集合的接收类型为Object)。这样在使用对象时就要在必要的地方加上强制转型代码,容易在运行时期出现类型转换异常(ClassCastException)。 List list = new ArrayList();
list.add("abc");
// 因为接收类型为Object,所以我们有可能误装入了Integer对象
list.add(4);
for (Object obj : list) {
//在迭代过程中,我们不知道有Integer对象,进行了向下转型
//这里会发生ClassCastException
String str = (String) obj;
System.out.println(str.length());
}
而有了泛型,它允许将类型定义成一个变量。那么,对于接收或返回多种类型的情况,我们可以把接收或返回的类型定义成变量,在具体使用时再传入类型实参来明确。比如,当我们给List<E>中传入了具体实参String后,List<String>就明确了其add方法为add(String e)。
//在使用List的时候,我们传入了参数String。那么其add方法也由add(E e),变为add(String e)
List<String> list = new ArrayList<String>();
list.add("abc");
// 由于add方法的接收类型变成了String,此时添加Integer对象就会出现编译报错
list.add(4);
3、泛型与函数的对比
函数→要处理的数据值不确定→定义成一个变量并进行参数声明→调用函数时必须传递实际参数
泛型→要操作的数据类型不确定→定义成一个变量并进行参数声明→使用泛型类、接口、方法时必须传递实际参数
注意:泛型同函数一样,传入的实际参数可以是一个确定的类型,也可以是一个类型变量。
public interface List<E> extends Collection<E>
//向Collection中传入的时List的类型变量E
4、泛型的通配符
同一个类的泛型类之间是没有继承关系的,比如List<String>和List<Object>是两个不同的类,而且并没有继承关系。
如果我们想定义一个引用来接收不同泛型类的对象(类似与父类接收子类对象),可以使用通配符 ?。
比如List<?>可以接收List的所有泛型类对象,其中 ? 表示任意类型。
通配符在使用的过程中可以进行上限和下限的限定,几种写法对比如下:
List<Person> //只能接收一种类型的对象
List<?> //可以接收所有泛型类的对象
List<? extends Person> //可以接收所有是Person子类的对象(包括Person)
List<? super Person> //可以接收所有是Person父类的对象(包括Person)
5、泛型的擦除
泛型实际上是Java的一颗语法糖,它在运行时期并不存在,仅仅是Java在编译时期的行为。
Java在编译期认为List<String>和List<Integer>是两个不同的类,然后进行了相关代码的检查。在编译通过后,Java编译器就自动在对象进入和离开方法的边界处添加了强制类型转换。而在运行时期,所谓的List<String>,List<Integer>类,以及类型变量,都是不存在的。List<String>,List<Integer>的对象在运行时期是相同的类型(都是List类),而类型变量都会被相应的具体类型(比如Object)替换掉。
所以在我们使用时,从List<String>对象中取出的是String,只是因为编译器添加了强制转换代码。
6、泛型类,泛型接口,泛型方法
a) 泛型类
所谓的泛型类,就是把泛型定义在类上,在继承或者实例化类的时候,由使用者来传入类型实参。
//用<>在类上声明,类中有一个类型变量E
class Tool<E>{
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
Tool<String> tool = new Tool<String>();
注意:静态变量与静态方法不能调用类上的泛型。
如果静态方法需要定义泛型,泛型只能定义在方法上。
b) 泛型方法
泛型类存在一个问题,就是其传参过程发生在实例化时期。一旦对象实例化完成,类中的类型变量就被固定不再改变。如果想再次传参只能重新实例化对象。
class Demo<E>{
public void show1(E e) {
System.out.println(e);
}
public void show2(E e) {
System.out.println(e);
}
}
Demo<String> d1 = new Demo<String>();
d1.show1("abc");
//编译失败,无法传入4,因为d1中类型变量已经定为String了
d1.show2(4);
Demo<Integer> d2 = new Demo<Integer>();
//只能通过重新实例化对象来重新传参
d2.show2(4);
而泛型方法解决了这个问题,泛型方法的传参过程是方法的调用时期。
class Demo<E>{
public void show1(E e) {
System.out.println(e);
}
public <T>void show2(T e) {
//这是一个泛型方法,类型变量为T
System.out.println(e);
}
}
Demo<String> d1 = new Demo<String>();
d1.show1("abc");
//show2调用时才进行单独的传参
d1.<Integer>show2(4);
c) 泛型接口
将泛型定义在接口上。
在实现接口的时候进行传参。
interface Inter<E>{
public void show(E e);
}
class Demo1 implements Inter<String>{
//传递一个具体类型作为实参
}
class Demo2<T> implements Inter<T>{
//传递一个类型变量作为实参
}