泛型
一、概念
泛型就是变量类型的参数化。也就是说,所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别称为泛型类、泛型接口和泛型方法。(使得变量的数据类型是动态的)
二、好处
1、类型安全
指定具体类型后,Java编译器会对错误的类型在编译时被捕获,而不是在运行时当作ClassCastException展示出来。从而提高程序的安全性和可靠性。
List<String> list = new ArrayList<String>() ;
list对象只能存放String对象,否则会编译出错
2、消除强制类型转换
//需要强制类型转换
List list = new ArrayList() ;
String str = (String)list.get(0) ;
//不需要类型转换
List<String> list = new ArrayList<String>() ;
String str = list.get(0) ;
3、潜在的性能收益
三、分类
1、泛型类
1)语法
// 第一:定义泛型类 - 指定泛型参数
[访问修饰符] class 类名<泛型1[,泛型2,...]> {
泛型1 泛型成员1 ;
泛型2 泛型成员2;
...
}
// 第二:实例化泛型类型对象 - 指定具体的数据类型
类名<数据类型> 对象名 = new 类名<数据类型>();
// 简写,类型推导
类名<数据类型> 对象名 = new 类名<>();
说明:
- 泛型参数名称:任意合法标识符 + 大写,如T、E、K、V
- 在实例化泛型类对象时,如果没有指定具体的数据类型,则默认为Object
... class 类名<泛型>
=
... class 类名<泛型 extend Object>
- 指定泛型具体数据类型时,不能使用基本类型
// 错误
List<int> list = new ArrayList<int>();
// 正确 - 使用基本类型对应的包装类
List<Integer> list = new ArrayList<Integer>();
案例:
//定义一个泛型
public class People<A> {
private String name;
private String sex;
private A pet;
}
//定义多个泛型
public class People<A,B> {
private String name;
private String sex;
private A pet;
private B phone ;
}
2)要点
2.1)泛型相当于类中一种特殊的类型。这种类型的特点是:在实例化该类时可指定为某种具体的实际类型。(不能是基本类型)
2.2)泛型1、泛型2…可以是JAVA的任意合法标识符(大写),如:T、E、K、V
2.3)class People<A>
声明了一个泛型类,这个A没有任何限制,实际上相当于Object类型,实际上相当于 class People<A extends Object>
。
class People<T> == class People<T extends Object>
2.4)与Object泛型类相比,使用泛型所定义的类在声明和构造实例的时候,可以使用“<实际类型>”来一并指定泛型类型持有者的真实类型。如:
People<Dog> p1= new People<Dog>() ;
People<Cat> p2 = new People<Cat>() ;
People<Animal> p3 = new People<Animal>() ;
2.5)当然,也可以在构造对象的时候不使用尖括号指定泛型类型的真实类型,但是你在使用该对象的时候,就需要强制转换了。如:
People p = new People("张三","男",new Cat("小花猫", 3, "雌")) ;
Cat c = (Cat)p.getPet() ;
实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。
案例:
/**
* 泛型类
*/
public class GenericTest<T> {
private T data ;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
/**
* 测试
*/
public class MainTest {
public static void main(String[] args) {
// 实例化一个泛型类对象,一般要指定具体的数据类型,如果没有指定的数据类型,则默认的是Object类型
// GenericTest gt1 = new GenericTest();
// gt1.setData(100);
//创建泛型类对象,并指定泛型为Integer
GenericTest<Integer> gt = new GenericTest<Integer>() ;
//封装数据,只能封装Integer类型数据
gt.setData(100) ;
//获取数据,不需要类型转换
int r = gt.getData() ;
//输出
System.out.println(r);
//根据需要,还可以创建其它类型的泛型类对象,封装相关类型的数据
//GenericTest<Float> gt = new GenericTest<Float>() ;
//...
}
}
2、泛型类继承
1)语法
[访问修饰符] interface 接口名<泛型1,泛型2,...> {
泛型1 泛型成员1 ;
泛型2 泛型成员2 ;
...
}
2)案例
/**
* 第一:定义泛型接口
*/
public interface GenericTest<T> {
// 1. 常量
public static final String name = "AAA";
// 2.抽象方法
public void sayHello(T sth);
}
/**
* 第二:实现接口
* 当实现泛型接口时,必须指定具体的数据类型
*/
public class GenericTestIntegerImpl implements GenericTest<Integer> {
@Override
public void sayHello(Integer sth) {
System.out.println("你好,"+sth);
}
}
/**
* 第二:实现接口
* 当实现泛型接口时,必须指定具体的数据类型
*/
public class GenericTestStringImpl implements GenericTest<String> {
@Override
public void sayHello(String sth) {
System.out.println("你好,"+sth);
}
}
/**
* 测试
* 第三:使用泛型接口
*/
public class MainTest {
public static void main(String[] args) {
GenericTest<Integer> gt1 = new GenericTestIntegerImpl() ;
gt1.sayHello(100) ;
GenericTest<String> gt2 = new GenericTestStringImpl() ;
gt2.sayHello("张三") ;
}
}
3、泛型接口
1)语法
[访问修饰符] interface 接口名<泛型1,泛型2,...> {
泛型1 泛型成员1 ;
泛型2 泛型成员2 ;
...
}
2)案例
// 第一:定义泛型接口
public interface GenericTest<T> {
// 注意:在泛型接口中,泛型不能定义常量
String msg = "好好学习,天天向上";
// 接口中的泛型可以定义方法参数、方法返回值
T sayHello(T msg);
}
/**
* 第二:泛型接口的实现一
* 注意:实现泛型接口时,一般要指定具体的数据类型,否则默认为Object
*/
public class GenericTestImpl1 implements GenericTest {
@Override
public Object sayHello(Object msg) {
return null;
}
}
/**
* 第二:泛型接口的实现二
* 注意:实现泛型接口时,一般要指定具体的数据类型,否则默认为Object
*/
public class GenericTestImpl2 implements GenericTest<String> {
@Override
public String sayHello(String msg) {
System.out.println("你好," + msg);
return "好好学习";
}
}
/**
* 测试
* 第三:使用泛型接口
*/
public class MainTest {
public static void main(String[] args) {
// 实例化接口对象
GenericTest<String> gt1 = new GenericTestImpl2();
// 错误 - 接口泛型 与 实现类实现接口指定的泛型不一致
// GenericTest<Integer> gt2 = new GenericTestImpl2();
}
}
4、泛型方法
1)语法
[访问修饰符] <泛型列表> 返回值 方法名(参数列表) {
}
2)要点:
- 泛型列表中可以指定多个泛型,使用逗号分隔,如:<T1,T2>
- 泛型可用于声明方法的返回值、方法的参数、方法内的局部变量
- 泛型方法中定义的泛型只作用于当前方法
public interface ProxyHandler {
<T> T createProxy(Class<T> targetClass);
}
// 泛型方法中定义的泛型只作用于当前方法
public class ProxyHandler {
public <T> createProxy(Class<T> targetClass) {
T t;
}
// 错误
public <P> sayHello() {
T t;
}
}
3)案例
/**
* 第一:定义泛型方法
*/
public class GenericTest {
// 定义一个泛型
// 注:泛型方法中的泛型只作用于本方法
public <T> void sayHello(T sth) {
// 泛型定义局部变量
T a = sth;
System.out.println(a.getClass().getSimpleName() + ":" + sth);
}
// 定义多个泛型
public <T1, T2, T3> void sayGoodBye(T1 s1, T2 s2, T3 s3) {
System.out.println(s1.getClass().getName()+":"+s1);
System.out.println(s2.getClass().getName()+":"+s2);
System.out.println(s3.getClass().getName()+":"+s3);
}
}
/**
* 第二:调用泛型方法
* 直接传递具体数据即可
*/
public class MainTest {
public static void main(String[] args) {
GenericTest gt = new GenericTest() ;
gt.sayHello(100) ;
gt.sayGoodBye("AA", 100, true) ;
}
}
5、泛型数组
注意:不能实例化泛型数组
解决方法:
- setter方法
- Array.newInstance(类型的class对象,长度)
/**
* 第一:定义泛型类,使用泛型定义数组
*/
public class GenericTest<T> {
T[] arr;
public T[] getArr() {
return arr;
}
public void setArr(T[] arr) {
this.arr = arr;
}
/**
* 实例化泛型数组
*/
public void createArr(Class<?> clazz, int len) {
Object obj = Array.newInstance(clazz, len);
this.arr = (T[])obj ;
}
}
/**
* 第二:定义泛型类对象,调用方法,传递数组
* 语法一
*/
public static void main(String[] args) {
// 实例化泛型类对象
GenericTest<String> gt = new GenericTest<>() ;
// 定义数组
String[] arr = {"AA","BB","CC"} ;
// 填充数据
gt.setArr(arr);
// 获取泛型数组,并打印输出 - Arrays.toString():转化为字符串
System.out.println(Arrays.toString(gt.getArr()));
}
/**
* 第二:定义泛型类对象,调用方法,传递数组
* 语法二
*/
public static void main(String[] args) {
// 实例化泛型类对象
GenericTest<String> gt = new GenericTest<>() ;
// 错误 - 泛型中的数据只是声明而已,并没有实例化
// String[] arr = gt.getArr();
// arr[0] = "你好" ;
gt.createArr(String.class,3) ;
String[] arr = gt.getArr();
arr[0] = "你好" ;
arr[1] = "我好" ;
arr[2] = "大家好,才是真的好" ;
System.out.println(Arrays.toString(arr));
}
6、嵌套泛型:在泛型类中使用另一个泛型类
在某泛型类中引用一个泛型类(包含)。 1)泛型类:宠物
/**
* 定义泛型类 - 宠物
*
* @author zing
*/
public class Pet<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2)泛型类:主人
/**
* 定义一个泛型类 - 主人
*
* @author zing
*/
public class People<T> {
/**
* 定义主人所养的宠物
*
* 嵌套泛型:在泛型类中使用另一个泛型类 -- 泛型类People嵌套使用泛型类Pet
*
* 注意:在这里,泛型类Pet指定的泛型为String
*/
private Pet<String> pet ;
public Pet<String> getPet() {
return pet;
}
public void setPet(Pet<String> pet) {
this.pet = pet;
}
}
3)测试
/**
* 嵌套泛型:在某泛型类中引用一个泛型类(包含)
*
* @author zing
*
*/
public class MainTest {
public static void main(String[] args) {
People<Double> zs = new People<>();
Pet<String> xh = new Pet<>() ;
Pet<Double> xb = new Pet<>() ;
zs.setPet(xh);
// 错误 - 泛型类型不一致 - 泛型的不变性
// xb 指定的是 Double类型,但 zs 对象封装的 Pet 指定的类型为 String
// zs.setPet(xb);
}
}
四、边界:把泛型默认为 Object 类型,收窄到指定类型的范围。
1、为什么需要边界
// 当定义一个泛型时,如果不知道具体的类型,则默认为Object
// T类型默认为Object,不存在compareTo方法,编译错误
// 如果T类型是实现了Comparable接口的类型,则编译通过 - 设置边界解决
public static <T> int compare(T x, T y){
return x.compareTo(y);
}
// 解决方法 - 设置边界
public static <T extends Comparable> int compare(T x, T y){
return x.compareTo(y);
}
2、语法
给类型参数添加限定就称之为给类型参数设定边界,设定边界的语法为:
// 语法一:指定一个边界
<T extends Xxx>
// 语法二:指定多个边界
<T extends Xxx & Yyy & Zzz & ...>
说明:
- Xxx是边界类型,它可以是
类
也可以是接口
,这个边界类型称之为上边界 - 类型实参(T)必须是这个边界类型或者是其子类型(包括子接口)或实现类,其它类型则报错
- extends关键字在这里不是继承的含义,应该理解为:
- T是Xxx接口或是Xxx接口的子接口
- T是实现Xxx接口的实现类
- T是Xxx类
- T是Xxx类的子类
- 如果不限制边界,则默认是Object
<T extends Object>
<T>
通过设置上边界,解决上面的compare方法:
// 限定T是实现Comparable接口的任意实现类,则就可以识别出compareTo方法,编译才通过
// 否则,默认为Object对象,并不存在compareTo方法,因此编译失败
public static <T extends Comparable> int compare(T x, T y){
return x.compareTo(y);
}
五、通配符
1、为什么需要通配符
由于Java泛型的不变性,父类泛型不能接收子类泛型,因此需要通配符实现协变、逆变。
必须保证泛型类型的一致性
2、语法
通配符用?
表示,表示某一个未知的类型。它主要的目标是给泛型实例确立父子关系。
1)通配符上边界
// 协变:满足条件诸如 List<Cat> 是 List<? extends Animal> 的子类型时,称为协变。
<? extends Xxx>
说明:?
代表未知类型,extends
关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
2)通配符下边界
// 逆变:满足条件 List<Animal> 是 List<? super Cat> 的子类型时,称为逆变。
<? super Xxx>
说明:?
代表未知类型,super
关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类。
六、型变
概念:表示泛型类型的父子关系。
1、不变
赋值运行符左右两边的泛型必须一致
// 泛型是不变的 - 必须指定相同类型的泛型
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
// 错误
List<Number> list2 = new ArrayList<Integer>();
ArrayList<Number> list2 = new ArrayList<Integer>();
2、协变
子类型的泛型可以赋值给父类型的泛型
- 语法:
<? extends T>
- 为了确保类型安全,无法调用协变后的对象中含泛型参数的方法,协变集合不能添加元素
List<? extends Number> list = new ArrayList<Integer>() ;
// 错误
list.add(1.0) ;
list.set(1,1.0) ;
- 应用:泛型的集合作为生产者时,用extends关键字
3、逆变
父类型的泛型可以赋值给子类型的泛型
- 语法:
<? super T>
- 逆变集合可以添加T类型及其子类型的数据,但不能取集合中的元素(不能查询元素)
解决方法:
- 强制类型转换
- 使用Object接收
// 本来,Pig 是 Animal的子类型 - 通过逆变操作后,Animal 变成了 ? super Pig 的子类型
List<? super Pig> list = new ArrayList<Animal>();
// 逆变集合可以添加Pig类及其子类,因为正式接收的是包含Pig或其父类的集合,
// 如上面的 ArrayList<Animal> ,当然也可以是 ArrayList<Pig>
// 当添加CartoonPig、Pig类型对象时,Pig类型或Animal类型都可以接收
list.add(new CartoonPig("乔治"));
list.add(new Pig());
// 错误:逆变可以添加元素,但不能获取元素 - 获取的元素类型为 Animal 类型,向下转型为Pig类型失败
// Pig object = list.get(0);
// 解决一:强制类型转换
Pig obj1 = (Pig)list.get(0);
// 解决二:使用Object接收
Object obj2 = list.get(0) ;
System.out.println(obj1);
System.out.println(obj2);
- 应用:当集合作为消费者时,用super关键字