Java中的泛型使用详解
目录
- 泛型类
- 语法
- 使用
- 泛型上界
- 定义
- 使用
- 类型擦除
- 通配符
- 通配符上界
- 通配符下界
- 泛型方法
- 语法
- 使用
- 泛型的限制
泛型类
语法
class 泛型类名称<类型形参列表> { // 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> { } class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ { // 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> { // 可以只使用部分类型参数 }
使用
泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
class MyArray<T> { public Object[] array = new Object[10]; public void set(int pos,T val) { array[pos] = val; }http://www.devze.com public T get(int pos) { return (T)array[pos]; } public Object[] getArray() { return array; } } public class Test { public static void main(String[] args) { MyArray<String> myArray = new MyArray<>(); } }
泛型上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
定义
class 泛型类名称<类型形参 extends 类型边界> { ... }
使用
class MyArray<T extends Number> { public Object[] array = new Object[10]; public void set(int pos,T val) { array[pos] = val; } public T get(int pos) { return (T)array[pos]; } public Object[] getArray() { return array; } } public class Test { public static void main(String[] args) { // 编译错误,因为 String 不是 Number 的子类型 // MyArray<String> javascriptmyArray1 = new MyArray<>(); // 正常,因为 Integer 是 Number 的子类型 MyArray<Integer> myArray = new MyArray<>(); } }
class Alg<T extends Comparable<T>> { public T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } }
类型擦除
Java中的泛型擦除是Java泛型实现的一个关键特性,它允许Java在编译时检查类型安全,但在运行时忽略泛型信息。这意味着,尽管你可以在编译时指定泛型类型,但在运行时,JVM看到的只是原始类型,并不保留任何关于泛型参数的信息。
class MyArray<E> { // E 会被擦除为 Object } class MyArray<E extends Comparable<E>> { // E 被擦除为 Comprable }
示例
class Alg<T extends Comparable<T>> { public T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } }
针对上面的代码,编译结果如下:
编译器在类型擦除阶段做什么?
一、擦除泛型类型信息
移除类型参数:编译器会移除所有与泛型相关的类型参数信息。这意味着在编译后的字节码中,将不再包含任何泛型类型参数的信息。替换为原始类型:泛型类型参数会被替换为其上界(如果指定了上界的话,比如T extends Comparable<T>中的T会被替换为Comparable<T>;如果没有指定上界,则默认为Object)。例如,List<String>和List<Integer>在编译后的字节码中都表现为List。二、调整类型信息方法参数、局部变量和字段的类型调整:除了泛型类型本身被擦除外,编译器还会相应地调整方法参数、局部变量和字段的类型,以确保它们在运行时的一致性。三、插入类型转换类型强制转换:由于类型信息在运行时不可用,编译器会在需要的地方插入必要的类型转换。例如,从List<String>中获取元素时,虽然运行时这个列表只是普通的List,但编译器会在幕后添加一个从Object到String的显式类型转换。四、确保类型安全编译时类型检查:在编译阶段,编译器会利用泛型类型信息进行类型检查,确保类型安全。例如,编译器会阻止向List<String>中添加整数或其他非字符串对象,或者尝试将List<Integer>赋值给List<Double>等不兼容的操作。五、桥接方法的生成向后兼容性:为了保持与未使用泛型的旧代码的兼容性,编译器可能会为泛型类生成桥接方法。这些桥接方法允许泛型类的方法与原生类型的方法共存,并且可以相互调用。总的来说,编译器在类型擦除阶段的主要目标是确保泛型代码在运行时能够安全、正确地执行,同时尽量减少对现有代码的影响。通过移除泛型类型信息并插入必要的类型转换,编译器使得泛型代码能够与非泛型代码无缝对接,从而保证了Java平台的整体一致性和稳定性。
通配符
?用于在泛型的使用,即为通配符。
class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test{ //可以传入任意类型的Message public static void fun(Message<?> temp){ System.out.println(temp.getMessage()); } public static void main(String[] args) { Message<String> message = new Message<>(); message.setMessage("你好!"); fun(message); Message<Integer> message2 = new Message<>(); message2.setMessage(199); fun(message2); android } }
通配符上界
<? extends 上界>
上界通配符用于指定一个类型的上界,即泛型参数必须是该类型或其子类。这种通配符主要用于读取操作,因为它允许你安全地读取数据,但不允许你写入数据(除了null,因为null可以被视为任何类型的实例)。
特点:
add()方法:不能添加任何元素(除了null),因为编译器无法确定具体的类型,添加其他元素可能会导致类型安全问题。
get()方法:可以安全地读取元素,并且返回的类型是T或T的子类,但通常需要用T或T的父类(如Object)来接收,因为编译器无法确定具体的子类类型。
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Message<T> { private T message; public T getMessage() { return message; } public void setMessage(T message) { this.message = message; } } public class Test1 { public static void funExtends(Message<? extends Fruit> temp){ System.out.println(temp.getMessage()); //通配符的上界来说 是不可以进行修改元素的 /* temp.setMessage(new Banana()); temp.setMessage(new Apple());*/ //向上转型 Fruit fruit = temp.getMessage(); } public static void main(String[] args) { Message<Apple> message1 = new Message<>()android; message1.setMessage(new Apple()); Message<Banana> message2 = new Message<>(); message2.setMessage(new Banana()); funExtends(message1); funExtends(message2); } }
通配符下界
<? super 下界>
下界通配符用于指定一个类型的下界,即泛型参数必须是该类型或其父类。这种通配符主要用于写入操作,因为它允许你安全地添加T或T的子类类型的元素,但在读取时只能确保是Object类型。
特点:
add()方法:可以添加T或T的子类类型的元素,因为所有这些类型都是下界类型的子类或相同类型。
get()方法:读取元素时,由于类型的不确定性,通常只能用Object类型来接收。如果需要具体的类型,则需要进行类型转换。
class Food { } class Fruit extends Food { } class Apple extends Fruit { } class Banana extends Fruit { } class Plate<T> { private T plate ; public T getPlate() { return plate; } public void setPlate(T plate) { this.plate = plate; } } public class Tpythonest1 { public static void funSuper(Plate<? super Fruit> temp){ // 此时可以修改!!添加的是Fruit 或者Fruit的子类 temp.setPlate(new Apple());//这个是Fruit的子类 向上转型 temp.setPlate(new Banana());//这个是Fruit的本身 // Fruit fruit = temp.getPlate(); //不能接收,这里无法确定是哪个父类 System.out.println(temp.getPlate());//只能直接输出 } public static void main(String[] args) { Plate<Fruit> message1 = new Plate<>(); Plate<Food> message2 = new Plate<>(); funSuper(message1); funSuper(message2); } }
泛型方法
语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
使用
class Alg2 { //泛型方法 public static <T extends Comparable<T>> T findMax(T[] array) { T max = array[0]; for (int i = 1; i < array.length; i++) { if(array[i].compareTo(max) > 0) { max = array[i]; } } return max; } } public class Test2 { public static void main1(String[] args) { Integer[] array = {1,13,51,71,19}; Integer ret = Alg2.<Integer>findMax(array); System.out.println(ret); } }
泛型的限制
1. 不能使用基本数据类型作为泛型类型参数
Java中的泛型只能使用引用数据类型作为类型参数,不能使用基本数据类型(如int、double等)。如果需要使用基本数据类型的泛型,通常需要使用它们对应的包装类(如Integer、Double等)。2. 不能创建泛型类型的数组
由于类型擦除的原因,Java不允许直接创建泛型类型的数组。例如,List<String>[] listArray = new ArrayList<String>[10]; 是不允许的。如果需要类似的功能,可以考虑使用ArrayList<List<String>>等集合类来实现。3. 不能实例化泛型类型的对象(除了使用new操作符的匿名内部类)
Java不允许直接实例化泛型类型的对象,如List<String> list = new List<String>(); 是不允许的。应该使用具体的类来实例化泛型,如List<String> list = new ArrayList<>();。4. 运行时类型查询的限制
由于类型擦除,在运行时无法直接查询泛型参数的具体类型。例如,instanceof 运算符和getClass()方法不能用于检查泛型类型参数的具体类型。这意味着,在运行时,所有的泛型类型都会被当作它们的原始类型(即擦除后的类型)来处理。5. 泛型类型参数不具有继承关系
即使两个类之间存在继承关系,它们的泛型类型参数之间也不具有继承关系。例如,List<A>并不是List<B>的子类,即使A是B的子类。这要求开发者在编写泛型代码时特别注意类型安全。6. 泛型上界和下界的限制
上界:通过extends关键字,可以限制泛型类型参数必须是某个类型的子类或实现类。这有助于在编译时检查类型兼容性,提高代码的安全性。下界:通过super关键字,可以限制泛型类型参数必须是某个类型的父类型。这允许在泛型方法或类中灵活处理不同但兼容的类型。7. 泛型通配符的限制泛型通配符(如?、<? extends T>、<? super T>)用于表示未知类型或受限类型。然而,它们也带来了一些限制,如在使用上界通配符时不能添加除null以外的元素,而在使用下界通配符时读取的元素类型只能为Object等。8. 反射和泛型
Java的反射机制可以在运行时访问类的信息,但由于类型擦除,反射无法直接获取泛型类型参数的具体信息。这要求开发者在使用反射和泛型时需要特别注意类型安全和类型转换的问题。
到此这篇关于Java中的泛型使用详解的文章就介绍到这了,更多相关java泛型内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论