Files
JavaYouth/docs/Java/Basis/泛型.md
2022-03-16 01:23:41 +08:00

2160 lines
57 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 万字长文详解Java泛型
tags:
- Java
- 基础
- 泛型
categories:
- Java基础
- 重难点
keywords: Java基础泛型
description: 万字长文详解Java泛型。
cover: 'https://gitee.com/youthlql/randombg/raw/master/logo/java.png'
abbrlink: adb2faf0
date: 2020-10-19 22:21:58
---
# 简介
## 泛型的优点
1、泛型的本质是为了参数化类型也就是在在不创建新的类型的情况下通过泛型指定的不同类型来控制形参具体限制的类型很明显这种方法提高了代码的复用性。
2、泛型的引入提高了安全性泛型提供了编译时类型安全检测机制该机制允许开发者在编译时检测到非法的类型。。
3、在没有泛型的情况的下通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。
那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
```java
public class GlmapperGeneric<T> {
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
public static void main(String[] args) {
// do nothing
}
/**
* 不指定类型
*/
public void noSpecifyType(){
GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 需要强制类型转换
String test = (String) glmapperGeneric.get();
System.out.println(test);
}
/**
* 指定类型
*/
public void specifyType(){
GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 不需要强制类型转换
String test = glmapperGeneric.get();
System.out.println(test);
}
}
```
## 为什么提高了安全性?
再举例子说明一下
**不安全举例**
```Java
package keyAndDifficultPoints.Generic;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 16:09
* <p>
* 功能描述:
*/
public class Test_Safe {
public static void main(String[] args) {
test();
}
public static void test() {
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for (int i = 0; i < arrayList.size(); i++) {
String s = (String) arrayList.get(i);
System.out.println(s);
}
}
}
```
结果:
```
aaaa
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at keyAndDifficultPoints.Generic.Test_Safe.test(Test_Safe.java:25)
at keyAndDifficultPoints.Generic.Test_Safe.main(Test_Safe.java:16)
```
很明显的一个类型转换错误。ArrayList可以存放任意类型例子中添加了一个String类型添加了一个Integer类型再使用时都以String的方式使用因此程序崩溃了。为了解决类似这样的问题在编译阶段就可以解决泛型应运而生。
**泛型提高安全性**
将上面的代码稍微改一下
```Java
public static void test01(){
List<String> arrayList = new ArrayList<>();
arrayList.add("aaaa");
//下面代码编译时就直接报错了
arrayList.add(100);
for (int i = 0; i < arrayList.size(); i++) {
String s = (String) arrayList.get(i);
System.out.println(s);
}
}
```
通过泛型来提前检测类型,编译时就通不过。
## 泛型为什么很重要
我们看一下比较常用的JUC包
```Java
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(asyncPool, fn);
}
public <U> CompletableFuture<U> thenComposeAsync(
Function<? super T, ? extends CompletionStage<U>> fn,
Executor executor) {
return uniComposeStage(screenExecutor(executor), fn);
}
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(asyncPool, action);
}
```
这些都大量的用到了泛型,如果不把泛型学好,想真正深入源码了解一些东西,可能就完全看不懂了。
# 泛型类
泛型类型用于类的定义中被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类List、Set、Map。
**最普通的泛型类:**
```Java
package keyAndDifficultPoints.Generic;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 16:38
* <p>
* 功能描述:
*/
public class Test_GenericClass {
public static void main(String[] args) {
test();
}
public static void test(){
/**
* 1、泛型的类型参数只能是类类型包括自定义类不能是简单数据类型比如int,long这些
* 2、传入的实参类型需与泛型的类型参数类型相同即为这里的Integer。
* 3、new 后面的泛型参数可以省略
*/
Generic<Integer> genericInteger1 = new Generic<Integer>(123);
Generic<Integer> genericInteger = new Generic<>(123);
Generic<String> genericString = new Generic<String>("my");
System.out.println(genericInteger.getVar());
System.out.println(genericString.getVar());
}
}
/**
* 1、此处T虽然可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
* 但是为了代码的可读性一般来说:
* K,V用来表示键值对
* E是Element的缩写常用来遍历时表示
* T就是Type的缩写常用在普通泛型类上
* 2、还有一些不常见的U,R啥的
*/
class Generic<T> {
//key这个成员变量的类型为T,T的类型由外部指定
private T var;
public Generic(T var) { //泛型构造方法形参key的类型也为TT的类型由外部指定
this.var = var;
}
public T getVar() { //泛型方法getKey的返回值类型为TT的类型由外部指定
return var;
}
}
class MyMap<K, V> { // 此处指定了两个泛型类型
private K key; // 此变量的类型由外部决定
private V value; // 此变量的类型由外部决定
public K getKey() {
return this.key;
}
public V getValue() {
return this.value;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
};
```
**结果:**
```
123
my
Process finished with exit code 0
```
- 定义的泛型类,就一定要传入泛型类型实参么?并不是这样,在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型。
还是以上面的泛型类为例进行测试
```Java
public static void test01() {
Generic generic = new Generic("我是字符串");
Generic generic1 = new Generic(123);
Generic generic2 = new Generic(123.123);
Generic generic3 = new Generic(false);
System.out.println(generic.getVar());
System.out.println(generic1.getVar());
System.out.println(generic2.getVar());
System.out.println(generic3.getVar());
}
```
**结果:**
```
我是字符串
123
123.123
false
Process finished with exit code 0
```
没有报错,正确输出了。
# 泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
```Java
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义方法,方法的返回值就是泛型类型
}
```
**当实现泛型接口的类,未传入泛型实参时:**
```java
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即class InfoImpl<T> implements Info<T>
* 如果不声明泛型class InfoImpl implements Info<T>,编译器会报错:"Unknown class"
*/
class InfoImpl<T> implements Info<T> { // 定义泛型接口的子类
private T var;
public InfoImpl(T var) {
this.setVar(var);
}
public void setVar(T var) {
this.var = var;
}
public T getVar() {
return this.var;
}
}
```
当实现泛型接口的类,传入泛型实参时:
```Java
/**
* 传入泛型实参时:
* 定义一个是先烈实现这个接口,虽然我们只创建了一个泛型接口Info<T>
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即InfoImpl01<T>public String getVar();中的的T都要替换成传入的String类型。
*/
class InfoImpl01 implements Info<String> { // 定义泛型接口的子类
private String var;
public InfoImpl01(String var) {
this.setVar(var);
}
public void setVar(String var) {
this.var = var;
}
public String getVar() {
return this.var;
}
}
```
# 泛型方法
在java中泛型类和接口的定义非常简单但是泛型方法就比较复杂了。
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型。
**最简单的一个泛型方法**
```Java
public class Test_GenericMethod {
public static void main(String[] args) {
Test_GenericMethod test_genericMethod = new Test_GenericMethod();
Integer integer = test_genericMethod.genericMethod(12);
System.out.println(integer);
}
/**
* 说明:
* 1、public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2、只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3、<T>表明该方法将使用泛型类型T此时才可以在方法中使用泛型类型T。
* 4、<T> 后面的这个T代表这个方法的返回值类型
* 4、与泛型类的定义一样此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(T a) {
return a;
}
}
```
## 基本用法(非泛型类中的泛型方法)
下面来细说一下泛型方法
首先说一个误区
```Java
class Generic01<T> {
private T key;
public Generic01(T key) {
this.key = key;
}
/**
* 1、这个虽然在方法中使用了泛型但这并不是一个泛型方法。这只是类中一个普通的
* 成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才
* 可以继续使用 T 这个泛型。
*/
public T getKey() {
return key;
}
/**
* 1、这个方法显然是有问题的在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
* 因为在类的声明中并未声明泛型E所以在使用E做形参和返回值类型时编译器会无法识别。
*/
// public E setKey(E key) {
// this.key = key;
// }
}
```
**基本用法**(非)
```Java
package keyAndDifficultPoints.Generic;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 17:46
* <p>
* 功能描述:
*/
public class Test_GenericMethod {
public static void main(String[] args) {
Test_GenericMethod test_genericMethod = new Test_GenericMethod();
Generic01<Integer> generic01 = new Generic01<>(123);
Generic01<String> generic02 = new Generic01<>("AAAAA");
test_genericMethod.genericMethod_test01(generic01);
test_genericMethod.genericMethod_test02(generic02, "我是T");
test_genericMethod.Method01(generic01);
}
/**
* 说明:
* 1、public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2、只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3、<T>表明该方法将使用泛型类型T此时才可以在方法中使用泛型类型T。
* 4、<T> 后面的这个T代表这个方法的返回值类型
* 4、与泛型类的定义一样此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(T a) {
return a;
}
/**
* 1、这才是一个真正的泛型方法。
* 2、首先在public与返回值之间的<T>必不可少这表明这是一个泛型方法并且声明了一个泛型T。
* 3、这个T可以出现在这个泛型方法的任意位置.泛型的数量也可以为任意多个
*/
public <T> T genericMethod_test01(Generic01<T> generic01) {
System.out.println("我是genericMethod_test01" + generic01.getKey());
T test = generic01.getKey();
return test;
}
public <T, V> T genericMethod_test02(Generic01<T> generic01, V value) {
System.out.println("我是genericMethod_test02" + generic01.getKey() + "==> value" + value);
T test = generic01.getKey();
return test;
}
//这也不是一个泛型方法这就是一个普通的方法只是使用了Generic<Number>这个泛型类做形参而已。
public void Method01(Generic01<? extends Number> generic01) {
System.out.println(generic01.getKey());
}
//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
//同时这也印证了泛型通配符章节所描述的,?是一种类型实参可以看做为Number等所有类的父类
public void Method02(Generic01<?> generic01) {
System.out.println(generic01.getKey());
}
/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T并未声明泛型类型E因此编译器并不知道该如何处理E这个类型。
*/
// public <T> T showKeyName(Generic01<E> generic01, T t) {
// return t;
// }
}
```
**结果:**
```
我是genericMethod_test01123
我是genericMethod_test02AAAAA==> value我是T
123
Process finished with exit code 0
```
## 泛型类中的泛型方法
当然这并不是泛型方法的全部,泛型方法可以出现杂任何地方和任何场景中使用。但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时,我们再通过一个例子看一下。
```Java
package keyAndDifficultPoints.Generic;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 20:14
* <p>
* 功能描述:
*/
public class Test_GenericMethod01 {
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
//apple是Fruit的子类所以这里可以
generateTest.show_1(apple);
//编译器会报错因为泛型类型实参指定的是Fruit而传入的实参类是Person
//generateTest.show_1(person);
//使用这两个方法都可以成功
generateTest.show_2(apple);
generateTest.show_2(person);
//使用这两个方法也都可以成功
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
abstract class GenericFruit {
}
class Fruit {
@Override
public String toString() {
return "fruit";
}
}
class Apple extends Fruit {
@Override
public String toString() {
return "apple";
}
}
class Person {
@Override
public String toString() {
return "Person";
}
}
class GenerateTest<T> {
public void show_1(T t) {
System.out.println(t.toString());
}
/**
* 1、在泛型类中声明了一个泛型方法使用泛型E这种泛型E可以为任意类型。可以类型与T相同也可以不同。
* 2、由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别
泛型方法中识别的泛型。
*/
public <E> void show_3(E t) {
System.out.println(t.toString());
}
/**
* 1、在泛型类中声明了一个泛型方法使用泛型T注意这个T是一种全新的类型可以与泛型类中声明的T
* 不是同一种类型。也就是说main函数中使用的时候也可以是不一样的泛型类型
*/
public <T> void show_2(T t) {
System.out.println(t.toString());
}
}
```
**结果:**
```
apple
apple
Person
apple
Person
Process finished with exit code 0
```
## 泛型方法与可变参数
再看一个泛型方法和可变参数的例子:
```Java
public class Test_GenericMethod02 {
public static void main(String[] args) {
print("123",753,123.12);
}
//必须是三个点
public static <T> void print(T... args) {
for (T t : args) {
System.out.println(t);
}
}
}
```
**结果:**
```
123
753
123.12
Process finished with exit code 0
```
## 静态方法与泛型
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:**静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。**
即:**如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法** 。
```java
public class StaticGenerator<T> {
/**
* 1、如果在类中定义使用泛型的静态方法需要添加额外的泛型声明将这个方法定义成泛型方法
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
* 2、泛型方法在方法中出现了泛型的结构泛型参数与类的泛型参数没有任何关系。换句话说
* 泛型方法所属的类是不是泛型类都没有关系。
* 3、泛型方法可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在初始化类时确定,所以无所谓
*/
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
```
## 细枝末节
> 可能合上面的有一些重复
1、泛型异常类
```
//异常类不能声明为泛型类,编译报错
class MyException<T> extends Exception{
}
```
2、
```Java
package keyAndDifficultPoints.Generic.Minutiae;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 22:28
* <p>
* 功能描述:
*/
public class Test_Minutiae1 {
}
class Order<T> {
String orderName;
int orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public Order(){
//编译不通过
// T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//如下的三个方法都不是泛型方法
public T getOrderT(){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//静态方法中不能使用类的泛型。
// public static void show(T orderT){
// System.out.println(orderT);
// }
public void show(){
//编译不通过
// try{
//
//
// }catch(T t){
//
// }
}
/**
* 2、泛型方法在方法中出现了泛型的结构泛型参数与类的泛型参数没有任何关系。换句话说
* 泛型方法所属的类是不是泛型类都没有关系。
* 3、泛型方法可以声明为静态的。原因泛型参数是在调用方法时确定的。并非在初始化类时确定,
* 所以无所谓
*/
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
class SubOrder extends Order<Integer> {//SubOrder:不是泛型类
public static <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}
class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}
```
# 泛型数组
```Java
package keyAndDifficultPoints.Generic;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 12:10
* <p>
* 功能描述: 测试泛型数组
*/
public class Test_GenericArray {
public static void main(String[] args) {
test02();
}
public static void test() {
//编译错误
// List<String>[] ls = new ArrayList<String>[10];
}
public static void test01() {
//这样声明是正确的
List<?>[] ls = new ArrayList<?>[10];
ls[1] = new ArrayList<String>();
//这样写编译就报错了
// ls[1].add(1);
}
/**
* 下面是sun官方文档里写的。其实不用太纠结平时泛型虽然用的多但也不会用的这么奇葩。
*/
public static void test02(){
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK
System.out.println(i);
}
//正确
public static void test03() {
List<String>[] ls = new ArrayList[10];
ls[0] = new ArrayList<String>();
ls[1] = new ArrayList<String>();
ls[0].add("x");
}
}
```
[sun文档](http://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html)
# 泛型在继承方面的细节
直接看代码注释
```java
/*
1. 泛型在继承方面的体现
虽然类A是类B的父类但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
补充类A是类B的父类A<G> 是 B<G> 的父类
*/
@Test
public void test1() {
/**
* 下面是有继承关系,所以可以赋值
*/
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
/**
* 下面属于并列关系,无继承关系。无法赋值
*/
//编译不通过
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此时的list1和list2的类型不具有子父类关系
//编译不通过
// list1 = list2;
/*
反证法:
假设list1 = list2;
list1.add(123);导致混入非String的数据。出错。
*/
}
@Test
public void test2() {
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>();
}
```
# 泛型通配符
我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 TEKV 等等,下面来详细讲一下这些通配符。
## 常用的通配符
本质上都是通配符没啥区别,只不过是编码时的一种约定俗成的东西(可以说提高了代码可读性)。比如上述代码中的 T ,我们可以换成 A-Z 之间的任何一个大小写字母都可以,并不会影响程序的正常运行,但是如果换成其他的字母代替 T 在可读性上可能会弱一些。通常情况下TEKV 是这样约定的:
* 表示不确定的 java 类型
* T (Type) 表示具体的一个java类型
* K V (Key Value) 分别代表java键值中的Key Value
* E (element) 代表Element
比较难的就是``通配符,下面就着重讲一下
## ' ? '无界通配符
### 基本用法
```Java
List<Animal> listAnimals
```
但是如果用通配符的话:
```Java
List<? extends Animal> listAnimals
```
为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
```Java
package keyAndDifficultPoints.Generic;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: youthlql-吕
* @Date: 2020/10/15 21:25
* <p>
* 功能描述: 泛型通配符测试
*/
public class Test_Wildcard_Character {
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
test(dogList);
test1(dogList);
}
static void test(List<? extends Animal> animals) {
System.out.println("test输出:");
for (Animal animal : animals) {
System.out.print(animal.toString() + "-");
}
}
static void test1(List<Animal> animals) {
System.out.println("test1输出:");
for (Animal animal : animals) {
System.out.print(animal.toString() + "-");
}
}
}
class Animal {
@Override
public String toString() {
return "Animal";
}
}
class Dog extends Animal {
@Override
public String toString() {
return "Dog";
}
}
```
`test1()`在编译时就会飘红
<img src="https://npm.elemecdn.com/youthlql@1.0.8/Java_Basis/Generic/0001.png">
所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型。像 `test()`方法中,限定了上界,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错,而`test1()`就不行。
### ' ? '通配符的继承
```Java
/*
2. 通配符的使用
通配符:?
类A是类B的父类G<A>和G<B>是没有关系的二者共同的父类是G<?>
*/
@Test
public void test3() {
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//添加(写入)对于List<?>就不能向其内部添加数据。
//除了添加null之外。
// list.add("DD");
// list.add('?');
list.add(null);
//获取(读取)允许读取数据读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o);
}
```
## extends和super上下界
### 上界通配符 < ? extends E>
> 上结:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
* 如果传入的类型不是 E 或者 E 的子类,编译不成功
* 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
### 下界通配符 < ? super E>
> 下界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
### 举例
```Java
/*
3.有限制条件的通配符的使用。
? extends A:
G<? extends A> 可以作为G<A>和G<B>的父类其中B是A的子类
? super A:
G<? super A> 可以作为G<A>和G<B>的父类其中B是A的父类
*/
@Test
public void test4() {
List<? extends Person> list1 = null; //[-无穷,Person]
List<? super Person> list2 = null; //[Person,+无穷]
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//下面的东西很奇怪
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
////编译不通过
// Person obj = list2.get(0);
//写入数据:
//编译不通过
// list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student());
}
}
class Person {
}
class Student extends Person {
}
```
## 和 T 的区别
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 不行,比如如下这种
```Java
// 可以
T t = operate();
// 不可以
car = operate();
```
简单总结下:
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
### 区别1通过T来确保泛型参数的一致性
```Java
package keyAndDifficultPoints.Wildcard_Character;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: youthlql-吕
* @Date: 2020/10/16 11:28
* <p>
* 功能描述:
*/
public class Test_difference {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
List<Float> floatList = new ArrayList<>();
//编译报错
// test(integerList, floatList);
//编译通过
test1(integerList, floatList);
//编译通过
test(integerList, integerList);
test1(integerList, integerList);
}
// 通过 T 来 确保 泛型参数的一致性
public static <T extends Number> void test(List<T> dest, List<T> src){
}
//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
public static void test1(List<? extends Number> dest, List<? extends Number> src){
}
}
```
### 区别2T可以通过&进行多重限定
```Java
public class Test_difference {
public static void main(String[] args) {
/*---------------------测试多重限定符---------------------*/
ArrayList list = new ArrayList<>();
ArrayDeque deque = new ArrayDeque<>();
LinkedList<Object> linkedList = new LinkedList<>();
//多重限定时,在编译的时候取最小范围或共同子类
test2(list);
// test3(list); 编译报错
//编译报错
// test2(deque);
// test3(deque);
//编译通过
test2(linkedList);
test3(linkedList);
}
//可以进行多重限定
public static <T extends List & Collection> void test2(T t) {
}
//可以进行多重限定
public static <T extends Queue & List> void test3(T t) {
}
//编译报错,无法进行多重限定
// public static <? extends List & Collection> void test4(List<T> dest, List<T> src){
//
// }
}
```
<img src="https://npm.elemecdn.com/youthlql@1.0.8/Java_Basis/Generic/0002.png">
### 区别3通配符可以使用超类限定而T不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
--------------------------
# 关于反射和泛型的一点东西
```Java
package keyAndDifficultPoints.Wildcard_Character;
/**
* @Author: youthlql-吕
* @Date: 2020/10/16 12:09
* <p>
* 功能描述: 泛型反射
*/
public class Test_Reflect {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
A a = createInstance(A.class);
B b = createInstance(B.class);
}
/**
* 这样写明显是要安全很多的
*/
public static <T> T createInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
return clazz.newInstance();
}
public static void getA(String path) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
A a = (A) Class.forName("keyAndDifficultPoints.Wildcard_Character.A").newInstance();
//很明显下面的这行代码是错的但是写代码的时候你不知道path是哪个
// B b = (B)Class.forName("keyAndDifficultPoints.Wildcard_Character.A").newInstance();
System.out.println(a.toString());
}
}
class A {
String name;
@Override
public String toString() {
return "我是对象A";
}
}
class B {
String name;
@Override
public String toString() {
return "我是对象B";
}
}
class C {
//所以当不知道声明什么类型的 Class 的时候可以定义一 个Class<?>。
public Class<?> clazz1;
//因为T没有声明所以编译报错
// public Class<T> clazz2;
}
class D<T> {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;
}
```
# 泛型原理(泛型擦除)
## 类型擦除简介
Java的泛型是伪泛型为什么说Java的泛型是伪泛型呢因为在编译期间所有的泛型信息都会被擦除掉我们常称为**泛型擦除**。
Java中的泛型基本上都是在编译器这个层次来实现的在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数编译器在编译的时候去掉这个过程就称为类型擦除。
如在代码中定义的`List<object>``List<String>`等类型在编译后都会编程ListJVM看到的只是List。而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。
可以通过两个例子来证明java泛型的类型擦除。
**例1**
```Java
@Test
public void test() {
List<String> stringList = new ArrayList<String>();
stringList.add("my");
List<Integer> integerList = new ArrayList<Integer>();
integerList.add(123);
System.out.println(stringList.getClass() == integerList.getClass());
}
```
**结果:**
```
true
Process finished with exit code 0
```
在这个例子中我们定义了两个List不过一个是List<String>泛型类型只能存储字符串。一个是List<Integer>泛型类型只能存储整形。最后我们通过stringList对象和integerList对象的getClass方法获取它们的类的信息最后发现结果为true。说明泛型类型String和Integer都被擦除掉了只剩下了原始类型。
**例2**
```Java
@Test
public void test01() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<Integer> list = new ArrayList<Integer>();
//这样调用add方法只能存储整形因为泛型类型的实例为Integer
list.add(1);
//这样写编译就会报错
// list.add("my");
//通过反射的方式则可以存储String
list.getClass().getMethod("add", Object.class).invoke(list, "my");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
```
结果:
```
1
my
Process finished with exit code 0
```
在程序中定义了一个List<Integer>泛型类型如果直接调用add方法那么只能存储整形的数据。不过当我们利用反射调用add方法的时候却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了只保留了 原始类型。
## 类型擦除后保留的原始类型
1、在上面几次提到了**原始类型**。什么是原始类型原始类型就是擦除去了泛型信息最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型相应的原始类型都会被自动地提供。类型变量被擦除并使用其限定类型无限定的变量用Object替换
例3
```Java
package keyAndDifficultPoints.principle;
/**
* @Author: youthlql-吕
* @Date: 2020/10/16 23:01
* <p>
* 功能描述:
*/
public class Test_principle02 {
public static void main(String[] args) {
}
}
class Test_Generic<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
```
下面我们用IDEA的工具查看这个类的字节码信息。我把完整的字节码复制在下方
```Java
// class version 52.0 (52)
// access flags 0x20
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: keyAndDifficultPoints/principle/Test_Generic<T>
class keyAndDifficultPoints/principle/Test_Generic {
// compiled from: Test_principle02.java
// access flags 0x2
// signature TT;
// declaration: T
private Ljava/lang/Object; value
// access flags 0x0
<init>()V
L0
LINENUMBER 13 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L1 0
// signature LkeyAndDifficultPoints/principle/Test_Generic<TT;>;
// declaration: keyAndDifficultPoints.principle.Test_Generic<T>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature ()TT;
// declaration: T getValue()
public getValue()Ljava/lang/Object;
L0
LINENUMBER 17 L0
ALOAD 0
GETFIELD keyAndDifficultPoints/principle/Test_Generic.value : Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L1 0
// signature LkeyAndDifficultPoints/principle/Test_Generic<TT;>;
// declaration: keyAndDifficultPoints.principle.Test_Generic<T>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void setValue(T)
public setValue(Ljava/lang/Object;)V
L0
LINENUMBER 21 L0
ALOAD 0
ALOAD 1
PUTFIELD keyAndDifficultPoints/principle/Test_Generic.value : Ljava/lang/Object;
L1
LINENUMBER 22 L1
RETURN
L2
LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_Generic; L0 L2 0
// signature LkeyAndDifficultPoints/principle/Test_Generic<TT;>;
// declaration: keyAndDifficultPoints.principle.Test_Generic<T>
LOCALVARIABLE value Ljava/lang/Object; L0 L2 1
// signature TT;
// declaration: T
MAXSTACK = 2
MAXLOCALS = 2
}
```
可以明显的看到**泛型T**被替换成了**Object**。
因为在Test_Generic<T>中T是一个无限定的类型变量所以用Object替换。其结果就是一个普通的类如同泛型加入java变成语言之前已经实现的那样。在程序中可以包含不同类型的Test_Generic如Test_Generic<String>或Test_Generic<Integer>但是擦除类型后它们就成为原始的Test_Generic类型了原始类型都是Object。
从上面的那个例2中我们也可以明白List<Integer>被擦除类型后原始类型也变成了Object所以通过反射我们就可以存储字符串了。
2、如果类型变量有限定那么原始类型就用第一个边界的类型变量来替换。
比如Test_Generic这样声明
```Java
class Test_Generic1<T extends List & Collection>
```
我们还是看字节码(后面如无必须,只截取部分字节码)
```Java
// class version 52.0 (52)
// access flags 0x20
// signature <T::Ljava/util/List;:Ljava/util/Collection;>Ljava/lang/Object;
// declaration: keyAndDifficultPoints/principle/Test_Generic1<T extends java.util.List, java.util.Collection>
class keyAndDifficultPoints/principle/Test_Generic1 {
// compiled from: Test_principle03.java
// access flags 0x2
// signature TT;
// declaration: T
private Ljava/util/List; value
```
会发现T变成了List
如果顺序变一下
```java
class Test_Generic1<T extends Collection & List>
```
字节码就变了
T变成了Collection
```java
// class version 52.0 (52)
// access flags 0x20
// signature <T::Ljava/util/Collection;:Ljava/util/List;>Ljava/lang/Object;
// declaration: keyAndDifficultPoints/principle/Test_Generic1<T extends java.util.Collection, java.util.List>
class keyAndDifficultPoints/principle/Test_Generic1 {
// compiled from: Test_principle03.java
// access flags 0x2
// signature TT;
// declaration: T
private Ljava/util/Collection; value
```
也就是说在进行字节码编译的时候是使用离T最近的一个类型。
### 解答一个疑惑
在上文说到&的多重限定时
```Java
package keyAndDifficultPoints.principle;
import java.util.*;
/**
* @Author: youthlql-吕
* @Date: 2020/10/16 23:30
* <p>
* 功能描述:
*/
public class Test_principle04 {
public static void main(String[] args) {
/*---------------------测试多重限定符---------------------*/
List list = new ArrayList<>();
Queue queue = new ArrayDeque<>();
LinkedList<Object> linkedList = new LinkedList<>();
//多重限定时,在编译的时候取最小范围或共同子类
test2(list);
// test3(list); 编译报错
test4(list);
//编译报错
// test2(deque);
// test3(deque);
// test4(queue);
//编译通过
test2(linkedList);
test3(linkedList);
test4(linkedList);
}
//可以进行多重限定
public static <T extends List & Collection> void test2(T t) {
}
//可以进行多重限定
public static <T extends Queue & List> void test3(T t) {
}
//可以进行多重限定
public static <T extends Collection & List> void test4(T t) {
}
//编译报错,无法进行多重限定
// public static <? extends List & Collection> void test4(List<T> dest, List<T> src){
//
// }
}
```
首先来看一下字节码
```Java
// class version 52.0 (52)
// access flags 0x21
public class keyAndDifficultPoints/principle/Test_principle04 {
// compiled from: Test_principle04.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/Test_principle04; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 17 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
L1
LINENUMBER 18 L1
NEW java/util/ArrayDeque
DUP
INVOKESPECIAL java/util/ArrayDeque.<init> ()V
ASTORE 2
L2
LINENUMBER 19 L2
NEW java/util/LinkedList
DUP
INVOKESPECIAL java/util/LinkedList.<init> ()V
ASTORE 3
L3
LINENUMBER 22 L3
ALOAD 1
INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test2 (Ljava/util/List;)V
L4
LINENUMBER 24 L4
ALOAD 1
INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test4 (Ljava/util/Collection;)V
L5
LINENUMBER 33 L5
ALOAD 3
INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test2 (Ljava/util/List;)V
L6
LINENUMBER 34 L6
ALOAD 3
INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test3 (Ljava/util/Queue;)V
L7
LINENUMBER 35 L7
ALOAD 3
INVOKESTATIC keyAndDifficultPoints/principle/Test_principle04.test4 (Ljava/util/Collection;)V
L8
LINENUMBER 38 L8
RETURN
L9
LOCALVARIABLE args [Ljava/lang/String; L0 L9 0
LOCALVARIABLE list Ljava/util/List; L1 L9 1
LOCALVARIABLE queue Ljava/util/Queue; L2 L9 2
LOCALVARIABLE linkedList Ljava/util/LinkedList; L3 L9 3
// signature Ljava/util/LinkedList<Ljava/lang/Object;>;
// declaration: java.util.LinkedList<java.lang.Object>
MAXSTACK = 2
MAXLOCALS = 4
// access flags 0x9
// signature <T::Ljava/util/List;:Ljava/util/Collection;>(TT;)V
// declaration: void test2<T extends java.util.List, java.util.Collection>(T)
public static test2(Ljava/util/List;)V
L0
LINENUMBER 44 L0
RETURN
L1
LOCALVARIABLE t Ljava/util/List; L0 L1 0
// signature TT;
// declaration: T
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x9
// signature <T::Ljava/util/Queue;:Ljava/util/List;>(TT;)V
// declaration: void test3<T extends java.util.Queue, java.util.List>(T)
public static test3(Ljava/util/Queue;)V
L0
LINENUMBER 49 L0
RETURN
L1
LOCALVARIABLE t Ljava/util/Queue; L0 L1 0
// signature TT;
// declaration: T
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x9
// signature <T::Ljava/util/Collection;:Ljava/util/List;>(TT;)V
// declaration: void test4<T extends java.util.Collection, java.util.List>(T)
public static test4(Ljava/util/Collection;)V
L0
LINENUMBER 54 L0
RETURN
L1
LOCALVARIABLE t Ljava/util/Collection; L0 L1 0
// signature TT;
// declaration: T
MAXSTACK = 0
MAXLOCALS = 1
}
```
`test4()`方法里离T最近的是`Collection`那么T在编译后就被`Collection`代替了。那按理来说
```
test4(queue);
```
1、这里我们传一个Collection的实现类Queue也应该是可以的啊但是为什么报错了呢注意一点报错报的是编译错误泛型提供编译前检测机制也就是说在没运行前泛型规定了`多重限定时,在编译的时候取最小范围或共同子类`
2、那实际上到底可以不可以传Queue呢根据之前的讲解我相信大家已经有了结论。实际上是可以的只不过要跳过编译检测机制通过反射来放Queue。
### 泛型方法调用
在调用泛型方法的时候,可以指定泛型,也可以不指定泛型。在不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级直到Object。在指定泛型的时候该方法中的几种类型必须是该泛型实例类型或者其子类。
```java
class Test {
public static void main(String[] args) {
//不指定泛型的时候
int a1 = add(1, 2); //这两个参数都是Integer所以T为Integer类型
Number b1 = add(1, 1.2);//这两个参数一个是Integer以风格是Float所以取同一父类的最小级为Number
Object c1 = add(1, "my");//这两个参数一个是Integer以风格是Float所以取同一父类的最小级为Object
//指定泛型的时候
int a = Test.<Integer>add(1, 2);//指定了Integer所以只能为Integer类型或者其子类
// int b = Test.<Integer>add(1, 2.2);//编译错误指定了Integer不能为Float
Number c = Test.<Number>add(1, 2.2); //指定为Number所以可以为Integer和Float
}
//这是一个简单的泛型方法
public static <T> T add(T x, T y) {
return x;
}
}
```
## 类型擦除引起的问题及解决方法
### 类型检测针对谁?
```Java
public static void main(String[] args) {
ArrayList<String> arrayList=new ArrayList<String>();
arrayList.add("123");
arrayList.add(123);//编译错误
}
```
类型擦除后原始类型为Object是应该运行任意引用类型的添加的。可实际上却不是这样这恰恰说明了关于泛型变量的使用是会在编译之前检查的。
那么,这么类型检查是针对谁的呢?我们来看例子:
```Java
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<String>();
arrayList.add(1); //编译报错
ArrayList<String> arrayList1 = new ArrayList(); //第一种 情况
arrayList1.add(1); //编译报错
ArrayList arrayList2 = new ArrayList<String>();//第二种 情况
arrayList2.add(1);
}
```
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
### 自动类型转换
因为类型擦除的问题,所以所有的泛型类型变量最后都会被替换为原始类型。这样就引起了一个问题,既然都被替换为原始类型,那么为什么我们在获取的时候,不需要进行强制类型转换呢?
我么来看一下List的get()方法:
```Java
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
```
可以看到基本各个类都已经自动帮你转了。
### 类型擦除与多态的冲突和解决方法
这个其实是类型擦除引起的最大的问题了。
```Java
public class Test_principle05 {
public static void main(String[] args) {
}
}
class Generic<T> {
//key这个成员变量的类型为T,T的类型由外部指定
private T var;
public T getVar() {
return var;
}
public void setVar(T var) {
this.var = var;
}
}
class MyGeneric extends Generic<Integer>{
@Override
public Integer getVar() {
return super.getVar();
}
@Override
public void setVar(Integer var) {
super.setVar(var);
}
}
```
实际上,从他们的@Override标签中也可以看到,在子类中重写这两个方法一点问题也没有,实际上是这样的吗?
**分析:**
泛型擦除后,父类是下面这样子
```java
class Generic {
//key这个成员变量的类型为T,T的类型由外部指定
private Object var;
public Object getVar() {
return var;
}
public void setVar(Object var) {
this.var = var;
}
}
```
子类还是这样
```java
class MyGeneric extends Generic<Integer>{
@Override
public Integer getVar() {
return super.getVar();
}
@Override
public void setVar(Integer var) {
super.setVar(var);
}
}
```
先来分析setValue方法父类的类型是Object而子类的类型是Date参数类型不一样这如果实在普通的继承关系中根本就不会是重写而是重载。
> **重载Overload:首先是位于一个类之中或者其子类中,具有相同的方法名,但是方法的参数不同,返回值类型可以相同也可以不同。**
>
> 1方法名必须相同。
>
> 2方法的参数列表一定不一样。
>
> 3访问修饰符和返回值类型可以相同也可以不同。
>
> **重写override一般都是表示子类和父类之间的关系其主要的特征是方法名相同参数相同但是具体的实现不同。**
>
> **重写的特征:**
>
> 1方法名必须相同返回值类型必须相同
>
> 2参数列表必须相同
>
> 3访问权限不能比父类中被重写的方法的访问权限更低。例如如果父类的一个方法被声明为public那么在子类中重写该方法就不能声明为protected。
>
> 4子类和父类在同一个包中那么子类可以重写父类所有方法除了声明为private和final的方法。
>
> 5构造方法不能被重写
我们来测试下到底是重载还是重写
```Java
public static void main(String[] args) {
MyGeneric myGeneric = new MyGeneric();
myGeneric.setVar(new Integer(1));
myGeneric.setVar(new Object());//编译错误
}
```
如果是重载的话第四行代码是不会报错的因为调的是不同的重载方法。但是发现编译报错了也就是说没有参数是Object的这样的重载函数。所以说是重写了导致MyGeneric对象只能调用自己重写的方法。
为什么会这样呢?
原因是这样的我们传入父类的泛型类型是IntegerGeneric<Integer>,我们的本意是将泛型类变为如下:
```java
class Generic {
//key这个成员变量的类型为T,T的类型由外部指定
private Integer var;
public Integer getVar() {
return var;
}
public void setVar(Integer var) {
this.var = var;
}
}
```
然后再子类中重写参数类型为Date的那两个方法实现继承中的多态。
可是由于种种原因虚拟机并不能将泛型类型变为Integer只能将类型擦除掉变为原始类型Object。这样我们的本意是进行重写实现多态。可是类型擦除后只能变为了重载。这样类型擦除就和多态有了冲突。JVM知道你的本意吗知道可是它能直接实现吗不能。如果真的不能的话那我们怎么去重写我们想要的Integer类型参数的方法啊。
JVM采用了一个特殊的方法来完成这项功能那就是**桥方法**。
我们对下面这个类进行编译,看其字节码
```Java
public class MyGeneric extends Generic<Integer>{
public static void main(String[] args) {
}
@Override
public Integer getVar() {
return super.getVar();
}
@Override
public void setVar(Integer var) {
super.setVar(var);
}
}
```
字节码:
```Java
// class version 52.0 (52)
// access flags 0x21
// signature LkeyAndDifficultPoints/principle/Generic<Ljava/lang/Integer;>;
// declaration: keyAndDifficultPoints/principle/MyGeneric extends keyAndDifficultPoints.principle.Generic<java.lang.Integer>
public class keyAndDifficultPoints/principle/MyGeneric extends keyAndDifficultPoints/principle/Generic {
// compiled from: MyGeneric.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 9 L0
ALOAD 0
INVOKESPECIAL keyAndDifficultPoints/principle/Generic.<init> ()V
RETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 12 L0
RETURN
L1
LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1
// access flags 0x1
public getVar()Ljava/lang/Integer; //这是我们重写的getVar()方法
L0
LINENUMBER 16 L0
ALOAD 0
INVOKESPECIAL keyAndDifficultPoints/principle/Generic.getVar ()Ljava/lang/Object;
CHECKCAST java/lang/Integer
ARETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public setVar(Ljava/lang/Integer;)V ////这是我们重写的setVar()方法
L0
LINENUMBER 20 L0
ALOAD 0
ALOAD 1
INVOKESPECIAL keyAndDifficultPoints/principle/Generic.setVar (Ljava/lang/Object;)V
L1
LINENUMBER 21 L1
RETURN
L2
LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L2 0
LOCALVARIABLE var Ljava/lang/Integer; L0 L2 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1041
public synthetic bridge setVar(Ljava/lang/Object;)V //编译时由编译器生成的桥方法
L0
LINENUMBER 9 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Integer
INVOKEVIRTUAL keyAndDifficultPoints/principle/MyGeneric.setVar (Ljava/lang/Integer;)V
RETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1041
public synthetic bridge getVar()Ljava/lang/Object; //编译时由编译器生成的桥方法
L0
LINENUMBER 9 L0
ALOAD 0
INVOKEVIRTUAL keyAndDifficultPoints/principle/MyGeneric.getVar ()Ljava/lang/Integer;
ARETURN
L1
LOCALVARIABLE this LkeyAndDifficultPoints/principle/MyGeneric; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
}
```
从编译的结果来看我们本意重写setValue和getValue方法的子类竟然有4个方法。最后的两个方法就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object也就是说子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
所以,虚拟机巧妙的使用了巧方法,来解决了类型擦除和多态的冲突。