网站首页 > java教程 正文
一、复杂泛型概述
Java 中的泛型允许在定义类、接口和方法时使用类型参数,提高代码的类型安全性和可重用性。其中复杂泛型更是在实际开发中有着广泛的应用,但也存在一些使用限制。
复杂泛型概述:
泛型,即“参数化类型”。在 Java 中,泛型的本质是为了在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。泛型类型用于类的定义中,被称为泛型类;用于方法上,被称为泛型方法;用于接口中,被称为泛型接口。最典型的应用就是各种容器类,如:List、Set、Map。
泛型的引入提高了代码的安全性。在没有泛型的情况下,通过对类型 Object 的引用来实现参数的“任意化”,但这种“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。而泛型提供了编译时类型安全检测机制,所有的强制转换都是自动和隐式的,在编译的时候能够检查类型安全。
例如,在集合类中,不使用泛型时,ArrayList 可以存放任意类型,可能会出现类型转换错误。如下代码:
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i < arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型测试","item = " + item);
}
程序运行结果会以崩溃结束,因为在使用时都以 String 的方式使用,但集合中包含了 Integer 类型,导致类型转换异常。
为了解决类似这样的问题,泛型应运而生。使用泛型后,代码如下:
List<String> arrayList = new ArrayList<String>();
arrayList.add("aaaa");
// arrayList.add(100); 在编译阶段,编译器就会报错
for (int i = 0; i < arrayList.size(); i++) {
String s = arrayList.get(i);
System.out.println(s);
}
通过泛型来提前检测类型,编译时就通不过可能出现类型转换错误的代码。
二、复杂泛型的使用方法
1. 集合类中的应用
Java 集合框架广泛使用泛型来提供类型安全的集合操作。例如 List<Integer> 表示存储整数的列表,Map<String, Integer> 表示键为字符串、值为整数的映射。在集合类中使用泛型可以确保只存储特定类型的元素,避免了类型转换错误,提高了代码的安全性和可读性。
2. 自定义类和接口
可以定义自己的泛型类和接口,以实现更灵活的数据结构和算法。例如 class MyGenericClass<T> {... },class MyInterface<T> {... }。泛型类和接口可以存储任何类型的对象,根据具体需求指定类型参数,提高了代码的可重用性和灵活性。
3. 泛型方法
可以在方法中使用泛型,以实现更通用的方法。如 public <T> void printArray(T[] array) {... },可以打印任何类型的数组。泛型方法可以根据传入的参数类型自动推断泛型类型,提高了代码的通用性和可读性。
4. 复杂例子
- 泛型边界:可以使用泛型边界来限制类型参数的类型范围,如 class NumberBox<T extends Number>{...},确保只能存储数字类型的对象。通过泛型边界,可以限制泛型类型的范围,提高代码的安全性和可读性。
- 泛型通配符:泛型通配符可以用于表示未知类型或多种类型,如 class WildcardExample{public static void printList(List<?> list){...}},可以接受任何类型的列表。泛型通配符可以增加代码的灵活性,在处理未知类型的集合时非常有用。
- 泛型方法的多态性:泛型方法可以在不同的类型上表现出多态性,如 class PolymorphicGenericMethod{public static<T>T getMiddleElement(T[] array){...}},可以根据传入的数组类型返回不同类型的中间元素。泛型方法的多态性可以提高代码的通用性和可重用性。
- 泛型接口的实现:可以实现泛型接口,并根据具体需求指定类型参数,如 interface Comparable<T>{int compareTo(T o);},class IntegerComparator implements Comparable<Integer>{...},用于比较整数类型的对象。泛型接口的实现可以提高代码的可扩展性和可维护性。
三、类型擦除的底层实现
类型擦除(Type Erasure)是 Java 泛型实现中的一种技术,它在编译时保留泛型类型信息,在生成的字节码中删除泛型类型信息。具体来说,将泛型类型转换为相应的原始类型,例如对于一个泛型类,在编译时会将泛型参数替换成相应的原始类型。如果泛型参数是无界的,则会被替换成 Object 类型;如果泛型参数是有界的,则会被替换成其边界类型。
在运行时,由于没有泛型类型信息,编译器将对类型进行擦除,导致泛型类型的参数化类型被擦除,而只能使用原始类型来代替。这也就意味着,泛型类型在运行时丢失了一部分类型信息,从而导致了一些限制和局限。
例如,在使用泛型类时,无法获得泛型类型的实际类型参数,也无法使用类型参数的特定方法或属性。这些限制需要在代码中进行额外的判断和处理。
此外,类型擦除还会带来一些其他的影响。比如,不能用基本类型去实例化泛型类型,不能创建泛型的实例,不能声明类型为泛型的静态字段,类型转换或 instanceof 不能和泛型类型一起使用,不能创建参数化类型的数组,不能创建、捕获或抛出泛型类型的对象,不能重载泛型类型擦除后具备相同原始类型的方法等。
为了应对类型擦除带来的影响,可以采取一些策略。比如,使用显式的类型转换和检查,避免在泛型中使用基本数据类型,利用泛型通配符和边界,使用 TypeToken 或类似技巧处理反射和泛型,了解并遵循 Java 泛型的最佳实践等。
四、复杂泛型的限制
- Java 泛型不能使用基本类型,因为泛型在编译时,会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类,当然不能使用基本数据类型。例如,使用基本类型的泛型会编译报错,代码如下:List<int> list = new List<int>();。
- 不允许进行实例化,通过类型擦除,泛型方法会转换为原始方法,可能会导致类型转换错误。比如错误代码如下:<T>void test(T t){t = new T();},通过类型擦除,上面的泛型方法会转换为如下的原始方法:void test(Object t){t = new Object();},而实际使用中,实参 t 一般情况下都是 Object 的子类,此时相当于:Integer t = 1;t = new Object();,而 Object 肯定无法转换成 Integer。Java 泛型为了防止此类类型转换错误的发生,禁止进行泛型实例化。
- 不允许进行静态化,静态变量在类中共享,而泛型类型是不确定的,因此编译器无法确定要使用的类型。例如参考下面的代码:static class Demo<T>{private static T t;public static T getT(){return t;}},这里的泛型变量 t 会在编译时报错。备注:这里的静态化针对的对象是泛型变量,并不是泛型方法。
- 不允许直接进行类型转换,但通过通配符可以实现类型转换,为了降低类型转换的安全隐患。示例代码如下:List<Integer> integerList = new ArrayList<Integer>();List<Double> doubleList = new ArrayList<Double>();,不能直接进行类型转换,类型检查会报错,即integerList = doubleList;是错误的。因为如果不限制类型转换,很容易产生ClassCastException异常。Java 泛型不允许直接进行类型转换,但是通过通配符可以实现类型转换(不建议这么做)。代码如下:static void cast(List<?> orgin, List<?> dest){dest = orgin;},public static void main(String[] args){List<Integer> integerList = new ArrayList<Integer>();List<Double> doubleList = new ArrayList<Double>();//通过通配符进行类型转换cast(doubleList,integerList);}。
- 不允许直接使用instanceof运算符进行运行时类型检查,但通过通配符可以实现,为了避免类型转换错误。直接使用instanceof运算符进行运行时类型检查:List<String> stringList = new ArrayList<String>();,不能直接使用instanceof,类型检查报错,即LOGGER.info(stringList instanceof ArrayList<Double>);是错误的。我们可以通过通配符的方式进行instanceof运行期检查(不建议):LOGGER.info(stringList instanceof ArrayList<?>);。
- 不允许创建确切类型的泛型数组,但通过通配符可以实现,为了避免类型转换错误。创建确切类型的泛型数组如下:Demo6<Integer>[] iDemo6s = new Demo6<Integer>[2];会类型检查报错。我们可以通过通配符的方式创建泛型数组(不建议):Demo6<?>[] iDemo6s = new Demo6<?>[2];iDemo6s[0]=new Demo6<Integer>(1);iDemo6s[1]=new Demo6<String>("hello");。
- 不允许定义泛型异常类或者 catch 异常,但可以以异常类作为边界和 throws 泛型类型,为了避免编译错误。直接看代码:// static class Apple<T> extends Exception {// }// static class Orange<T> extends Throwable {// },//7.Java 泛型:可以 throws 泛型类型<T extends Exception>void test7(T t,Class<T> tClass)throws T {try{//...// } catch (T t) {//不允许捕获一个泛型类型的异常}catch(Exception e){//不允许捕获一个泛型类型的异常//...}}。如果假设可以定义泛型异常类,进行类型擦除时会去掉泛型信息,形成同时 catch 两个一样的异常,肯定不能通过编译。Java 泛型为了避免这种错误的产生,不允许定义泛型异常类或者 catch 异常。
- 不允许作为参数进行重载,因为类型擦除之后,两个方法是一样的参数列表,无法重载。例如代码如下:class Test{public void method(List<Integer> list){}public void method(List<String> list){},这样的重载是不被允许的,因为类型擦除后,两个方法的参数列表都是List,无法区分。
- 上一篇: Java类是如何加载的?
- 下一篇: Java程序员必备技能:静态方法的正确使用姿势!
猜你喜欢
- 2025-01-08 VBA|变量的类型、声明、作用域
- 2025-01-08 西门子S7-1200变量如何使用?局部/全局变量、临时变量、静态变量
- 2025-01-08 长知识了!Java 关键字 transient 还能这么用
- 2025-01-08 「C++学习笔记(十)」理解类中的静态成员变量与静态成员函数
- 2025-01-08 Java程序员必备技能:静态方法的正确使用姿势!
- 2025-01-08 Java类是如何加载的?
- 2025-01-08 CPU眼里的:静态、全局、临时变量
- 2025-01-08 面试官问我什么是JMM
- 2025-01-08 C/C++中的内存四区
- 2025-01-08 C语言的随机数函数和静态变量
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)