概念
- Java泛型是JDK5引入的新特性,提供编译时类型安全监测机制,监测非法类型数据结构
- 本质是参数化类型,也就是锁操作的数据类型被指定为一个参数
- 泛型相当于提供了一个安全限制,不符合类型转换的就会报错
- 可以用在类、接口和方法中,被称为泛型类、泛型接口和泛型方法
特点
- 保证了类型的安全性
- 避免不必要的装箱拆箱操作,提高性能
- 提高代码的重用性
泛型标识
是一种约定,也可以使用A-Z英文字母的其中一个。
T :代表一般的任何类(Java类)。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
?:表示不确定的java类型
泛型类
类型参数用于类型的定义称为泛型类,使用位置:
- 非静态的成员属性类型
- 非静态方法的形参类型(包括非静态成员方法和构造器)
- 非静态成员方法的返回值类型
注意事项:
- 类中的静态方法和静态变量不可以使用泛型类所声明的类型函数。类型参数在创建时候确定,但静态变量和方法在类加载时初始化,直接使用类名调用,可能会类型未确定时被调用
- 静态泛型方法中使用自身新定义的类型参数,不能用泛型类中定义的类型参数
- 泛型类不只接受一个类型参数,还可以接受多个类型参数
public class Person<T> {
// T的类型由外部指定
// name成员变量的数据类型为T => 非静态的成员属性类型
private T name;
// 构造方法形参name的类型也为T => 非静态方法的形参类型(包括非静态成员方法和构造器)
public Person(T name) {
this.name = name;
}
// 泛型方法getName的返回值类型为T => 非静态成员方法的返回值类型
public T getName(){
return name;
}
// 泛型类定义的类型参数 T 不能在静态方法中使用
// 这是正确的,因为 E 是在静态方法签名中新定义的类型参数
public static <E> E show(E one){
return null;
}
}
public class Student<E,T> {
E name;
T sex;
public E getName(){
return name;
}
public T getSex(){
return sex;
}
}
使用场景
在集合中限制添加对象的类型,若添加对象类型不一致,则会直接报错,这就是类型安全监测机制的实现原理
public void test() {
// 传入String类型,原泛型类可以想象它会自动扩展,其类型参数会被替换
Person<String> person= new Person<>();
// <> 中什么都不传入,等价于 Person <Object> person = new Person <>();
Person person = new Person ();
}
泛型接口
注意事项
- 类型参数在接口被继承或者实现的时候确定
- 静态成员不能使用泛型接口定义的类型参数
使用场景
- 定义一个描述人的接口IPerson
- 定义一个IStudent接口继承泛型接口IPerson,在接口IStudent中确认泛型借口IPerson中的类型参数
- 定义一个类WorkStudent实现泛型接口IPerson,在类WorkStudent中确定泛型接口IPerson的类型参数
- 定义一个类SchoolStudent实现泛型接口IPerson,若没有确定泛型接口IPerson的类型参数,则默认为Object
- 定义一个类SocietyStudent实现泛型接口IPerson,若没有确定泛型接口IPerson的类型参数,也可以将SocietyStudent定义成泛型类,类型参数和泛型接口保持相同
//1、定义一个描述人的接口IPerson
interface IPerson<U, R> {
// 报错! 接口中的属性默认是静态的,因此不能使用类型参数声明
U name;
int n = 10;
// 普通方法中,可以使用类型参数
R getRunSpeed(U u);
// 抽象方法中,可以使用类型参数
void StudyMaths(R r);
// 在jdk8 中,可以在接口中使用默认方法, 默认方法可以使用泛型接口的类型参数
default R method(U u) {
return null;
}
}
//2、定义一个IStudent接口继承泛型接口IPerson,
//在接口IStudent中确认泛型借口IPerson中的类型参数
interface IStudent extends IPerson<String, Double> {
...
}
//使用 String 替换 U,用 Double 替换 R
class StudentOne implements IStudent{
@Override
public Double getRunSpeed(String s) {
return null;
}
@Override
public void StudyMaths(Double d) {
...
}
}
//3、定义一个类WorkStudent实现泛型接口IPerson,
//在类WorkStudent中确定泛型接口IPerson的类型参数,
//会使用 Integer 替换 U, 使用 Float 替换 R
class WorkStudent implements IPerson<Integer, Float> {
@Override
public Float getRunSpeed(Integer integer) {
return null;
}
@Override
public void StudyMaths(Float afloat) {
...
}
}
//4、定义一个类SchoolStudent实现泛型接口IPerson,
//若没有确定泛型接口IPerson的类型参数,则默认为Object,
//建议直接写成 IPerson<Object, Object>,
//等价 class SchoolStudent implements IPerson<Object, Object>
class SchoolStudent implements IPerson{
@Override
public Object getRunSpeed(Object o) {
return null;
}
@Override
public void StudyMaths(Object o) {
...
}
}
//5、定义一个类SocietyStudent实现泛型接口IPerson,
//若没有确定泛型接口IPerson的类型参数,
//也可以将SocietyStudent定义成泛型类,类型参数和泛型接口保持相同
class SocietyStudent<U, R> implements IPerson<U, R> {
...
}
泛型方法
定义
- 当在一个方法签名中的返回值前面声明一个<T>时,该方法称为泛型方法
- <T>表明该方法声明了一个类型参数T,并且咋合格方法只能在该方法中使用
- 泛型方法中也可以使用泛型类中定义的泛型参数
注意事项
- 只有在方法签名中声明<T>的方法才是泛型方法,使用了泛型类定义的类型参数的方法不是泛型方法
- 泛型方法中可以同时声明多个参数类型
- 泛型方法中可以使用泛型类型定义的泛型参数
- 泛型方法和泛型类中定义的类型参数是相互独立的,是没有关系的,最好不同名
- 将静态方法声明为泛型方法,在静态成员中不能使用泛型类定义的类型参数,但我们可以将静态成员方法定义为一个泛型方法
class Person<U>{
// 该方法只是使用了泛型类定义的类型参数,不是泛型方法
public void say(U u){
System.out.println(u);
}
//<T>真正声明了下面的方法是一个泛型方法,可以接收任意类型的数据
public <T> T Run(T t) {
return t;
}
//泛型方法中可以同时声明多个参数类型
public <T, S> T SearchOut(T t, S s) {
return null;
}
//泛型方法中可以使用泛型类型定义的泛型参数
public <T> U WalkAround(T t, U u) {
return u;
}
// 静态的泛型方法
public static <E> void show(E one){
System.out.println(one);
}
}
public class Test{
public static void main(String args[]) {
// 创建Person对象
Person<Object> person = new Person();
//非泛型方法调用
person.say("hello world");
// 给Person中的泛型方法传递字符串
String s = person.Run("跑的很快");
//给Person中的泛型方法传递数字,自动装箱
int i = person.Run(30);
// 输出 跑的很快
System.out.println(s);
// 输出 30
System.out.println(i);
// 输出 Good
Person.show("Good");
}
}
泛型方法中的类型推断
- 可以显示指定类型参数,也可以不指定
- 指定时,传入泛型方法的参数数据类型必须是指定数据类型或者子类
- 不指定时,多个类型参数时,取共同父级的最小级,直到Object
public class Student{
// 这是一个简单的泛型方法
public static <T> T add(T x, T y) {
return y;
}
public static void main(String[] args) {
//不显式地指定类型参数
//传入的两个实参都是Integer,所以泛型方法中的<T> => <Integer>
int i = Student.add(1, 5);
//传入的两个实参一个是 Integer,另一个是 Float,
//所以<T>取共同父类的最小级,<T> => <Number>
Number f = Student.add(1, 3.6);
//传入的两个实参一个是 Integer,另一个是 String,
//所以<T>取共同父类的最小级,<T> => <Object>
Object o = Student.add(1, "qwer");
//显式地指定类型参数
//指定了<T> => <Integer>,所以传入的实参只能为 Integer 对象
int a = Student.<Integer>add(1, 6);
//指定了<T> => <Integer>,所以不能传入 Float 对象
//编译错误
int b = Student.<Integer>add(1, 3.2);
//指定<T> => <Number>,所以可以传入 Number 对象
//Integer 和 Float 都是 Number 的子类,因此可以传入两者的对象
Number c = Student.<Number>add(1, 8.2);
}
}
类型擦除
概念定义
- 泛型信息只存在于代码编译阶段,代码编译结束后,与泛型相关的信息会被擦掉
- 成功编译过后的 class 文件中不包含任何泛型信息,泛型信息不会进入到运行时阶段
- 代码成功编译后,其内的所有泛型信息都会被擦除,并且类型参数 T 会被统一替换为其原始类型(默认是 Object 类,若有 extends 或者 super 则另外分析)
- 在泛型信息被擦除后,若还需要使用到对象相关的泛型信息,编译器底层会自动将对象进行类型转换
public class GenericType {
public static void main(String[] args) {
ArrayList<String> arrayString = new ArrayList<String>();
ArrayList<Integer> arrayInteger = new ArrayList<Integer>();
//ArrayList<String>,ArrayList<Integer>编译后都会变成ArrayList< Objec t>类型
//输出true
System.out.println(arrayString.getClass() == arrayInteger.getClass());
}
}
泛型通配符
定义概念:希望泛型能够处理某一类型范围内的类型参数
泛型通配符有 3 种形式:
- <?> :被称作无限定的通配符。
- <? extends T> :被称作有上界的通配符。
- <? super T> :被称作有下界的通配符。
<? extends T> 有上界的通配符 => 可以读,不能写
- T代表类型参数的上界,表示类型范围是T和T的子类,ArrayList<Integer 和 ArrayList< umber>不存在继承关系,在逻辑上可以看做父子类关系
- ArrayList<? extends Number> 不能确定到底是哪一个集合,是一个未知类型集合,所以不能进行写入操作,否则产生ClassCastException异常
- 目的是为了拓展方法形参中类型参数的范围
- 但是可以往集合中添加 null,因为 null 表示任何类型
public class Test {
public static void main(String[] args) {
// 编译错误
ArrayList<Number> list01 = new ArrayList<Integer>();
// 编译正确
ArrayList<? extends Number> list02 = new ArrayList<Integer>();
// 写操作
ArrayList<? extends Number> list = new ArrayList<>();
// 编译正确,只能添加空值 null
list.add(null);
// 编译错误
list.add(new Integer(1));
list.add(new Float(1.5));
// 创建一个 ArrayList<Integer> 集合
ArrayList<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
// 将 ArrayList<Integer> 传入 printIntVal() 方法
printIntVal(integerList);
// 创建一个 ArrayList<Float> 集合
ArrayList<Float> floatList = new ArrayList<>();
floatList.add((float) 1.0);
floatList.add((float) 2.0);
// 将 ArrayList<Float> 传入 printIntVal() 方法
printIntVal(floatList);
}
public static void printIntVal(ArrayList<? extends Number> list) {
// 遍历传入的集合,并输出集合中的元素
for (Number number : list) {
System.out.print(number.intValue() + " ");
}
System.out.println();
}
}
<? super T>下界的通配符 => 可以写,不能读
- T 代表了类型参数的下界,表示类型参数的范围是T和T的父类,直至Object
- ArrayList<? super Number> 集合中可以添加 Number 类及其子类的对象(不包括 Number 类)
- 不能将传入集合的元素赋值给 Number 对象,因为传入的可能是 ArrayList< Object > 集合,向下转型可能会产生ClassCastException异常
public class Test {
public static void main(String[] args) {
// 编译错误
ArrayList<Integer> list01 = new ArrayList<Number>();
// 编译正确
ArrayList<? super Integer> list02 = new ArrayList<Number>();
// 创建一个 ArrayList<? super Number> 集合
ArrayList<Number> list = new ArrayList();
// Object 是 Number 的父类
// 编译错误
list.add(new Object());
// 往集合中添加 Number 类及其子类对象
list.add(new Integer(1));
list.add(new Float(1.1));
// 调用 fillNumList() 方法,传入 ArrayList<Number> 集合
fillNumList(list);
System.out.println(list);
}
public static void fillNumList(ArrayList<? super Number> list) {
list.add(new Integer(0));
list.add(new Float(1.0));
// 遍历传入集合中的元素,并赋值给 Number 对象;
// 会编译错误
for (Number number : list) {
System.out.print(number.intValue() + " ");
System.out.println();
}
// 遍历传入集合中的元素,并赋值给 Object 对象;
// 但只能调用 Object 类的方法,不建议这样使用
// 可以正确编译
for (Object obj : list) {
System.out.println(obj);
}
}
}
<?> 无限定通配符
- ?代表任意一种数据类型,能代表任何一种数据类型的只有 null
- Object 本身也算是一种数据类型,但却不能代表任何一种数据类型
- ArrayList<?> 是 ArrayList< Object > 逻辑上的父类,本质上没有继承关系
- 大多数情况下,可以用类型参数 < T > 代替 <?> 通配符
public class Test{
public static void main(String[] args) {
ArrayList<Integer> integerList = new ArrayList<>(110, 668);
// 安全地向上转型
ArrayList<?> list = integerList ;
ArrayList<?> listTwo = new ArrayList<>();
// 编译正确
listTwo .add(null);
Object obj = listTwo .get(0);
// 编译错误,不能指定 ArrayList<?> 的数据类型
listTwo .add(new Integer(1));
Integer num = listTwo .get(0);
}
}
PECS 原则
Producer Extends Consumer Super
- 如果需要返回T,则它是生产者(Producer),需要用到extends通配符
- 如果需要写入T,则它是消费者(Consumer ),需要super通配符
Collections 的 copy() 方法
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
// src 是 producer,dest 是 consumer
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}