前言:突然有一天发现被人代码写用了很多的泛型定义,结果自己一敲不通, 太难过了, 下面简单介绍几种常用的泛型定义
1、泛型基本用法
泛型表现一(集合类中的泛型举例)
//这里举例说明List<E>,Comparable<T>
public interface List<E> extends Collection<E> {
// Query Operations
boolean add(E e);
E get(int index);
...
}
public interface Comparable<T> {
public int compareTo(T o);
}
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
//总结: 类名中指定传入的参数类型为E, 然后List类中的方法add, get就只能处理包含E类型的参数, 这种就是属于传入参数就结果也是返回入参类型E
定义在方法中的泛型
普通类中的泛型方法
(直接使用<>表示就可以,在返回类型之前, 权限修饰符后面)泛型类中的泛型方法
// 1.普通类的泛型方法
//普通类中使用泛型方法需要<T>表示,没有<T>编译报错
class Pair {
//这个是普通类的泛型方法的定义
public <T> T flusTeeth(T a) {
System.out.println("我是普通类中的泛型方法,吃东西前先刷牙!");
return null;
}
//这个是普通类的泛型方法的定义
public static <T> T getBane(T t) {
System.out.println("我是普通类中的泛型方法,吃东西前先刷牙!"+t.getClass());
return t;
}
}
// 2.泛型类中的泛型方法
interface Person<E> {
void eat(E e);
//泛型类中的泛型方法
<T> void sleep(T t);
}
class Son<E> implements Person<E> {
private String name="gongxs";
@Override
public void eat(E e) {
System.out.println("我是son,准备吃东西"+e.getClass().getName());
}
//我是泛型类中的泛型方法
@Override
public <T> void sleep(T t) {
System.out.println("睡觉");
}
}
//总结: 不管你是在泛型类或者是普通类中你就把泛型想象成Object类型类看待, 只要注意一点就是泛型方法需要使用<>号来声明一下我是泛型方法,其实其他的和正常的方法都是一样的
定义在接口中的泛型
- 我们在接口中已经声明了
只可以处理E类型的参数
,那么他的实现了也就只能处理父类声明,或者自己额外声明的类型参数
interface Person<E> {
void eat(E e);
<T> void sleep(T t);
}
//注意:我们在接口中已经声明了只可以处理E类型的参数,那么他的实现了也就只能处理父类声明,或者自己额外声明的类型参数
//由此可见,E是父类声明的参数,T是自己扩张的
class LitterSon<T, E> implements Person<E>{
@Override
public void eat(E e) {
}
public T eat(T t, E e) {
return t;
}
//泛型方法
@Override
public <U> void sleep(U t) {
}
}
泛型的通配符?
泛型限定
泛型代码和JVM
- 类型擦除
- 泛型翻译表达式
泛型擦除
泛型定义我们有一个原始类型(就是我们常写的普通类,<>符号之前),擦除类型变量,并替换成限定的类型(没有限定的类型就是Object类型,如果 ? extend 类
就只能是类以及是类的子类 , 类 super ?
就只能是类以及是类的父类)
//普通类中使用泛型方法需要<T>表示
class Pair<T> {
private T first;
private T second;
Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
//<T>因为上面的T没有限定, 所以这里直接是Object类
class Pair<Object> {
private Object first;
private TObject second;
Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
}
泛型翻译
解释:是含有泛型参数的父类在被子类所继承的同时,指定了泛型参数的具体类型,并重写了含有泛型参数的方法;但在编译后泛型擦除了,T 会变成 Object 类型,和子类中的的方法同名类型不一样, 但是其实应该是同一个才对,但是实际上不是一样的,所以要多加一个方法
桥接
在子类方法和父类方法之间。
- 泛型返回类型将变成Object类型(没有限定类情况)
- 由JVM自动的插入类型转换操作
桥接方法
先声明一个父类泛型类,代码如下所示:
//普通类中使用泛型方法需要<T>表示
class Pair<T> {
private T first;
private T second;
Pair(T first, T second) {
this.first = first;
this.second = second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
}
//泛型擦除后上面代码变成下面注释代码
/*class Pair<Object> {
private Object first;
private Object second;
Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public void setFirst(Object first) {
this.first = first;
}
//注意这里的入参是Object类型
public void setSecond(Object second) {
this.second = second;
System.out.println("我是父父类的setEcond方法");
}
}*/
接着子类继承这个父类,并且指定泛型类型
,然后我们重写这个父类的方法setSecond
代码如下所示:
//子类继承父类然后明确了泛型类型参数:LocalDate
class DateInteval extends Pair<LocalDate> {
//这个构造方法也是需要的, 因为父类显示声明了构造方法
DateInteval(LocalDate first, LocalDate second) {
super(first, second);
}
//注意这里的入参是LocalDate,这里大家注意下是重写???
@Override
public void setSecond(LocalDate second) {
super.setSecond(second);
}
}
public class DemoApplication {
public static void main(String[] args) {
Method[] methods = DateInteval.class.getDeclaredMethods();
Arrays.stream(methods).forEach(r->{
Arrays.stream(r.getParameterTypes()).forEach(e->{
System.out.println("方法 "+r.getName()+" 入参类型是:"+e.getTypeName());
});
});
}
}
//运行结果 如下所示:
/*
方法 setSecond 入参类型是:java.time.LocalDate
方法 setSecond 入参类型是:java.lang.Object
*/
看 上面输出结果为什么会出现两个方法呢?明明是父类只声明了一个方法setSecond?
这里我们是因为代码在编译时jvm在对子类进行编译的时候,生成了一个以Object为参数的方法(其实是父类泛型擦除之后生成的Object类型,T–>Object),这个jvm为子类新创建的那个方法就是桥接方法
现在知道了什么是桥接方法,这里提个问题上面子类方法setSecond
是重写嘛???
上代码
//父类方法
public void setSecond(T second) {
this.second = second;
System.out.println("我是父父类的setEcond方法");
}
//擦除变成(仅仅便于理解)
/*public void setSecond(Object second) {
this.second = second;
System.out.println("我是父父类的setEcond方法");
}*/
//子类方法
@Override
public void setSecond(LocalDate second) {
System.out.println("我是子类的setEcond方法");
}
public class DemoApplication {
public static void main(String[] args) {
DateInteval inteval = new DateInteval(LocalDate.now(),LocalDate.now());
//多态调用(使用的是子类的引用调用setSecond)
inteval.setSecond(LocalDate.now());
}
}
//运行结果:
//我是子类的setEcond方法
发现这里输出的我是子类的setEcond方法
可以证明setSecond
方法已经被子类给重写成功了,如果没有被重写肯定是输出我是父父类的setEcond方法
,所以方法桥接其实可以理解是一种另类的方法重写,其实在字节码底层真正执行的还是子类具体类型的方法,为什么不是执行的Object方法呢?
上代码
public void setSecond(java.lang.String);
...
Code:
stack=2, locals=2, args_size=2
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_1
4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
...
public void setSecond(java.lang.Object);
...
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #4 // class java/lang/String
5: invokevirtual #5 // Method print:(Ljava/lang/String;)V
8: return
...
- 方法
public void setSecond(String)
,执行System.out.println()
方法,我们重点看一Object
的方法实现。 - 首先我们通过
args_size
知道这个方法有两个参数(this
和Object
),存放在局部变量表中。 - 代码依次执行
aload_0
和aload_1
将局部变量表中的this
和Object
依次`压入操作数栈栈顶。 checkcast
检查栈顶元素(object
)是否可以强转成String
.invokevirtual #5
也即使调用public void setSecond(String)
;当然会使用栈顶两个参数this
和Object
.- 现在我们明白了,其实绕了一大圈,创建的方法通过类型转换后最终还是调用了重写的方法。