The essence of generics is the application ofParameterized Type, That is to say, the data type being operated is specified as a parameter, and the specific type is specified when used.
This parameter type can be used in the creation of classes, interfaces and methods, calledgeneric classes, generic interfaces and generic methodsrespectively.
Before JDK 1.5, onlyObjectis the parent class of all types and type cast The combination of these characteristics can achieve type generalization.
Therefore, during compilation, the compiler cannot check whether the cast of this Object is successful, which may cause a ClassCastException (cast exception).
Let’s take a look at an example to understand the role of generics:
Without using generics (before 1.5)
ArrayList arrayList = new ArrayList(); arrayList.add(100); arrayList.add("abc");//因为不知道取出来的值的类型,类型转换的时候容易出错 String str = (String) arrayList.get(0);
Using generics (after 1.5)
ArrayListarrayList = new ArrayList (); arrayList.add("abc");//因为限定了类型,所以不能添加整形,编译器会提示出错arrayList.add(100);
// 以 ArrayList,ArrayList 为例:ArrayList :泛型类型 E:类型变量(或者类型参数) ArrayList :参数化的类型 Integer:类型参数的实例(或实际类型参数) ArrayList :原始类型
Generic class
class Demo{ private T value; Demo(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } }public class Test { public static void main(String[] args) { Demo demo = new Demo("abc"); demo.setValue("cba"); System.out.println(demo.getValue()); // cba } }
Generic interface
interface Demo{ void print(K k, V v); } class DemoImpl implements Demo { @Override public void print(String k, Integer v) { System.out.println(k + "-" + v); } }public class Test { public static void main(String[] args) { Demo demo = new DemoImpl(); demo.print("abc", 100); } }
Generic method
public class Test { public static void main(String[] args) { int num = get("abc", 100); System.out.println(num); } // 关键 --> 多了,可以理解为声明此方法为泛型方法 public static V get(K k, V v) { if (k != null) { return v; } return null; } }
Type qualification can be used in generic classes, generic interfaces and generic methods, but please pay attention to the following points:
Regardless of whether the qualification is a class or an interface, the keyword extends is used uniformly
You can use the & symbol to give multiple limitations
If there are both interfaces and classes, then there must be only one class and it must be placed first. For example:
public staticT get(T t1,T t2)
Let’s analyze the role of type qualification...
Observe the following code, a compilation error will occur when the type parameters are not type-qualified. The reason is as follows:
Because before compilation, the compiler cannot confirm what type the generic type (T) is
So it defaults to T Is a primitive type (Object).
So you can only call the Object method, but not the compareTo method.
public staticT get(T t1,T t2) { //编译错误 if(t1.compareTo(t2)>=0); return t1; }
When setting bounds on type parameter T, the compilation error will not occur Happens again. Because at this time the compiler defaults to the original type of T as Comparable.
public staticT get(T t1,T t2) { if(t1.compareTo(t2)>=0); return t1; }
Generics in Java are basically implemented at the compiler level.
The generated Java bytecode does not contain type information in generics.
The type parameters added when using generics will be removed by the compiler during compilation. This process is called type erasure.
Look at the following example:
public class Test { public static void main(String[] args) { ArrayListarrayList1 =new ArrayList (); ArrayList arrayList2 = new ArrayList (); // true System.out.println(arrayList1.getClass() == arrayList2.getClass()); } }
Observe the code, two ArrayList arrays are defined here:
One is the ArrayList generic type, which can only store strings, and the other is the ArrayList generic type, which can only store integers.
By comparing their class objects, the result is found to be true.
Explanation that the generic types String and Integer are erased during the compilation process, leaving only the original type (i.e. Object).
Let’s look at another example:
public class Test { public static void main(String[] args) throws Exception{ ArrayListarrayList =new ArrayList (); arrayList.add("abc"); arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, 100); for (int i=0;i Observe the code, here an ArrayList generic type is instantiated as an Integer object
If you call the add method directly, only integer data can be stored.
Use reflection to call the add method, but you can store strings.
Explanation Integer generic instances are erased after compilation, leaving only the original type.
1. Raw type
The raw type (raw type) erases the generic information , and finally the real typeof the type variable in thebytecode.
Any generic type parameter has a corresponding primitive variable.
Once a type variable is erased (crased), it will be replaced with its qualified type (unqualified variables are Object).
// 此时 T 是一个无限定类型,所以原始类型就是 Objectclass Pair{ } // 类型变量有限定,原始类型就用第一个边界的类型变量来替换,即Comparableclass Pair { } // 此时原始类型为 Serializable,编译器在必要的时要向 Comparable 插入强制类型转换 // 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界限定列表的末尾class Pair
2. Type of type parameter
In the following example, the type parameter refers to T, and the type of T is the so-called The type of [type parameter].
Observing the code, we can draw the following conclusions:
Do not specify the type of [type parameter T]. When the types of parameters are inconsistent, the original type takes the same parent class. The minimum level
When specifying the type of [type parameter T], the original type can only be the specified type or a subclass of the type
public class Test { // 定义泛型方法 public staticT add(T x, T y) { return y; } public static void main(String[] args) { // 1.不指定泛型 // 两个参数都是 Integer,所以 T 为 Integer 类型 int i = Test.add(1, 2); // 两个参数分别是 Integer,Float,取同一父类的最小级,T 为 Number 类型 Number f = Test.add(1, 1.2); // T 为 Object Object o = Test.add(1, "asd"); // 2.指定泛型 // 指定了Integer,所以只能为 Integer 类型或者其子类 int a = Test. add(1, 2); //编译错误,指定了 Integer,不能为Float int b=Test. add(1, 2.2); // 指定为Number,所以可以为 Integer,Float Number c = Test. add(1, 2.2); } }
3. Type checking
The type checking of generics is for the reference, not for the referenced object itself.
In the following example, list is a reference object, so the type check is against it.
// 没有进行类型检查,等价于 ArrayList list = new ArrayLis()ArrayList list = new ArrayList(); list.add(100); list.add("hello");// 进行编译检查,等价于 ArrayList list = new ArrayList ();ArrayList list = new ArrayList(); list.add("hello"); list.add(100); // 编译错误
4.类型擦除与多态的冲突
来看下面的例子,这里定义了一个泛型类 Parent,一个实现它的子类 Son,并在子类中重写了父类的方法。
class Parent{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } class Son extends Parent { @Override public void setValue(String value) { super.setValue(value); } @Override public String getValue(){ return super.getValue();} } 在上面提到过泛型的类型参数在编译时会被类型擦除,因此编译后的 Parent 类如下:
class Parent { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }此时对比 Parent 与 Son 的 getValue/setValue 方法,发现方法的参数类型已经改变,从 Object -> String,这也意味着不是重写(overrride) 而是重载(overload)。
然而调用 Son 的 setValue 方法, 发现添加 Object 对象时编译错误。说明也不是重载。
public class Test { public static void main(String[] args) { Son son = new Son(); son.setValue("hello"); // 关键 -->编译错误 son.setValue(new Object()); } }那么问题来了,通过上面的分析?Son 中定义的方法到底是重写还是重载?答案是:重写。这里 JVM 采用了桥方法(Brige)来解决类型擦除和多态引起的冲突。
我们对 Son 进行反编译(”Javap -c 类名.class”),得到如下内容:
Compiled from "Test.java"class Son extends Parent{ Son(); Code: 0: aload_0 1: invokespecial #8 // Method Parent." ":()V 4: return public void setValue(java.lang.String); Code: 0: aload_0 1: aload_1 2: invokespecial #16 // Method Parent.setValue:(Ljava/lang/Object;)V 5: return public java.lang.String getValue(); Code: 0: aload_0 1: invokespecial #23 // Method Parent.getValue:()Ljava/lang/Object; 4: checkcast #26 // class java/lang/String 7: areturn public java.lang.Object getValue(); Code: 0: aload_0 1: invokevirtual #28 // Method getValue:()Ljava/lang/String; 4: areturn public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #26 // class java/lang/String 5: invokevirtual #30 // Method setValue:(Ljava/lang/String;)V 8: return } 发现这里共有 4 个 setValue/getValue 方法,除了 Son 表面上重写的 String 类型,编译器又自己生成了 Object 类型的方法,也称为桥方法。结果就是,编译器通过桥方法真正实现了重写,只是在访问时又去调用表面的定义的方法。
注意事项
不能用基本类型实例化类型参数,可以用对应的包装类来实例化类型参数
// 编译错误ArrayListlist = new ArrayList ();// 正确写法ArrayList list = new ArrayList ();
参数化类型的数组不合法
Demo{ }public class Test { public static void main(String[] args) { // 编译错误 --> 类型擦除导致数组变成 Object [],因此没有意义 Demo [ ] demo =new Demo[10]; } }
不能实例化类型变量
// 编译错误,需要类型参数需要确定类型Demodemo = new Demo
泛型类的静态上下文中不能使用类型变量
public class Demo{ public static T name; public static T getName() { ... } }
不能抛出也不能捕获泛型类的对象
//异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。会导致这里捕获的类型一致try{ }catch(Probleme1){ //do Something... }catch(Problem e2){ // do Something ...} 以上就是11.Java 基础 - 泛型的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!