注:本篇属于Colletion体系插曲,主要目的是全方位梳理泛型的知识点
目录
泛型的概念
- 泛型,即"参数化类型",也就是说所操作的数据类型被指定为一个参数。
泛型的特性
- 泛型只是在java的编译期会产生错误,但是在java的运行期(已经生成字节码文件后)是会被擦除的,这个期间并没泛型的存在。
//传入的类型需与泛型的类型参数相同
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
//list.add("HelloWorld"); 编译报错
}
}
public class GenericTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
//获取ArrayList的字节码文件
Class clazz = Class.forName("java.util.ArrayList");
//获取add() 方法,Object.class 代表任意对象类型的数据
Method m = clazz.getMethod("add", Object.class);
m.invoke(list,"HelloWorld");//通过反射添加字符串类型元素数据
System.out.println(list);
}
}
//输出结果:[1, 2, HelloWorld]
泛型的使用
泛型类
泛型类的基本格式
class 类名称 <泛型标识>{
......
}
泛型类的注意事项
- 在Java泛型中,T 是类型形参,当我们调用的时候,则传入的是类型实参。
- T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
- 在实例化泛型类时,必须指定T的具体类型。
- 在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
class Person<T>{
private T height;
public Person(T height) {
this.height = height;
}
public T getHeight() {
return height;
}
public void setHeight(T height) {
this.height = height;
}
}
public class GenericTest {
public static void main(String[] args) {
//因为是Integer类型 所以传入的实参应该是int类型。下面同理
Person<Integer> person1=new Person<>(180);
Person<String> person2=new Person<>("180");
//在使用泛型类的时候不一定要传入泛型实参
Person person3=new Person(180.5);
System.out.println(person3.getHeight());
Person person4=new Person("180厘米");
System.out.println(person4.getHeight());
}
}
泛型接口
泛型接口的基本格式
public interface 接口名称 <T> {
......
}
泛型接口的注意事项
- 在子类定义的时候继续使用泛型
interface behavior <T> {
String cry= "正在哭";
//T run = "正在跑"; 编译报错,因为在接口中,public static final常量需赋值,所以必须确定类型
void shout(T key);
}
class Animal<T,V> implements behavior<T>{
V name;
T age;
public Animal(V name, T age) {
this.name = name;
this.age = age;
}
@Override
public void shout(T key) {
System.out.println("姓名:"+name+",年龄:"+age+",行为:"+key);
}
}
public static void main(String[] args) {
Animal<String,String> animal=new Animal<>("小狗","6岁");
animal.shout("正在大叫");
}
//输出结果:
姓名:小狗,年龄:6岁,行为:正在大叫
- 在子类实现接口的时候明确给出类型
interface behavior <T> {
String cry= "正在哭";
//T run = "正在跑"; 编译报错,因为在接口中,public static final常量需赋值,所以必须确定类型
void shout(T key);
}
class Teacher<V> implements behavior<String>{
V name;
public Teacher(V name) {
this.name = name;
}
@Override
public void shout(String key) {
System.out.println(name+"正在"+key);
}
}
public static void main(String[] args) {
Teacher<String> teacher=new Teacher<>("赵老师");
teacher.shout("上课");
}
//输出结果:
赵老师正在上课
泛型通配符
协变、逆变和不变
- 逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系(比如,A≤B表示A是由B派生出来的子类)
- f(⋅)是**逆变(contravariant)**的,当A≤B时有f(B)≤f(A)成立;
- f(⋅)是**协变(covariant)**的,当A≤B时有f(A)≤f(B)成立;
- f(⋅)是**不变(invariant)**的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
数组的协变
- 可以把数组的协变看成是多态,因为B是A的子类,所以相当于B[ ]是A[ ]的子类。
class A{ }
class B extends A{ }
class C extends A{ }
class D extends B{ }
public class GenericTest2 {
public static void main(String[] args) {
A[] a1=new B[10]; //和多态一样,子类对象数组可以向上转型为父类对象数组的引用
B[] b=(B[])a1;
//D[] d=(D[])a1; java.lang.ClassCastException 类型转换错误
//只能向数组b赋值B类
b[0]=new B();
//b[1]=new D();
//b[2]=new A();
//b[3]=new C();
System.out.println("----------");
//数组a1的各个元素可以赋值以B或者B的子类
a1[0]=new B();
a1[1]=new D();
//a1[2]=new A(); java.lang.ArrayStoreException 将错误类型的对象存储到一个对象数组时抛出的异常
//a1[3]=new C(); java.lang.ArrayStoreException 将错误类型的对象存储到一个对象数组时抛出的异常
System.out.println("----------");
//数组a2的各个元素可以赋值以A或者A的子类
A[] a2=new A[10];
a2[0]=new B();
a2[1]=new C();
a2[2]=new D();
}
}
泛型的不变
- 泛型是不变的,也就是说虽然B是A的子类,但是ArrayList< A >和ArrayList< B >之间并没有内建的协变类型。
class A{ }
class B extends A{ }
class C extends A{ }
class D extends B{ }
ArrayList<A> list1=new ArrayList<A>();
//ArrayList<A> list2=new ArrayList<B>(); 编译报错
通配符的协变和逆变
协变 <? extends T>
- extends指出了泛型的上界为T,<? extends T>称为子类通配符(上界通配符),<? extends A>意味着某个继承自A的具体类型。
- <? extends T>不能往里存,只能往外取,取出来的东西只能存放在T或它的基类里。
class A{ }
class B extends A{ }
class C extends A{ }
class D extends C{ }
class E extends B{ }
class F extends B{ }
class G extends E{ }
public static void main(String[] args) {
//<? extends T>不能往里存,只能往外取
ArrayList aim=new ArrayList();
aim.add(new B());
ArrayList<? extends B> al = new ArrayList<E>(aim);
//al.add(new A()); 编译错误
//al.add(new B()); 编译错误
//al.add(new C()); 编译错误
//al.add(new D()); 编译错误
//al.add(new E()); 编译错误
//al.add(new F()); 编译错误
//al.add(new G()); 编译错误
//<? extends T>取出来的东西只能存放在T或它的基类里
//所以<? extends B>只能放在B或它的基类里
B newB=al.get(0);
A newA=al.get(0);
//C newc=al.get(0); 编译报错
Object newObject=al.get(0);
System.out.println("newB:"+newB);
System.out.println("newA:"+newA);
System.out.println("newObject:"+newObject);
}
//输出结果:
newB:javabase.javagather.B@5cad8086
newA:javabase.javagather.B@5cad8086
newObject:javabase.javagather.B@5cad8086
逆变 <? super T>
- super指出泛型的下界为T,<? super T>称为超类通配符,<? extends B>代表一个具体类型,而这个类型是B的超类。
- <? super T>往里存只能存T或T的子类,往外取只能放在Object对象里。
class A{ }
class B extends A{ }
class C extends A{ }
class D extends C{ }
class E extends B{ }
class F extends B{ }
class G extends E{ }
public static void main(String[] args) {
//因为<? super B>说明必须是B或者B的父类
//所以等式右边可以是new ArrayList<A>()或new ArrayList<B>()
ArrayList<? super B> b1 = new ArrayList<A>();
//<? super T>往里存只能存T或T的子类
//所以<? super B>只能添加B或B的子类
//b1.add(new A()); 编译错误
b1.add(new B());
//b1.add(new C()); 编译错误
//b1.add(new D()); 编译错误
b1.add(new E());
b1.add(new F());
b1.add(new G());
//<? super T>往外取只能放在Object对象里
//B newB=b1.get(0); 编译错误
//A newA=b1.get(0); 编译错误
Object newObject=b1.get(0);
System.out.println("newObject:"+newObject);
System.out.println("--------------");
//因为<? super D>说明等式右边必须是D或者D的父类
//所以等式右边可以是new ArrayList<A>()、new ArrayList<C>()或new ArrayList<D>()
ArrayList<? super D> b2 = new ArrayList<C>();
//<? super T>往里存只能存T或T的子类
//所以<? super D>所以只能添加D或D的子类
//b2.add(new A()); 编译错误
//b2.add(new B()); 编译错误
//b2.add(new C()); 编译错误
b2.add(new D());
//b2.add(new E()); 编译错误
//b2.add(new F()); 编译错误
//b2.add(new G()); 编译错误
//<? super T>往外取只能放在Object对象里
//B newB=b1.get(0); 编译错误
//A newA=b1.get(0); 编译错误
Object newObject2=b2.get(0);
System.out.println("newObject2:"+newObject2);
}
//输出结果:
newObject:javabase.javagather.B@5cad8086
--------------
newObject2:javabase.javagather.D@6e0be858
通配符?的其他使用
public class GenericTest3 {
public static void main(String[] args) {
infor(new Worker<Number>(100));
infor(new Worker<Integer>(200));
}
//通配符?解决当具体类型不确定的时候
public static void infor(Worker<?> workers){
System.out.println("电子厂工人总数"+workers.num+"人");
}
}
class Worker<T>{
T num;
public Worker(T num) {
this.num = num;
}
}
//输出结果:
电子厂工人总数100人
电子厂工人总数200人
泛型方法
泛型方法的基本格式
//<T>必不可少,这表明这是一个泛型方法
public <T> T 方法名(){
......
}
//可以这样
public <T,K> T 方法名(泛型类<K> obj){
......
}
泛型类中的泛型方法
class Elephant<T,K>{
T name;
K age;
public Elephant(T name, K age) {
this.name = name;
this.age = age;
}
//可以创建完泛型方法,但不使用<U>
public <U> void show2(){
System.out.println("start...");
}
//Elephant<T>的T和此泛型方法的T不是一个T
public <T> void show(Elephant<T,K> elephant){
System.out.println(elephant.name+",年龄"+elephant.age+"岁,健康状态良好");
}
//泛型方法中有可变参数
public <T> void show3(T...args){
for (T arg : args) {
System.out.println(arg);
}
System.out.println("end....");
}
}
public static void main(String[] args) {
Elephant<String,Integer> e=new Elephant<>("大壮",6);
e.show2();
e.show(e);
e.show3(1,2);
}
//输出结果:
start...
大壮,年龄6岁,健康状态良好
1
2
end....
泛型和静态方法
- 静态方法不能访问类中定义的泛型。
- 如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法
class Doctor<T>{
T name;
/* public static void show(){ //编译报错
System.out.println(name);
}
*/
/* public static void show(T age){ //编译报错
System.out.println();
}
*/
public static <U> void show(U doctorName){
System.out.println(doctorName+"医生已到岗");
}
}
public static void main(String[] args) {
Doctor.show("小月");
}
//输出结果:
小月医生已到岗
不能实例化一个参数化类型的数组
- 什么叫参数化类型的数组?就是这个数组里存储的对象是参数化类型,比如说ArrayList< String >就是一个类型参数为String的参数化类型,我们在Java中也称呼它为泛型。
public static void main(String[] args) {
//ArrayList<String>[] arrayList=new ArrayList<String>[10]; 编译报错
}
- 由于泛型具有擦除机制,在运行时的类型参数会被擦除,对于Java的数组来说,他必须知道它持有的所有对象的具体类型,而泛型的这种运行时擦除机制违反了数组安全检查的原则。
- 如何解决这种情况呢?我们可以通过创建一个非参数化类型的数组,然后将他强制类型转换为一个参数化类型数组。
public class GenericArray {
public static void main(String[] args) {
//Cat<String>[] cat = new Cat<String>[10]; 编译报错
Cat<String>[] cat;
Cat[] c=new Cat[10];
cat=(Cat<String>[])c;
cat[0]=new Cat<String>("小花");
System.out.println(cat[0].getName());
}
}
class Cat<T>{
T name;
public T getName() {
return name;
}
public Cat(T name) {
this.name = name;
}
}
Hi, welcome to JasperのBlog!