泛型的定义与作用
泛型即参数化类型,而参数概念,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。而参数化类型就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式,然后在调用时传入具体的类型(类型实参)
泛型的本质是为了参数化类型
泛型的优点
1.编译时的强类型检查
泛型要求在声明时指定数据类型,Java编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。
2.避免了类型转换
未使用泛型:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
使用泛型:
List list = new ArrayList();
list.add("hello");
String s = list.get(0);
泛型编程可以实现通用算法
通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读
PCES法则:泛型上下边界
泛型擦除原理,泛型桥方法
泛型擦除做了如下工作:
1.把泛型中的所有类型参数替换为Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。
2.擦除出现的类型声明,即去掉<>的内容,比如T get()方法,声明就变成了Object get();List
3.生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,反省不会产生运行时开销
而对于泛型中的桥方法,举例如下
public class B extends A {
@Override
public void setValue(String value) {
System.out.println("---B.setValue()---");
}
}
通过上述的泛型的擦除机制,实际A类中的方法应该是这样的
// A 类中的 setValue 方法
public void setValue(Object value){
this.value = value;
}
这个时候就出现了B类中的setValue方法参数与A类中的setValue方法参数不一样。安装Java重写方法的规则,B类中单setValue方法实际上并没有重写父类中的方法,而是重载。
所以实际上B有两个setValue方法,一个自己的,一个继承来的。
所以在某些场景,比如反射调用B类中的方法的时候,就有可能会调到父类的setValue方法,但是Java编译器处理了这种情况 如下:
public final class com/example/myapplication/test/A extends com/example/myapplication/test/B {
// access flags 0x1
public setValue(Ljava/lang/String;)V
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 1
LDC "t"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 5 L1
LDC "Not yet implemented"
ASTORE 2
L2
ICONST_0
ISTORE 3
L3
NEW kotlin/NotImplementedError
DUP
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
LDC "An operation is not implemented: "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESPECIAL kotlin/NotImplementedError. (Ljava/lang/String;)V
CHECKCAST java/lang/Throwable
ATHROW
L4
LOCALVARIABLE this Lcom/example/myapplication/test/A; L0 L4 0
LOCALVARIABLE t Ljava/lang/String; L0 L4 1
MAXSTACK = 4
MAXLOCALS = 4
// access flags 0x1041
public synthetic bridge setValue(Ljava/lang/Object;)V
L0
LINENUMBER 3 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/String
INVOKEVIRTUAL com/example/myapplication/test/A.setValue (Ljava/lang/String;)V
RETURN
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public ()V
L0
LINENUMBER 3 L0
ALOAD 0
L1
LINENUMBER 3 L1
INVOKESPECIAL com/example/myapplication/test/B. ()V
RETURN
L2
LOCALVARIABLE this Lcom/example/myapplication/test/A; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
@Lkotlin/Metadata;(mv={1, 4, 3}, bv={1, 0, 3}, k=1, d1={"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0002\u0008\u0002\u0018\u00002\u0008\u0012\u0004\u0012\u00020\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0003J\u0010\u0010\u0004\u001a\u00020\u00052\u0006\u0010\u0006\u001a\u00020\u0002H\u0016\u00a8\u0006\u0007"}, d2={"Lcom/example/myapplication/test/A;", "Lcom/example/myapplication/test/B;", "", "()V", "setValue", "", "t", "app_debug"})
// compiled from: A.kt
}
会发现其中存在两个setValue,其参数值不一样,其中Object类型的就是Java编译器帮我们生成的桥方法
类型边界
类型边界可以对泛型的类型参数设置限制条件
类型边界的语法形式如下:
示例:
public class GenericsExtendsDemo01 {
static > T max(T x, T y, T z) {
T max = x; // 假设x是初始最大值
if (y.compareTo(max) > 0) {
max = y; //y 更大
}
if (z.compareTo(max) > 0) {
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main(String[] args) {
System.out.println(max(3, 4, 5));
System.out.println(max(6.6, 8.8, 7.7));
System.out.println(max("pear", "apple", "orange"));
}
}
// Output:
// 5
// 8.8
// pear
类型边界可以设置多个,语法形式如下:
类型通配符
类型通配符一般是使用?代替具体的类型参数。例如List在逻辑上是List
上界通配符语法形式为
下界通配符语法形式为
上界和下界通配符不能同时使用
无界通配符语法形式为
其有两种应用场景:
1.可以使用Object类中提供的功能来实现的方法
2.使用不依赖于类型参数的泛型类中的方法