首页 > 编程笔记 > Java笔记 阅读:7

Java泛型详解(附带实例)

Java 泛型是 JDK 5.0 之后才引入的一个新特性,允许类、接口和方法在定义时使用一个或多个类型参数。这些类型参数在调用时会被实际类型替换,从而增强代码的重用性和类型安全性。

Java 程序中通过使用泛型,可以编写更加通用的代码,同时减少代码中的强制类型转换操作,提高代码的可读性和可维护性。

在 Java 泛型中,可以使用符号 <T>、<E> 和 <K,V> 来定义泛型。其中:
在 Java 中,泛型通过类型擦除技术实现,即在编译时将泛型类型转换为原始类型,避免类型检查的开销和运行时的类型转换。泛型在 Java 集合框架中尤其重要,例如 List 和 Map 等集合类可以存储任意类型的对象,通过使用泛型可以确保集合中元素的类型安全。

Java泛型类

可以使用如下格式声明一个类:
Class 类名<E>
E 是其中的泛型,并没有指定 E 是何种类型的数据。它可以是任何对象或接口,但不能是基本类型数据。

下面代码定义了一个泛型类:
public class Pair<T> {
    private T first;
    private T second;
    public Pair() {first = null; second = null;}
    public Pair(T first, T second) {this.first = first; this.second = second;}
    public T getFirst() {return first;}
    public T getSecond() {return second;}
    public void setFirst(T newValue) {first = newValue;}
    public void setSecond(T newValue) {second = newValue;}
}
Pair 类引入了一个类型变量 T,用 < > 括起来,并放在类名的后面。

泛型类可以有多个类型变量。例如,可以定义 Pair 类,其中第一个成员变量和第二个成员变量使用不同的类型:
public class Pair<T, U> {...}
类定义中的类型变量指定方法的返回类型以及成员变量和局部变量的类型。例如:
private T first;
用具体的类型替换类型变量就可以实例化泛型类型,例如:Pair<String>可以将结果想象成带有构造器的普通类。下面通过一个实例演示泛型类的使用:
public class PairTest {
    public static void main(String[] args) {
        Pair<String> pair = new Pair<String>("Hello", "Java");
        System.out.println("first=" + pair.getFirst());
        System.out.println("second=" + pair.getSecond());
    }
}
程序执行结果为:

first=Hello
second=Java

程序的第 3 行创建了一个泛型类对象 pair,指定该对象的成员变量的类型为 String 类型,并调用其带 String 类型参数的构造方法对其进行初始化,将 pair 对象的第一个成员变量 first 的值设置为 "Hello",第二个成员变量 second 的值设置为 "Java"。

第 4、5 行分别调用 pair 对象的 getFirst()、getSecond() 方法获得成员变量 first、second 的值并输出到控制台。

Java泛型方法

前面已经介绍了如何定义一个泛型类。实际上,还可以定义一个带有参数类型的方法即泛型方法。泛型方法使得该方法能够独立于类而产生变化,泛型方法所在的类可以是泛型类,也可以不是泛型类。

创建一个泛型方法常用的形式如下:
[访问修饰符] [static] [final] <参数类型列表> 返回值 方法名([形式参数列表])
例如,创建一个 GenericMethod 类,在其中声明一个 f() 泛型方法,用于返回调用该方法时所传入的参数类型的类名:
class GenericMethod {
    public<T> void f(T x) {
        System.out.println(x.getClass().getName());
    }
}

public class GenericMethodTest {
    public static void main(String[] args) {
        GenericMethod gm = new GenericMethod();
        gm.f("");
        gm.f(1);
        gm.f(1.0f);
        gm.f('c');
        gm.f(gm);
    }
}
程序执行结果为:

java.lang.String
java.lang.Integer
java.lang.Float
java.lang.Character
GenericMethod

上述程序的第 8 行创建了一个 GenericMethod 类型的对象 gm。

第 9 行调用该对象的 f() 方法,传入参数为 ""。f() 方法通过 getClass() 方法获取传入参数""的类别,并通过 getName() 方法获取该类别的名字,然后输出到控制台。

同理,第 10~13 行分别将 1、1.0f、'c'、gm 所属类别的名字输出到控制台。

注意,使用泛型类时,必须在创建对象的时候指定类型参数的值。而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。因此我们可以像调用普通方法一样调用 f(),编译器会将调用 f() 时传入的参数类型与泛型类型进行匹配。

Java通配类型参数

泛型已经可以解决大多数的实际问题,但在某些特殊情况下,仍然会有一些问题无法轻松地解决。

例如,一个名为 Stats 的类,假设在其中存在一个名为 doSomething() 的方法,这个方法有一个形式参数,也是 Stats 类型,代码如下所示:
class Stats<T extends Number> {
    T[] nums;
    Stats<T[]> obj;
    nums = obj;
}

double average() {
    double sum = 0.0;
    for (int i = 0; i < nums.length; ++i)
        sum += nums[i].doubleValue();
    return sum / nums.length;
}

void doSomething(Stats<T> obj) {
    System.out.println(obj.getClass().getName());
}
下面通过例子测试 Stats 类的使用情况:
public class StatsTest {
    public static void main(String[] args) {
        Integer inums[] = {1, 2, 3, 4, 5};
        Stats<Integer> lobj = new Stats<Integer>(inums);
        Double dnums[] = {1.1, 2.2, 3.3, 4.4, 5.5};
        Stats<Double> dobj = new Stats<Double>(dnums);
        dobj.doSomething(lobj);  // lobj 和 dobj 的类型不相同
    }
}
该程序编译时出错,因为在 StatsTest 类中,“dobj.doSomething(lobj);”这条语句有问题。dobj 是 Stats<Double> 类型,lobj 是 Stats<Integer> 类型,由于实际类型不同,而声明时用的是 void doSomething(Stats<T> obj),它的类型参数也是 T,与声明对象时的类型参数 T 相同。于是在实际使用中,要求 lobj 和 dobj 的类型必须相同。

解决这个问题的办法是使用 Java 提供的通配符“?”,使用形式如下:
genericClassName <?>
现将上面 Stats 类当中的 doSomething() 声明如下:
void doSomething(Stats<?> obj)  // 这里使用了类型通配符
参数 obj 可以表示任意的 Stats 类型。调用该方法的对象就不必和实际参数对象类型一致。

注意,由于泛型类 Stats 的声明中 T 是有上界的,所以:
void doSomething(Stats<?> obj)  // 这里使用了类型通配符
其中,通配符“?”有一个默认的上界,就是 Number。可以改变这个上界,但改变后的上界必须是 Number 类的子类。例如:
Stats <? extends Integer> obj
但是不能写成这样:
Stats <? extends String> obj
因为 Integer 是 Number 的子类,而 String 不是 Number 的子类,所以通配符无法将上界改变得超出泛型类声明时的上界范围。

最后读者需要注意一点,通配符是用来声明一个泛型类的变量的,它不能创建一个泛型类。例如下面这种写法是错误的:
class Stats<? extends Number>{......}

相关文章