1. 为什么要使用泛型?
Java 中引入泛型是为了提高代码的类型安全性和重用性。在 Java 5 中引入泛型后,可以通过在类、接口、方法、变量等地方使用泛型,将类型作为参数传递给类或方法,从而使代码更具可读性、可维护性和可扩展性。
在使用泛型之前,程序员需要自己手动进行类型转换,这样容易引起类型错误和运行时异常。而使用泛型之后,编译器会自动检查类型,并在编译时发现类型错误,从而提高了程序的类型安全性。
此外,使用泛型还可以提高代码的重用性。在使用泛型的情况下,可以编写一些通用的代码,而不必为每种数据类型都编写一份代码。这样可以减少代码的重复性,提高代码的可维护性和可扩展性。
private static int add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static float add(float a, float b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
private static double add(double a, double b) {
System.out.println(a + "+" + b + "=" + (a + b));
return a + b;
}
如果没有使用泛型,要实现不同类型的加法,每种类型都需要重载一个add方法,通过泛型,我们可以复用为一个方法:
private static <T extends Number> double add(T a, T b) {
System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}
2. 泛型类如何定义使用?
泛型类是一种可以处理不同类型数据的类。泛型类的定义需要在类名后面加上尖括号,括号内可以是任意标识符,通常使用单个大写字母表示类型参数。例如,以下是一个泛型类的定义:
public class MyClass<T> {
private T value;
public MyClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在这个例子中,MyClass 是一个泛型类,类型参数为 T,该类具有一个属性 value,它的类型为 T。类中定义了一个构造方法和两个访问器方法,用于设置和获取属性值。
可以使用泛型类创建对象,需要在类名后面加上类型参数。例如,可以创建一个 MyClass 对象来存储一个整数值:
MyClass<Integer> myObject = new MyClass<Integer>(42);
此外,还可以定义多元泛型:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
在上面这个例子中,Pair 是一个泛型类,有两个类型参数 K 和 V,代表键和值的类型。可以使用 Pair 类来存储一对键值:
Pair<String, Integer> myPair = new Pair<String, Integer>("foo", 42);
在创建对象时,使用 <String, Integer> 指定类型参数为 String 和 Integer,因此在类中使用的类型都会被替换为 String 和 Integer。
3. 泛型接口如何定义使用?
泛型接口是一种可以定义操作不同类型数据的接口。泛型接口的定义方式与泛型类类似,在接口名后面使用尖括号定义类型参数。以下是一个泛型接口的定义:
public interface MyInterface<T> {
public T getValue();
public void setValue(T value);
}
在这个例子中,MyInterface 是一个泛型接口,类型参数为 T。该接口定义了两个方法 getValue 和 setValue,这些方法的类型会根据实现类传入的类型参数来确定。
可以实现泛型接口来创建具有不同类型行为的类。例如,可以创建一个实现 MyInterface 接口的类来存储整数值:
public class MyClass implements MyInterface<Integer> {
private Integer value;
public MyClass(Integer value) {
this.value = value;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
MyClass 实现了 MyInterface 接口,并将类型参数指定为 Integer。因此,它必须实现 getValue 和 setValue 方法,这些方法的类型都是 Integer。
可以使用 MyClass 类创建对象来存储整数值:
MyInterface<Integer> myObject = new MyClass(42);
在创建对象时,使用 指定类型参数为 Integer,因此在类中使用的类型都会被替换为 Integer。
需要注意的是,如果实现泛型接口的类没有指定类型参数,那么默认使用原生类型(raw type),这意味着代码可能不安全并且会导致警告。因此,建议在实现泛型接口时总是指定类型参数。
4. 泛型方法如何定义使用?
泛型方法是一种可以在方法中处理不同类型数据的方法。泛型方法可以在普通类、泛型类、接口中定义,并且可以包含类型参数。以下是一个泛型方法的定义:
public <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
printArray 是一个泛型方法,类型参数为 T。该方法接收一个泛型数组作为参数,然后遍历数组并打印出每个元素。在方法中,可以使用类型参数 T 来代替实际的类型。
可以使用泛型方法来处理不同类型的数据。例如,可以使用 printArray 方法来打印整型数组和字符串数组:
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"foo", "bar", "baz"};
printArray(intArray); // prints 1, 2, 3, 4, 5
printArray(stringArray); // prints "foo", "bar", "baz"
5. 泛型的上限和下限
泛型可以使用上限和下限来限制类型参数的范围。
泛型的上限是指可以使用的类型参数必须是某个类或接口及其子类或实现类。在泛型中使用上限,可以使用关键字 extends 后跟上限类型来指定,例如:
public class MyClass<T extends Number> {
// ...
}
泛型类 MyClass 中的类型参数 T 是一个上限为 Number 的泛型类型。这意味着 T 只能是 Number 类或其子类,例如 Integer、Double、Float 等。
泛型的下限是指可以使用的类型参数必须是某个类或接口及其父类。在泛型中使用下限,可以使用关键字 super 后跟下限类型来指定,例如:
public void myMethod(List<? super Integer> list) {
// ...
}
泛型方法 myMethod 中的类型参数 ? super Integer 是一个下限为 Integer 的泛型类型。这意味着可以使用的类型参数必须是 Integer 的父类,例如 Number 或 Object。
需要注意的是,使用上限和下限时,不能同时使用。在一个泛型类型中,只能使用一个上限或下限,或者不使用限定符。
另外需要注意的是,使用上限和下限时,类型参数不能用作方法的参数类型或返回类型。例如,下面的代码是错误的:
public <T extends Number> T myMethod(T t) { // Error: T cannot be used as method parameter type
// ...
}
public <T super Integer> T myMethod() { // Error: T cannot be used as method return type
// ...
}
6. 如何理解Java中的泛型是伪泛型?
Java中的泛型被称为伪泛型,是因为Java的泛型是通过类型擦除来实现的。类型擦除是指在编译时将泛型类型转换为它们的非泛型类型或原始类型,并在运行时使用这些类型。
具体来说,Java中的泛型是在编译时实现类型安全检查,但在运行时并不会保留泛型信息。