Skip to content

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); // 编译错误,只能获取Object

6. 泛型的类型擦除

问题:什么是类型擦除?

答案

  • 类型擦除: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.ArrayList

7. 泛型的限制

问题:泛型有哪些限制?

答案

  • 不能创建泛型数组。
  • 不能实例化类型参数。
  • 不能使用基本类型作为类型参数。
  • 不能在静态上下文中使用类型参数。
  • 不能抛出或捕获泛型类的实例。

示例

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); // 5

12. 泛型和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等框架大量使用泛型。