Appearance
Java泛型面试题
1. 泛型的基本概念
问题:什么是泛型?Java中的泛型有什么作用?
答案:
- 泛型:一种参数化类型的机制,允许在定义类、接口和方法时使用类型参数。
- 作用:
- 提高代码的安全性,避免类型转换错误。
- 提高代码的可读性,明确集合中元素的类型。
- 提高代码的重用性。
2. 泛型的定义
问题:如何定义泛型?
答案:
text
// 泛型类
public class Box<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 泛型接口
public interface Repository<T> {
T findById(int id);
List<T> findAll();
void save(T entity);
}
// 泛型方法
public class Utils {
public static <T> T getFirst(List<T> list) {
return list.get(0);
}
public static <T, U> U convert(T value, Function<T, U> function) {
return function.apply(value);
}
}3. 泛型的使用
问题:如何使用泛型?
答案:
text
// 使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
String value = stringBox.getValue();
Box<Integer> integerBox = new Box<>();
integerBox.setValue(123);
Integer intValue = integerBox.getValue();
// 使用泛型接口
public class UserRepository implements Repository<User> {
@Override
public User findById(int id) {
// 实现
}
@Override
public List<User> findAll() {
// 实现
}
@Override
public void save(User entity) {
// 实现
}
}
// 使用泛型方法
List<String> list = Arrays.asList("a", "b", "c");
String first = Utils.getFirst(list);4. 泛型的类型参数
问题:泛型的类型参数有哪些约定?
答案:
- T:Type,表示类型。
- E:Element,表示元素,常用于集合。
- K:Key,表示键。
- V:Value,表示值。
- N:Number,表示数字。
5. 泛型的通配符
问题:泛型的通配符有哪些?
答案:
- ?:表示未知类型。
- ? extends T:表示T或T的子类。
- ? super T:表示T或T的父类。
示例:
text
// 通配符
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();
// 上界通配符
List<? extends Number> list = new ArrayList<Integer>();
list = new ArrayList<Double>();
// list.add(1); // 编译错误,不能添加元素
// 下界通配符
List<? super Integer> list = new ArrayList<Number>();
list.add(1);
list.add(2);
// Integer i = list.get(0); // 编译错误,只能获取Object6. 泛型的类型擦除
问题:什么是类型擦除?
答案:
- 类型擦除:Java的泛型在编译时会被擦除,运行时无法获取泛型类型信息。
- 原因:为了兼容Java 5之前的版本。
示例:
text
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
// 编译后
List stringList = new ArrayList();
List integerList = new ArrayList();
// 运行时无法获取泛型类型
System.out.println(stringList.getClass()); // class java.util.ArrayList
System.out.println(integerList.getClass()); // class java.util.ArrayList7. 泛型的限制
问题:泛型有哪些限制?
答案:
- 不能创建泛型数组。
- 不能实例化类型参数。
- 不能使用基本类型作为类型参数。
- 不能在静态上下文中使用类型参数。
- 不能抛出或捕获泛型类的实例。
示例:
text
// 不能创建泛型数组
List<String>[] array = new List<String>[10]; // 编译错误
// 不能实例化类型参数
public class Box<T> {
private T value = new T(); // 编译错误
}
// 不能使用基本类型作为类型参数
List<int> list = new ArrayList<int>(); // 编译错误
List<Integer> list = new ArrayList<Integer>(); // 正确
// 不能在静态上下文中使用类型参数
public class Box<T> {
private static T value; // 编译错误
}
// 不能抛出或捕获泛型类的实例
public class Box<T> {
public void method() throws T { // 编译错误
}
public void method() {
try {
// 代码
} catch (T e) { // 编译错误
}
}
}8. 泛型和数组
问题:泛型和数组有什么关系?
答案:
- 不能创建泛型数组。
- 可以使用通配符创建数组。
示例:
text
// 不能创建泛型数组
List<String>[] array = new List<String>[10]; // 编译错误
// 可以使用通配符创建数组
List<?>[] array = new List<?>[10];
// 可以创建Object数组
Object[] array = new List<?>[10];
array[0] = new ArrayList<String>();
array[1] = new ArrayList<Integer>();9. 泛型和集合
问题:泛型和集合有什么关系?
答案:
- 集合框架大量使用泛型。
- 泛型提高了集合的类型安全性。
示例:
text
// 不使用泛型
List list = new ArrayList();
list.add("Hello");
list.add(123);
String s = (String) list.get(0); // 需要类型转换
Integer i = (Integer) list.get(1); // 需要类型转换
// 使用泛型
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误
String s = list.get(0); // 不需要类型转换10. 泛型和比较器
问题:如何使用泛型实现比较器?
答案:
text
// 泛型比较器
public class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return Integer.compare(p1.getAge(), p2.getAge());
}
}
// 使用比较器
List<Person> people = new ArrayList<>();
people.add(new Person("John", 30));
people.add(new Person("Alice", 25));
Collections.sort(people, new AgeComparator());11. 泛型和函数式接口
问题:如何使用泛型实现函数式接口?
答案:
text
// 泛型函数式接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// 使用泛型函数式接口
Function<String, Integer> function = s -> s.length();
Integer length = function.apply("Hello");
System.out.println(length); // 512. 泛型和Optional
问题:Optional如何使用泛型?
答案:
text
// Optional使用泛型
Optional<String> optional = Optional.of("Hello");
Optional<Integer> optionalInt = Optional.of(123);
// 使用Optional
optional.ifPresent(s -> System.out.println(s));
String value = optional.orElse("default");
String value2 = optional.orElseGet(() -> "default");
optional.orElseThrow(() -> new RuntimeException("value is empty"));13. 泛型和Stream
问题:Stream如何使用泛型?
答案:
text
// Stream使用泛型
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// 使用Stream
stream.filter(s -> s.startsWith("a"))
.map(String::toUpperCase)
.forEach(System.out::println);14. 泛型和类型推断
问题:什么是类型推断?
答案:
- 类型推断:编译器根据上下文自动推断泛型类型。
- 菱形语法:Java 7引入的语法糖,简化泛型对象的创建。
示例:
text
// 类型推断
List<String> list = new ArrayList<>(); // 编译器推断出ArrayList<String>
// 方法调用的类型推断
List<String> list = Arrays.asList("a", "b", "c");
// Lambda表达式的类型推断
Function<String, Integer> function = s -> s.length();15. 泛型和桥接方法
问题:什么是桥接方法?
答案:
- 桥接方法:编译器为了保持泛型类型擦除后的多态性而生成的方法。
- 作用:确保子类正确重写父类的泛型方法。
示例:
text
// 父类
public class Parent<T> {
public void method(T t) {
System.out.println("Parent method: " + t);
}
}
// 子类
public class Child extends Parent<String> {
@Override
public void method(String s) {
System.out.println("Child method: " + s);
}
}
// 编译器会生成桥接方法
// public void method(Object t) {
// method((String) t);
// }16. 泛型和递归类型边界
问题:什么是递归类型边界?
答案:
- 递归类型边界:类型参数本身作为类型参数的边界。
- 作用:用于表示可比较的类型。
示例:
text
// 递归类型边界
public class Comparable<T extends Comparable<T>> {
public int compareTo(T other) {
// 实现
}
}
// 使用递归类型边界
public class Person implements Comparable<Person> {
private String name;
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
}17. 泛型和多重边界
问题:什么是多重边界?
答案:
- 多重边界:类型参数可以有多个边界,使用&符号连接。
- 限制:只能有一个类边界,可以有多个接口边界。
示例:
text
// 多重边界
public class MyClass<T extends Number & Comparable<T>> {
// T必须是Number的子类,并且实现Comparable接口
}
// 使用多重边界
public class MyClass<T extends Serializable & Cloneable> {
// T必须实现Serializable和Cloneable接口
}18. 泛型和类型字面量
问题:什么是类型字面量?
答案:
- 类型字面量:表示类型的对象,用于在运行时获取类型信息。
- 作用:解决类型擦除后无法获取泛型类型信息的问题。(使用 Type 代替 T)
示例:
text
// 类型字面量
public interface TypeLiteral<T> {
Class getType();
}
// 使用类型字面量
public class MyTypeLiteral<T> implements TypeLiteral<T> {
private final Class type;
public MyTypeLiteral(Class type) {
this.type = type;
}
@Override
public Class getType() {
return type;
}
}
// 使用
TypeLiteral<String> literal = new MyTypeLiteral<>(String.class);
Class type = literal.getType();19. 泛型和反射
问题:如何通过反射获取泛型信息?
答案:
text
// 通过反射获取泛型信息
public class MyClass<T> {
private List list;
public void method(List list) {
}
}
// 获取字段的泛型信息
Field field = MyClass.class.getDeclaredField("list");
Type fieldType = field.getGenericType();
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
for (Type arg : typeArguments) {
System.out.println(arg);
}
}20. 泛型的最佳实践
问题:使用泛型的最佳实践有哪些?
答案:
- 优先使用泛型集合,而不是原始类型。
- 使用有意义的类型参数名称。
- 尽量限制类型参数的范围。
- 避免使用通配符,除非必要。
- 使用泛型方法提高代码的重用性。
- 注意类型擦除的限制。
- 使用Optional表示可能为null的值。
21. 泛型和协变
问题:什么是协变?
答案:
- 协变:如果A是B的子类,那么Generic(A)是Generic(B)的子类。
- Java中的协变:数组是协变的,泛型不是协变的。
示例:
text
// 数组是协变的
String[] strings = new String[10];
Object[] objects = strings; // 正确
// 泛型不是协变的
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误22. 泛型和逆变
问题:什么是逆变?
答案:
- 逆变:如果A是B的子类,那么Generic(B)是Generic(A)的子类。
- Java中的逆变:使用下界通配符实现逆变。
示例:
text
// 逆变
List<? super Integer> list = new ArrayList<Number>();
list.add(1);
list.add(2);23. 泛型和PECS原则
问题:什么是PECS原则?
答案:
- PECS:Producer Extends, Consumer Super。
- Producer Extends:如果泛型类型只生产数据,使用上界通配符(? extends T)。
- Consumer Super:如果泛型类型只消费数据,使用下界通配符(? super T)。
示例:
text
// Producer Extends
public void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
// Consumer Super
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}24. 泛型和类型安全
问题:如何确保泛型的类型安全?
答案:
- 使用泛型而不是原始类型。
- 限制类型参数的范围。
- 使用通配符时注意类型安全。
- 使用类型字面量获取泛型类型信息。
- 使用反射时注意类型擦除。
25. 泛型的应用场景
问题:泛型有哪些应用场景?
答案:
- 集合框架:List、Set、Map等。
- 函数式接口:Function、Predicate、Consumer等。
- Optional:表示可能为null的值。
- Stream:函数式编程。
- 自定义工具类:提高代码的重用性。
- 框架开发:Spring、Hibernate等框架大量使用泛型。
