泛型的概念 #
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用(例如,继承或实现这个接口,用这个类型声明变量、创建对象时确定(即传入实际的类型参数,也称为类型实参)。
JDK1.5引入
泛型的定义中,不可以使用基本数据类型,可以使用对应的包装类进行替换
可避免的问题 #
1、类型不安全,导入数据存入混乱,添加泛型,在编译时就会进行类型检查,保证了数据安全
2、避免了强制类型转换时,出现异常ClassCastException
伪泛型 #
Java中的泛型是伪泛型,泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。
Java的泛型只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类ArrayList。
所以以下代码,不会造成任何异常:
List<Integer> list1 = new ArrayList<>();
List list2 = new ArrayList<>();
list2.add("hello");
list1 = list2;
System.out.println(list1);
泛型的使用 #
List<String> li = new ArrayList<String>();
1、在实例化集合类时,可以指明具体的泛型类型
2、指明完泛型以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
3、如果实例化时,没指明泛型的类型。默认类型为java.lang.Object类型。
4、泛型可以嵌套使用
5、JDK7新特性,类型推断,可以写成List<String> li = new ArrayList<>();
泛型类 #
修饰符 class 类名<泛型形参1,泛型形参2,......> {
在类中使用泛型形参对类型进行占位;
}
- 泛型形参名称,一般由单个大写字母组成,常用的:E、T、V、N、K
- 约定的含义为
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- 约定的含义为
如果泛型类在实例化的时候没有指明泛型的类型,那么默认的类型就是Object类型,如果实例化的类是带有泛型的,那么建议在实例化的时候指明类的泛型
- 泛型不同的两个引用不可以互相赋值
- 在类、接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型,但在静态方法中不能使用类的泛型。
- 异常类不能是泛型的
- 如果子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
- 子类除了指定或保留父类的泛型,还可以增加自己的泛型
泛型接口 #
修饰符 interface 接口名<泛型形参> {
在接口中使用泛型形参对类型进行占位;
}
泛型方法 #
泛型方法是指在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系和类是不是泛型类也没有关系。
可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public <E> List<E> 方法名(E[] arr){
方法体;
}
使用泛型的注意事项 #
-
类A是类B的父类,
G<A>和G<B>不具备子父类关系,属于并列关系 -
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
-
泛型不同的引用不能相互赋值
- 未指定泛型类型的除外,例如
ArrayList的实例可以赋值给ArrayList<String>
- 未指定泛型类型的除外,例如
-
泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但是不等价于泛型
<Object> -
如果泛型结构是一个接口或者抽象类,则不可以创建泛型类的对象
-
泛型的指定不能使用基本数据类型,可以使用包装类替换
-
在类或接口上声明的泛型,可以作为此类非静态属性类型、非静态方法的参数、返回值类型,但是在静态方法中不能使用类的泛型
-
异常类不能是泛型的
-
不能使用
new E[10],但是可以使用E[] e = (E[])new Object[10]来代替 -
父类有泛型,子类可以选择保留泛型也可以指定泛型类型
-
子类除了指定或保留父类的泛型,还可以增加自己的泛型
通配符 #
Java泛型通配符遵循:PECS(Producer Extends Consumer Super)原则
协变性、逆变性、不变性 #
若类A是类B的子类,则记作A ≦ B。设有类型变换f()
- 协变性
- 允许将一个更具体的类型(子类型)当作一个更一般的类型(父类型)的替代品使用
- 当
A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性 - Java中数组具有协变性,例如:
Object[] a = new Integer[10] - Java中上限统配符声明的泛型具有协变性:例如:
<? extends T>
- 逆变性
- 逆变性允许将一个泛型类型参数替代为其超类型
- 当
A ≦ B时,有f(B)≦ f(A),则称变换f()具有逆变性 - Java中下限统配符声明的泛型具有协变性:例如:
<? super T>
- 不变性
- 表示泛型类型参数既不是协变的也不是逆变的,即不能被替代为其超类型或子类型
- 当
A ≦ B时,f(A)与f(B)无关,则称变换f()具有不变性 - Java中泛型具有不变性,例如:
List<Object> a = new ArrayList(),List<Integer> b = new ArrayList(),此时如果a = b会编译错误
理解 #
假设Orange类是Fruit类的子类,以集合类List<T>为例:
- 协变(covariance):满足条件诸如
List<Orange>是List<? extends Fruit>的子类型时,称为协变。 - 逆变(covariance):满足条件诸如
List<Fruit>是List<? super Orange>的子类型时,称为逆变。 - 不变(invariance):表示
List<Orange>和List<Fruit>不存在型变关系。
子类(subclass)和子类型(subtype)不是同一个概念。
里氏代换原则 #
里氏替换原则(Liskov Substitution Principle, LSP),由Barbara Liskov于1987年提出
- 定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象
- 核心思想:如果将一个父类对象替换成它的子类对象后,该程序不会发生异常。这也是该原则希望达到的一种理想状态。
- 通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
- 含义
- 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法。
- 子类中可以增加自己的方法。
- 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更为宽松。
- 当子类覆盖或实现父类的方法时,方法的返回值要比父类更严格。
// 含义中最后两句话的理解
class Super{
Number method(Number n){}
}
class Sub{
@Override
Integer method(Object n){}
}
// 此时如果将父类引用指向子类对象(多态)
Super s = new Sub();
// 由于子类所重写的方法,形参比父类更宽松,返回值比父类更严格,程序并不会发生异常
Number n = s.method(1);
限制的通配符 #
由于无限通配符、上限通配符,元素无法确认最小类型,所以都不允许add(),其实就是都不允许调用定义了最小类型无法确认的形参的方法。
无限通配符(只读) #
语法:<?>,例如List<?>,表示了List中元素类型未知
List<?> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
list1 = list2; //success,由于list1的泛型为Object及其子类
list1.add(1); //error,使用无限通配符不允许调用参数为泛型的方法
上限通配符(只读) #
语法:<? extends T>,例如List<? extends T>,表示了List中元素类型转换的上界
List<? extends Number> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
list1 = list2; //success,由于list1的泛型为Number及其子类
list1.add(1); //error,使用上限通配符不允许调用参数为泛型的方法
下限通配符(只写) #
语法:<? super T>,例如List<? super T>,表示了List中元素类型转换的下界
List<? super Number> list1 = new ArrayList<>();
List<Number> list2 = new ArrayList<>();
List<Object> list3 = new ArrayList<>();
list1 = list2;//success,由于list1的泛型为Number及其父类
list1 = list3;//success,由于list1的泛型为Number及其父类
list1.add(1); //success