Java

Java 知识量:11 - 45 - 220

4.2 Java泛型><

什么是泛型- 4.2.1 -

泛型(Generics)是Java编程语言中的一种特性,它允许程序员在类、接口和方法中使用类型参数。泛型的主要目的是提高代码的可重用性和可读性。

使用泛型可以使代码适用于各种数据类型,同时保留编译时类型安全的优点。在Java中,泛型是在JDK 5.0中引入的,并且现在被广泛应用在各种数据结构和类库中。

泛型的工作原理是,在定义类、接口或方法时,可以用一个类型参数来代表某种数据类型。在实际使用时,可以将这个类型参数替换为任何你需要的数据类型。

泛型和类型参数- 4.2.2 -

Java泛型是一种允许在类、接口或方法上指定类型参数的功能。这些类型参数在实例化类、实现接口或调用方法时可以由任意类型替换。这样可以使代码更加灵活,且类型安全。

类型参数声明看起来像这样: <T>,这里的 T 是类型参数的名称。这个 T 可以是任何有效的标识符,但通常使用单个大写字母。也可以使用多个类型参数,例如 <K, V>,这里的 K 和 V 是两个不同的类型参数。

下面是一个泛型类和一个泛型方法的示例:

public class Box<T> {  
    private T t; // T stands for "Type"  
  
    public void set(T t) {  
        this.t = t;  
    }  
  
    public T get() {  
        return t;  
    }  
}  
  
public class Main {  
    public static void main(String[] args) {  
        Box<Integer> integerBox = new Box<>();  
        integerBox.set(new Integer(10));  
        System.out.printf("整数值为 :%d\n\n", integerBox.get());  
  
        Box<String> stringBox = new Box<>();  
        stringBox.set(new String("Hello World"));  
        System.out.printf("字符串为 :%s\n", stringBox.get());  
    }  
}

在这个例子中,Box 是一个泛型类,T 是它的类型参数。在 main 方法中,创建了两个 Box 对象:一个是 Box<Integer>,另一个是 Box<String>。在创建这些对象时,将 T 替换为 Integer 和 String。这样,每个 Box 对象都可以保存特定类型的值。

菱形句法- 4.2.3 -

Java菱形语法(Diamond Syntax)是Java 7中引入的一种针对泛型的语法特性,其本质是一种语法糖,用于简化泛型代码的书写和提高代码的可读性。

在Java 7之前,在实例化一个泛型类或调用一个泛型方法时,需要在尖括号中指定泛型参数类型。例如,在Java 7之前,需要这样来创建一个List对象:

List<String> list = new ArrayList<String>();

而在Java 7及以后的版本中,可以省略掉泛型参数类型,编译器会自动推断出正确的类型。因此,使用菱形语法可以将上述代码简化为:

List<String> list = new ArrayList<>();

在这个例子中,尖括号<>中的内容被省略了,就像是一个菱形,因此这种语法被称为“菱形语法”。

菱形语法的优点除了简化代码和提高代码可读性之外,还可以减少代码中的错误,特别是在使用复杂的泛型类型时。例如,在使用嵌套泛型的例子中,使用菱形语法可以使代码更加清晰,避免在类型参数中重复输入类型名称的可能性,从而减少错误的发生。

注意:菱形语法只能用于实例化时的泛型参数,而不能用于方法调用或类型定义中的泛型参数。

类型擦除- 4.2.4 -

Java的类型擦除(Type Erasure)是一个关于Java泛型的重要概念。在Java中,泛型是通过在类、接口和方法上添加类型参数来实现的。然而,在编译后的字节码中,这些类型参数的相关信息会被擦除,这就是所谓的类型擦除。

类型擦除的主要原因是为了使Java语言在添加泛型后仍然保持与旧版本的兼容性。因为在Java泛型被引入之前,Java的所有类型都是在编译期确定的,而不是在运行时确定的。因此,类型擦除可以保证在运行时不会出现类型不匹配的问题。

类型擦除的规则如下:

  • 泛型信息不会被保存在运行时,即泛型类型在运行时都会被替换为它们的边界类型(如果有的话),或者替换为Object类型(如果没有边界的话)。

  • 对于一个泛型类,如果它的类型参数没有被明确指定,那么它的所有类型参数都会被替换为Object类型。

  • 对于一个泛型方法,如果它的类型参数没有被明确指定,那么它的所有类型参数都会被替换为Object类型。

  • 对于一个泛型接口,如果它的类型参数没有被明确指定,那么它的所有类型参数都会被替换为Object类型。

例如,以下的泛型类:

public class Box<T> {  
    private T t;  
}

在编译后,实际上变成了:

public class Box {  
    private Object t;  
}

这就是Java的类型擦除机制。

绑定类型参数- 4.2.5 -

可以使用类型参数来创建可重用的代码。类型参数类似于方法参数,只不过它们用于类型而不是值。类型参数在Java的泛型编程中非常有用。泛型是Java 5引入的特性,允许程序员在类、接口和方法上声明类型参数。

下面是一个简单的Java泛型类示例:

public class Box<T> {  
    private T t;  
  
    public void set(T t) {  
        this.t = t;  
    }  
  
    public T get() {  
        return t;  
    }  
}

在这个例子中,T是一个类型参数。可以使用任何非保留字的名称,但通常使用单个大写字母。这个Box类可以用来存储任何类型的对象,因为可以用任何类型实例化它。例如:

Box<Integer> integerBox = new Box<>();  
integerBox.set(new Integer(10));  
System.out.printf("整数值为 :%d\n\n", integerBox.get());  
  
Box<String> stringBox = new Box<>();  
stringBox.set(new String("Hello World"));  
System.out.printf("字符串为 :%s\n", stringBox.get());

在这个例子中,创建了两个Box对象:一个是Integer类型,另一个是String类型。通过使用类型参数,可以写出可以处理多种数据类型的代码,而不必为每一种数据类型编写一个单独的类。

泛型中的通配符- 4.2.6 -

在 Java 泛型中,通配符(Wildcard)用于表示不确定的类型参数。使用通配符可以使代码更加灵活,同时保持类型安全。

通配符的基本语法是:<?>,其中 ? 表示未知的类型参数。

通配符在 Java 泛型中有以下几种用法:

1、上限通配符(Upper Bounded Wildcard):<? extends T>,表示类型参数是 T 或者 T 的任意父类。示例:

List<? extends Number> list = new ArrayList<>();  
list.add(123); // 允许添加 Integer 类型  
list.add(new Double(456.78)); // 允许添加 Double 类型  
list.add("abc"); // 编译错误,不允许添加 String 类型  
Number num = list.get(0); // 允许获取 Number 类型  
Integer intValue = list.get(0); // 编译错误,不允许获取 Integer 类型

2、下限通配符(Lower Bounded Wildcard):<? super T>,表示类型参数是 T 或者 T 的任意子类。示例:

List<? super Integer> list = new ArrayList<>();  
list.add(123); // 允许添加 Integer 类型  
list.add(new Double(456.78)); // 允许添加 Double 类型  
list.add("abc"); // 允许添加 String 类型  
Integer intValue = list.get(0); // 允许获取 Integer 类型  
Double doubleValue = list.get(0); // 允许获取 Double 类型  
String strValue = list.get(0); // 允许获取 String 类型

3无限制通配符(Unbounded Wildcard):<?>,表示类型参数可以是任意类型。示例:

List<?> list = new ArrayList<>();  
list.add(123); // 允许添加 Integer 类型  
list.add(new Double(456.78)); // 允许添加 Double 类型  
list.add("abc"); // 允许添加 String 类型  
Integer intValue = list.get(0); // 编译错误,不允许获取 Integer 类型  
Double doubleValue = list.get(0); // 编译错误,不允许获取 Double 类型  
String strValue = list.get(0); // 编译错误,不允许获取 String 类型

使用通配符时,需要注意以下几点:

  • 通配符只能在方法参数、返回值和局部变量中使用,不能在类、接口和方法声明中使用。

  • 通配符不能用于基本类型,如 int、double 等。可以使用其对应的包装类型,如 Integer、Double 等。

  • 通配符不能用于数组类型。可以使用集合类型,如 List、Set 等。

泛型方法- 4.2.7 -

Java泛型方法是定义在类或接口中的具有泛型参数的方法。Java泛型方法定义的基本语法格式为:

public <T> void functionName(T parameterName) {  
    // method body  
}

其中,<T>是泛型参数列表,可以有一个或多个,一般放置在返回值前。T称为类型变量,表示一个类型占位符,在方法调用时将会被具体的类型替换。

例如,下面是一个使用泛型方法的例子:

public class Box<T> {  
    public <U> void printBoxContents(U contents) {  
        System.out.println(contents);  
    }  
}

在这个例子中,Box类定义了一个泛型方法printBoxContents,它的参数是一个泛型参数U。当Box类的实例被创建时,将会用实际的类型替换泛型类型。

注意:泛型方法不影响它所在的类的类型。即使一个类定义了泛型方法,该类仍然可以是非泛型的。另外,如果一个方法是静态的,那么它不能使用泛型类的类型参数,所以如果需要让一个静态方法使用泛型能力,必须将它定义为泛型方法。