泛型
1、概述
1.1 定义
(1)本质是“类型参数化”。
(2)在声明一个类、接口、方法的时候,需要涉及到到一个问题:要给属性确定一个类型,或者给方法的返回值确定一个类型,或者给方法的参数确定一个类型。之前,定义类、接口、方法的时候,上面所描述的类型都是直接写死,不会变化的。而类型参数化,顾名思义,就是将原来的具体的类型进行参数化,类似于方法中的参数变量,不过这里是将类型作为参数,在使用时会传入具体的类型。
1.2 泛型的使用
public class Point{
int x;
int y;
}
这里Pint类里面的属性x和y的类型已经定义好了,都是int类型。如果希望x属性和y属性的类型更加灵活一点,就可以为这两个属性添加泛型参数T,如下:
public class Point<T>{
T x;
T y;
}
(1)T表示泛型参数,表示一种数据类型,具体是什么类型,需要将来使用Point的时候进行传参来确定。
(2)如果将来Point在使用的时候,没有给泛型参数T传值,那么T默认就表示为Object类型。
(3)T是泛型参数的名字,也就是相当于形参,名字随便起,但是一般用一个有意义的大写字母,例如一般E用在集合中。
Print的使用如下
public static void main(String[] args){
//p1对象中x和y的属性类型都是Integer
Point<Integer> p1 = new Point<Integer>();
p1.x = 1;
p1.y = 2;
//p2对象中x和y的属性类型都是String
Point<String> p2 = new Point<String>();
p2.x = "1";
p2.y = "2";
//p3对象中x和y的属性类型都是Object
Point p3 = new Point();
p3.x = new Object();
p3.y = new Object();
}
可以看到,Point类的中x和y属性的类型,是可以根据我们在使用时所传的参数,进行临时变化的。
如果没有传这个泛型参数,那么这个参数T就默认是Object类型。
注意:给泛型参照传的值,只能是引用类型,不能是基本类型:Point编译报错
1.2 为什么要用泛型
有如下代码:
import java.util.ArrayList;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
//创建一个ArrayList集合
List list = new ArrayList();
//添加String类型的数据
list.add("aaaa");
//添加Integer类型的数据(int会自动包装为Integer)
list.add(100);
//第一种方式,用Object类型接收输出
// for(int i = 0; i<list.size(); i++) {
// Object object = list.get(i);
// System.out.println(object);
// }
//第二种方式,如果指定了用String类型接收
for(int i = 0; i<list.size(); i++) {
String string = (String) list.get(i);
System.out.println(string);
}
}
}
第一种方式输出的结果:
aaaa
100
第二种方式的输出结果:
aaaa
Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Test9.test01.Demo01.main(Demo01.java:24)
可以看到,第二种方式报错了,ClassCastException,类型转换异常,为什么会报这个异常呢?因为这个list数组中存的是两种不同类型(String和Integer)的数据,如果你指定用其中某一种类型接收,如String类型(而不用object类型接收),那使用时就只能用String类型的数据,另外的一种类型(Integer)就不能被接收和使用,此时就会发生类型转换异常。在这种情况下,就要使用泛型了,对集合传入的数据进行类型限制。
在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。说人话就是限制所传入数据的类型。
对上面的案例使用泛型,代码如下:
import java.util.ArrayList;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
//创建一个ArrayList集合
List<String> list = new ArrayList<String>();
//添加String类型的数据
list.add("aaaa");
//添加Integer类型的数据(int会自动包装为Integer)
// list.add(100); //这句代码报错了
//指定用String类型接收
for(int i = 0; i<list.size(); i++) {
String string = list.get(i);
System.out.println(string);
}
}
}
可以看到,在List list=new ArrayList();这句代码两边多出了两个,这就是表示为这个集合类型添加了泛型类型,且这个泛型类型指定为String类型。表示这个集合只能添加String类型的数据了,如果添加了其他类型的数据就会报错。
后面那对尖括号中的泛型类型是可以省略的,即可以写成下面这样子
List<String> list = new ArrayList<>();
2、泛型的使用
2.1 泛型类
泛型参数定义在类上面,那这个类就是泛型类。
//泛型类
public class Point<T>{...}
//创建泛型类对象
public static void main(String[] args){
Point<String> p = new Point<String>();
}
代码样例:
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
//泛型构造方法形参key的类型也为T,T的类型由外部指定
public Generic(T key) {
this.key = key;
}
//泛型方法getKey的返回值类型为T,T的类型由外部指定
public T getKey(){
return key;
}
}
//在实例化泛型类时,必须指定T的具体类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> integer = new Generic<Integer>(123456);
//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> string = new Generic<String>("lalala");
System.out.println(integer.getkey());
System.out.println(string.getkey());
注意:不能对确切的泛型类型使用instanceof操作
如下是错误的
if(Num instanceof Generic){ } //Number是所有与数字相关的父类
2.2 泛型接口
泛型接口,如果泛型参数定义在接口上面,那么这个接口就是一个泛型接口。
使用其实和泛型类差不太多。。。
如下:
//泛型接口
public interface Action<T>{...}
public static void main(String[] args){
//使用匿名内部类实现接口,且指定了这个接口的类型为String
Action<String> a = new Action<>(){
//...
};
}
2.3 泛型方法
泛型参数定义在方法上面,那么这个方法就是泛型方法
//泛型方法
public class Test{
public <T> T test(T t){
//...
}
}
//泛型方法中的返回类型和参数类型都得是泛型才能叫泛型方法,否则只是带泛型参数的普通方法或者返回值为泛型的普通方法
public static void main(String[] args){
Test t = new Test();
String str = t.test("hello");
Integer i = t.test(1);
Double d = t.test(10.5D);
}
3、泛型的类型
先观察下面的代码
//编译通过,父类型的引用,指向子类对象
Object o = new Integer(1);
//编译通过,Object[]类型兼容所有的【引用】类型数组,arr可以指向任意 引用类型 数组对象
Object[] arr = new Integer[1];
//编译失败,类型不兼容,int[] 是基本类型数组
Object[] arr = new int[1];
//编译失败,错误信息:ArrayList<Integer>无法转为ArrayList<Object>,原因:在编译期间,ArrayList<Integer>和ArrayList<Object>是俩个不同的类型,并且没有子父类型的关系
ArrayList<Object> list = new ArrayList<Integer>();
俩个类型,如果是当做泛型的指定类型的时候,就没有多态的特点了
4、泛型通配符
4.1 怎么用
public void test1(Collection<Integer> c){}
public void test2(Collection<String> c){}
public void test3(Collection<Object> c){}
test1方法【只能】接收泛型是Integer类型的集合对象
test2方法【只能】接收泛型是String类型的集合对象
test3方法【只能】接收泛型是Object类型的集合对象
原因:由于泛型的类型之间没有多态,所以=号俩边的泛型类型必须一致
在这种情况下,就可以使用通配符(?)来表示泛型的父类型,如下:
public void test(Collection<?> c){}
这个问号就是统配符,可以匹配所有的泛型类型
4.2 使用通配符"?"带来的问题
有如下代码:
Collection<?> c;
c = new ArrayList<String>();
c.add("hello");
//编译报错
//因为变量c所声明的类型是Collection,同时泛型类型是通配符(?),那么编译器也不知道这个?将来会是什么类型,因为这个?只是一个通配符,所以,编译器不允许使用变量c来向集合中添加新数据。
c.add(null);
//编译通过
//集合中一定存的是引用类型,null是所有引用类型共同的一个值,所以一定可以添加进去。
5、泛型边界
使用extends和super关键字对泛型的类型进行限制。如规定泛型的上限和下限。
5.1 上限
例如:List<? extends Number> list
将来引用list就可以接收泛型是Number或者Number子类型的List集合对象
public static void main(String[] args) {
List<? extends Number> list;
//list可以指向泛型是Number或者Number【子】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Integer>();
list = new ArrayList<Double>();
//编译报错,因为String不是Number类型,也不是Number的子类型
//list = new ArrayList<String>();
5.2 下限
例如:List<? super Number> list
将来引用list就可以接收泛型是Number或者Number父类型的List集合对象
public static void main(String[] args) {
List<? super Number> list;
//list可以指向泛型是Number或者Number【父】类型的集合对象
list = new ArrayList<Number>();
list = new ArrayList<Serializable>();
list = new ArrayList<Object>();
//编译报错,因为String不是Number类型,也不是Number的父类型
//list = new ArrayList<String>();
//编译报错,因为Integer不是Number类型,也不是Number的父类型
//list = new ArrayList<Integer>();}
5.3 对比extends和super
使用extends可以定义泛型的【上限】,这个就表示将来泛型所接收的类型【最大】是什么类型。可以是这个最大类型或者它的【子类型】。
使用super可以定义泛型的【下限】,这个就表示将来泛型所接收的类型【最小】是什么类型。可以是这个【最小类型】或者它的【父类型】。
5.4 extends和super作为上限和下限使用场景
对于extends:
1、在声明泛型类或者泛型接口的时候【可以】使用
2、在声明泛型方法的时候【可以】使用
3、在声明变量的时候【可以】使用
对于super:
1、在声明泛型类或者泛型接口的时候【不能】使用
2、在声明泛型方法的时候【不能】使用
3、在声明变量的时候【可以】使用
6、泛型特性:泛型擦除
泛型类型仅存在于编译期间,编译后的字节码和运行时不包含泛型信息,所有的泛型类型映射到同一份字节码。
定义一个泛型类如下:
class Generic<T> {
private T obj;
public Generic(T o) {
obj = o;
}
public T getObj() {
return obj;
}
}
Java编译后的字节码中Generic相当于这样的:
class Generic {
private Object obj;
public Generic(Object o) {
obj = o;
}
public Object getObj() {
return obj;
}
}
泛型信息被擦除后,所有的泛型类型都会统一变为原始类型:Object
泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常.