泛型在JDK1.5被加入(也可以叫做参数化类型),使JAVA中的集合(List ,set, map…)能够记住被添加的元素的类型,但不是只有java中的集合才能用泛型。接下来通过介绍泛型被引入的意义和作用以及他的使用场景来一步步回顾泛型。
1.泛型的写法
List<String> list = new ArrayList<String>();
在类的后面跟上<>,里面写上类型参数,这就是泛型的一般写法,因为很像菱形,又被称为菱形语法
Java7开始,可以在创建实例的时候不写右边<>中的参数,比如:
List<String> list = new ArrayList<>();
2.泛型被引入的意义
一.让集合记住传入的参数类型,避免运行时强制类型转化的繁琐和出现ClassCastException。 在JDK1.5之前,集合是没有泛型支持的,它允许添加任何类型的元素,但是它们的数据类型都会被“遗忘”,在编译期会被当作Object类型处理,(但是运行时类型没有变化),我们在取出元素使用的时候就需要利用类型转换得到原始类型,这样会增加出现ClassCastException异常的风险。引入泛型后,集合会记住添加的元素类型,同时,添加的时候如果不是指定的类型,会给出泛型警告,无法通过编译,泛型机制保证,编译期不出现警告,运行时就不会出现ClassCastException异常。
二. 如上所说,泛型是一种参数化类型概念的实现,继承父类或实现接口是能够更灵活的实例化子类,比如定义一个类:
public class Animal<T>{}
在创建子类的时候,可能需要的参数类型不同,可以让一个父类提供不同的参数需求
public class Cat extends Animal<String>{}
public class Dog extends Animal<Integer>{}
注意:继承或实现泛型类型的父类或接口的时候,不能带类型形参
public class Cat extends Animal<T>{}
这样是不正确的,不能带类型形参< T >,可以不带或像上面传入实参
3.定义泛型类和接口
泛型类和泛型接口定义没有什么区别(类型形参经常是用一个大写字母表示)
//泛型类
public class Animal<T>{
public void set(T t){
.....
}
public T get(){
return ....
}
}
//泛型接口
public interface Animal<T>{
void set(T t);
T get();
}
//泛型接口和类的形参T也可以作为内部方法和变量的类型,如代码中所写,在创建子类的时候传入实参,T就会被替换成实参
注意:泛型类和不加泛型,类本身没有变化,还是同一个类.
Animal<T>和Animal没有什么不同,java并没有把Animal<T>编译成单独的类
4.派生子类
还是以上面的类为例:
//继承的时候需要为泛型传入实参,这里是String类型
public class Cat extends Animal<String>{}
//也可以不传实参,但是也不能写形参,这是后相当于传入了实参Object,内部原有的T 都会变成Object
public class Cat extends Animal{
void set(Object t);
Object get();
}
5.类型通配符
类型通配符用?表示,< ? >
//带这种通配符的父类,不能添加数据
List<?> list = new ArrayList<String>();
//这一行会出现编译错误,因为在编译期list的参数类型还是“?”,list并不知道自己实际的参数类型是什么
list.add("hello");
还有2中限定形式的通配符表示
1.有上限的通配符:
<? extends Animal>,这个表示我这个通配符传入实际参数时,只能是Animal或者Animal的子类,比如Animal , Cat 或者Dog
2.有下限的通配符:
<? super String>,这个表示传入实际参数时,只能是String或父类,比如 String,Object
泛型参数也可以有这样的表示。
<T extends Animal>
<T super String>
表示的意义也是差不多,限定类型的范围,但是这个通配符或T依然是不知道具体类型是什么,只知道他是 extends 或 super后面的类的子类或父类
6.泛型方法
不是泛型类或者接口,同样可以定义泛型方法。
比如:
public class Dog{
//在修饰符和返回值类型之间,标注<>,表明是泛型方法
public <T> void run(T t){
...
}
//这里面返回值类型是E,
public <E> E getName(){
}
}
使用时候不用显示的制定方法或返回值的类型实参,传入数据或者将返回值赋值给具体变量,java能够自己推断出真是类型,比如
Dog dog = new Dog();
//调用run的时候,参数为 '欢快的跑',系统就推断出T为String类型
dog.run("欢快的跑");
//调用getName(),返回值赋给String类型的name,推断出E为String类型
String name = dog.getName();
7.泛型擦除
泛型擦除其实扩展开来,涉及的还是很多,从泛型的设计之初就包含了这个现象。这里不详细描述,免得太杂乱。只介绍使用时典型的擦除,最后给出链接介绍擦除。
比如,我将一个有传入泛型实参的类赋给一个没有传入实参的类,那么传入的类的参数类型会被擦除
//传入类型实参
Animal<String> animal = new Animal<>();
//将animal传给没有实参的animal2
Animal animal2 = animal;
//animal2并不会拥有animal的实参,animal中的类型会被擦除,animal2不会记住传入的是String类型,只会把类型当作Object类型处理,也就是String的父类
//如果我们定义 public class Animal<T extends Number>
//创建2个实例 Integer是Number的子类
Animal<Integer> animal = new Animal<>();
//将animal赋值给没有实参的变量
Animal animal2 = animal;
//这时java一样擦除了Integer,只会把类型当作Number类型处理
关于擦除和java为什么不支持泛型数组,可以看看这些博客
泛型擦除和子类继承限制
java为什么不支持泛型数组
如果有什么错误或者疑问,欢迎指出,谢谢