当前位置: 首页 > news >正文

Java 泛型中级面试题及答案

Java 泛型中级面试题及答案

1. 泛型擦除的原理及对运行时的影响

答案

泛型擦除是Java编译器在编译阶段将泛型类型信息移除的过程,使得生成的字节码中仅保留原始类型。这一机制主要是为了保持Java的向后兼容性。在运行时,由于泛型类型信息被擦除,会导致以下限制:

  • 对象创建限制:不能直接使用泛型类型参数创建对象,例如 new T() 这种写法是不允许的。因为在运行时,泛型类型参数 T 已被擦除,编译器无法确定要创建的具体对象类型。
  • 数组初始化限制:无法直接进行泛型数组初始化。这是因为数组在运行时需要知道其元素的确切类型来进行类型检查,而泛型擦除后无法提供这种确切类型信息。
  • 类型判断限制:不能用泛型类型参数进行 instanceof 判断。因为运行时泛型类型信息不存在,无法准确判断对象是否属于某个泛型类型。

代码示例

public class ErasureDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Hello");// 编译错误:无法创建泛型数组// T[] arr = new T[10];// 反射绕过泛型检查try {list.getClass().getMethod("add", Object.class).invoke(list, 123);System.out.println(list); // 输出:[Hello, 123]} catch (Exception e) {e.printStackTrace();}}
}

在上述代码中,首先创建了一个 List<String> 并添加了字符串元素。尝试创建泛型数组会导致编译错误,体现了泛型数组初始化的限制。通过反射绕过泛型检查,向 List<String> 中添加了一个 Integer 类型元素,虽然编译通过,但运行时可能引发 ClassCastException,展示了泛型擦除后可能出现的类型安全问题。

2. 泛型方法与类型推断

答案

泛型方法通过在方法声明中使用 <T> 等形式声明类型参数,类型推断则允许编译器在调用泛型方法时自动确定实际类型。泛型方法的返回值类型通常需要包含泛型参数,并且类型参数可以在参数列表中使用,以增强方法的通用性。

代码示例

public class GenericMethod {// 类型推断示例public static <T> void print(T value) {System.out.println("Value: " + value);}// 多类型参数示例public static <K, V> void put(K key, V value) {System.out.println("Key: " + key + ", Value: " + value);}public static void main(String[] args) {print("Hello"); // 自动推断为Stringprint(123);    // 自动推断为Integerput("name", "Alice");put(1001, "Java");}
}

在这个示例中,print 方法展示了类型推断的功能,编译器根据传入的参数类型自动确定泛型参数 T 的实际类型。put 方法则展示了多类型参数的泛型方法,通过不同的类型参数 KV 来处理不同类型的键值对。

3. 通配符 <?><? extends T> 的区别

答案

  • <?>(无界通配符):表示可以接受任何类型,它用于在对集合元素类型完全未知的情况下进行操作。
  • <? extends T>(上界通配符):表示可以接受 T 类型及其子类类型,用于限制集合只能操作 T 及其子类类型的元素。

使用场景

  • <?> 的场景:当你只需要对集合进行读取操作,且不关心集合中元素的具体类型时,使用无界通配符。例如,简单地打印集合中的所有元素。
  • <? extends T> 的场景:当你需要读取集合中的元素,并且希望确保这些元素是特定类型 T 或其子类时,使用上界通配符。比如计算一组数字(Number 及其子类)的总和。

代码示例

import java.util.Arrays;
import java.util.List;public class WildcardDemo {public static void printList(List<?> list) {for (Object obj : list) {System.out.println(obj);}}public static double sumNumbers(List<? extends Number> numbers) {double total = 0;for (Number num : numbers) {total += num.doubleValue();}return total;}public static void main(String[] args) {List<Integer> intList = Arrays.asList(1, 2, 3);printList(intList);System.out.println("Sum: " + sumNumbers(intList));}
}

printList 方法使用无界通配符 <?>,可以接受任何类型的列表并打印其元素。sumNumbers 方法使用上界通配符 <? extends Number>,只能接受 Number 及其子类类型的列表,并计算其总和。

4. 泛型类与泛型方法的优先级

答案

当泛型类和泛型方法同时存在时,编译器优先使用方法级泛型参数。如果方法没有定义泛型参数,那么编译器会使用类的泛型参数。

代码示例

public class GenericPriority<T> {public void print(T value) {System.out.println("Class Generic: " + value);}public <T> void print(T value) {System.out.println("Method Generic: " + value);}public static void main(String[] args) {GenericPriority<String> gp = new GenericPriority<>();gp.print("Hello");    // 调用方法级泛型gp.print(123);        // 编译错误(类泛型为String)}
}

在这个示例中,GenericPriority 类定义了泛型参数 T,同时类中又有一个与类泛型参数同名但独立的泛型方法 print。在调用 print 方法时,编译器优先使用方法级泛型参数,所以 gp.print("Hello") 调用的是方法级泛型。而 gp.print(123) 由于类泛型为 String,与传入的 Integer 类型不匹配,会导致编译错误。

5. 泛型与数组的兼容性问题

答案

Java不允许直接创建泛型数组,因为泛型擦除会导致运行时无法确定数组元素的确切类型,从而引发潜在的类型安全问题。泛型数组在运行时其实际类型为 Object[],这可能会使程序在运行时出现 ClassCastException。不过,可以通过反射或者使用 List 来间接实现类似泛型数组的功能。

解决方案代码示例

public class GenericArray<T> {private T[] array;@SuppressWarnings("unchecked")public GenericArray(int size) {array = (T[]) new Object[size]; // 强制转换绕过限制}public void set(int index, T value) {array[index] = value;}public T get(int index) {return array[index];}public static void main(String[] args) {GenericArray<Integer> arr = new GenericArray<>(3);arr.set(0, 10);arr.set(1, 20);System.out.println(arr.get(0)); // 输出:10}
}

在上述代码中,GenericArray 类通过在构造函数中使用强制类型转换 (T[]) new Object[size] 来绕过无法直接创建泛型数组的限制。但这种方式需要手动保证类型安全,因为运行时数组实际类型为 Object[]。如果不小心向数组中添加了不匹配的类型,运行时可能会抛出 ClassCastException

6. 泛型在集合框架中的应用

答案

Java集合框架广泛应用泛型来确保类型安全。例如 List<E>Map<K, V> 等接口,通过泛型参数 EKV 来指定集合中元素或键值对的类型。使用泛型后,集合在编译期就能进行类型检查,避免运行时出现 ClassCastException,提高了代码的可靠性和可读性。

代码示例

import java.util.ArrayList;
import java.util.List;public class CollectionGeneric {public static void main(String[] args) {List<String> names = new ArrayList<>();names.add("Alice");names.add("Bob");// 编译错误:类型不匹配// names.add(123);for (String name : names) {System.out.println(name.length());}}
}

在这个例子中,创建了一个 List<String> 集合,只能添加 String 类型的元素。如果尝试添加 Integer 类型元素(如 names.add(123)),会导致编译错误,从而在编译阶段就发现类型不匹配问题。通过使用泛型,增强了集合操作的类型安全性。

7. 泛型与继承的关系

答案

泛型类型之间不存在继承关系,例如 List<String> 不是 List<Object> 的子类型。然而,可以通过通配符来实现有限制的继承关系。比如 List<? extends Object> 是合法的,表示可以接受 Object 及其子类类型的列表;但直接将 List<String> 赋值给 List<Object> 会导致编译错误。

代码示例

import java.util.ArrayList;
import java.util.List;public class GenericInheritance {public static void process(List<Object> list) {list.add("Test");}public static void main(String[] args) {List<String> strList = new ArrayList<>();// 编译错误:不兼容的类型// process(strList);// 使用通配符实现多态process(new ArrayList<Object>());}
}

在上述代码中,process 方法接受 List<Object> 类型的参数。当尝试将 List<String> 传递给该方法时,会出现编译错误,因为 List<String> 不是 List<Object> 的子类型。但通过使用通配符,如 process(new ArrayList<Object>()),可以实现类似多态的效果,将符合 List<Object> 要求的列表传递给方法。

http://www.dtcms.com/a/462067.html

相关文章:

  • Java虚拟机(VM)相关线程与方法的说明展开
  • Windows 11 安装 JDK 25
  • web 消息推送
  • 手写MyBatis第99弹:MyBatis线程安全问题与连接池调优
  • springboot088健康追踪系统软件lgl(源码+部署说明+演示视频+源码介绍+lw)
  • Levenberg-Marquardt( LM)算法详解和二次曲线拟合实战
  • 局域网站建设模版模拟装修设计app免费
  • JavaWeb和MavenJavaWeb项目部署到Tomcat的三种方法
  • 备案的网站建设书是什么网站后台策划
  • 组合两个表-力扣
  • 网站内页不收录医院网站建设ppt
  • 1.2 Java语言的特性
  • 网络TCP解析
  • C++ -->STL 搜索平衡二叉树 AVL树
  • 建德做网站wordpress指定分类名称
  • 如何偷别人dedecms网站的模板购物网站难做
  • 网站建设属于硬件还是软件网易云音乐wordpress
  • 帝国cms 微信小程序的登录逻辑
  • 什么网站可以教做面包福州企业网站模板建站
  • 视频网站建设wordpress主题路径
  • 将爬虫部署到服务器:Scrapy+Scrapyd 实现定时任务与监控
  • billfish素材管理工具小说阅读
  • 数据结构-ArrayList与顺序表
  • 如何给移动固态硬盘分区?分区后无法识别怎么办?
  • 怎么注册网自己的网站吗天津企业网站建站模板
  • 基于spark的基于可穿戴设备运动数据预测
  • ref/reactive 声明变量 有什么区别??
  • 多模态RAG面试笔记整理
  • VoceChat:轻量可自托管的聊天系统
  • 网站自适应周口网站建设电话