Java实现DDD中UnitOfWorkdnf卡盟
Java的泛型详解
泛型的利益
编写的代码可以被差别类型的工具所重用。
由于上面的一个优点,泛型也可以削减代码的编写。
泛型的使用
简朴泛型类
public class Pair {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
}
上面例子可以看出泛型变量为T;
用尖括号(<>)括起来,并放在类名后面;
泛型还可以界说多个类型变量好比上面的例子 first和second差别的类型:
public class Pair {....}
注: 类型变量的界说需要一定的规范:
(1) 类型变量使用大写形式,而且要比较短;
(2)常见的类型变量稀奇代表一些意义:变量E 示意聚集类型,K和V示意要害字和值的类型;T、U、S示意随便类型;
类界说的类型变量可以作为方式的返回类型或者局部变量的类型;
例如: private T first;
用详细的类型替换类型变量就可以实例化泛型类型;
例如: Pair 代表将上述所有的T 都替换成了String
由此可见泛型类是可以看作通俗类的工厂
泛型方式
我们应该若何界说一个泛型方式呢?
泛型的方式可以界说在泛型类,也可以界说在通俗类,那若是界说在通俗类需要有一个尖括号加类型来指定这个泛型方式详细的类型;
public class TestUtils {
public static T getMiddle(T... a){
return a[a.length / 2];
}
}
类型变量放在修饰符(static)和返回类型的中心;
当你挪用上面的方式的时刻只需要在方式名前面的尖括号放入详细的类型即可;
String middle = TestUtils.getMiddle("a", "b", "c");
若是上图这种情形实在可以省略 ,由于编译器能够推断出挪用的方式一定是String,以是下面这种挪用也是可以的;
String middle = TestUtils.getMiddle("a", "b", "c");
然则若是是以下挪用可能会有问题:
如图:可以看到变意思没有办法确定这里的类型,由于此时我们入参通报了一个Double3.14 两个Integer1729 和0 编译器以为这三个不属于同一个类型;
此时有一种解决办法就是把整型写成Double类型
类型变量的限制
有时刻我们不能无限制的让使用者通报随便的类型,我们需要对我们泛型的方式举行限制通报变量,好比如下例子
盘算数组中最下的元素
这个时刻是无法编译通过的,且编译器会报错
由于我们的编译器不能确定你这个T 类型是否有compareTo这个函数,以是这么能让编译器信赖我们这个T是一定会有compareTo呢?
我们可以这么写 这里的意思是T一定是继续Comparable的类
由于Comparable是一定有compareTo这个方式,以是T一定有compareTo方式,于是编译器就不会报错了
由于加了限制那么min这个方式也只有继续了Comparable的类才可以挪用;
若是要限制方式的泛型继续多个类可以加extends 要害字并用&支解如:T extends Comparable & Serializable
限制类型是用&支解的,逗号来支解多个类型变量
类型擦除
岂论什么时刻界说一个泛型类型,虚拟机都市提供一个响应的原始类型(raw type)。原始类型的名字就是删掉类型参数后的泛型类型。擦除类型变量,并替换限制类型(没有限制类型的变量用Object)
列如: Pair 的原始类型如下所示
public class Pair {
private Object first;
private Object second;
public Pair() {
first = null;
second = null;
}
public Pair(Object first, Object second){
this.first = first;
this.second = second;
}
public Object getFirst(){
return first;
}
public Object getSecond(){
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
}
由于上面的T是没有限制变量,于是用Object取代了;
若是有限制变量则会以第一个限制变量替换为原始类型如:
public class Interval implements Serializable{
private T lower;
private T upper;
}
原始类型如下所示:
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
}
翻译泛型表达式
上面说到泛型擦除类型变量后对于无限制变量后会以Object来替换泛型类型变量;
然则我们使用的时刻并不需要举行强制类型转换;
原因是编译器已经强制插入类型转换;
例如:
Pair buddies = ...;
Employee buddy = buddies.getFirst();
擦除getFirst的返回类型后将返回Object类型,然则编译器自动插入Employee的强制类型转换,编译器会把这个方式挪用翻译为两条虚拟机指令;
对原始方式Pair.getFirst的挪用
将返回的Object类型强制转换为Employee类型;
我们可以反编译验证一下
要害的字节码有以下两条
9: invokevirtual #4 // Method com/canglang/Pair.getFirst:()Ljava/lang/Object;
12: checkcast #5 // class com/canglang/model/Employee
虚拟机指令寄义如下:,dnf论坛,
invokevirtual:虚函数挪用,挪用工具的实例方式,凭据工具的现实类型举行派发,支持多态;
checkcast:用于检查类型强制转换是否可以举行。若是可以举行,checkcast指令不会改变操作数栈,否则它会抛出ClassCastException异常;
由此我们可以验证了上述的结论,在反编译后的字节码中看到,当对泛型表达式挪用时,虚拟机操作如下:
对于工具的现实类型举行替换泛型;
检查类型是否可以强制转换,若是可以将对返回的类型举行强制转换;
翻译泛型方式
类型擦除也会出现在泛型方式内里
public static T min(T[] a)
类型擦除后
public static Comparable min(Comparable[] a)
此时可以看到类型参数T已经被擦除了,只剩下限制类型Comparable;
方式的类型擦除带来了两个庞大的问题,看下面的示例:
public class DateInterval extends Pair {
public void setSecond(LocalDate second){
System.out.println("DateInterval: 进来这里了!");
}
}
此时有个问题,从Pair继续的setSecond方式类型擦除后为
public void setSecond(Object second)
这个和DateInterval的setSecond显著是两个差别的方式,由于他们有差别的类型的参数,一个是Object,一个LocalDate;
那么看下面一个列子
public class Test {
public static void main(String[] args) {
DateInterval interval = new DateInterval();
Pair pair = interval;
pair.setSecond(LocalDate.of(2020, 5, 20));
}
}
Pair引用了DateInterval工具,以是应该挪用DateInterval.setSecond。
我们看一下运行效果
然则看了反编译的字节码可能发现一个问题:
17: invokestatic #4 // Method java/time/LocalDate.of:(III)Ljava/time/LocalDate;
20: invokevirtual #5 // Method com/canglang/Pair.setSecond:(Ljava/lang/Object;)V
这里可以看到此处字节码挪用的是Pair.setSecond
这里有个主要的观点就是桥方式
引用Oracle中对于这个征象的注释
为了解决此问题并在类型擦除后保留通用类型的 多态性,
Java编译器天生了一个桥接方式,以确保子类型能够按预期事情。
对于DateInterval类,编译器为setSecond天生以下桥接方式:
public class DateInterval extends Pair {
// Bridge method generated by the compiler
//
public void setSecond(Object second) {
setSecond((LocalDate)second);
}
public void setSecond(LocalDate second){
System.out.println("DateInterval: 进来这里了!");
}
}
那么我们若何验证是否天生这个桥方式呢?我们可以反编译一下DateInterval.java看一下字节码;
public void setSecond(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #5 // class java/time/LocalDate
5: invokevirtual #6 // Method setSecond:(Ljava/time/LocalDate;)V
8: return
我截取了部门发现在 DateInterval的字节码中简直会有一个桥方式,同时验证了上面的问题;
总结:
虚拟机中没有泛型,只有通俗的类和方式
所有的类型参数都用他们的限制类型替换
桥方式被合成来保持多态
为保持类型安全性,必要时插入强制类型转换,dnf助手Kubernetes学习笔记(二):Pod、标签、注解