JavaTutorial

泛型

泛型的引入

大家可以看一看我的博客:DIJKSTRA的双栈表达式求值算法

这是一个求10以内的加减乘除的计算器

里面用到了栈:

Stack<String> ops = new Stack<String>();    
Stack<Double> vals = new Stack<Double>();

大家可以看到<>里面可以是各种各样类型的数据,甚至你自己定义的类型(类,接口以及其他泛型参数等)也可以放进去。

为什么定义Stack类的时候,不把String写到类型成员的位置上?采用泛型,可以等到构造栈的时候再指定栈内的数据是何种类型。如果定义类型的时候就指定了内部的数据类型,就需要定义StringStackDoubleStack等类型,不需要读取栈内部成员的时候,还需要定义AbstractStack类型。

为什么定义Stack类的时候,不把相关的类型成员置为Object类型?如果容纳的是Object类型,那么栈里就会同时存在字符串和数字,严重情况下会造成堆污染。

什么是泛型

假如我们想要编写泛型,泛型声明格式如下:

修饰符 class 类名 <泛型> {
    类成员;
}

显然,编写泛型类比普通类要复杂。

什么时候需要泛型

通常来说,泛型类一般用在集合类中,例如ArrayList<T>。如果某个类引用了其他类型的对象,而被引用的对象可能属于这个类无法推断的任何类型,那么就需要编写泛型类。

如何编写泛型类

可以按照以下步骤来编写一个泛型类。

首先,按照某种类型,例如:String,来编写类:

public class Pair {
    private String first;
    private String last;
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }
}

然后,标记所有的特定类型,这里是String

public class Pair {
    private String first;
    private String last;
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }
}

最后,把特定类型String替换为T,并申明<T>

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}

熟练后即可直接从T开始编写。

泛型参数和泛型返回值

for-each遍历泛型列表

废话不多说,直接开始代码:

public class FirstSample {
    public static void main(String[] args) {

        List<int> nums = Arrays.asList(1, 2, 3, 4, 5);
        for (int e: nums) {
            System.out.println(e);
        }
     }
}

可以看到,for-each循环,定义了一个变量e用来存储集合中的每一个元素,然后每次都输出e的值

如果e的类型不是int?泛型参数规定了e

for-each循环的好处在于,不需要定义循环下标,也就不用担心下标的初始值以及终止值,推荐使用for-each来遍历数组

静态方法和静态泛型

编写泛型类时,要特别注意,类型的泛型参数<T>不能用于静态方法。例如:

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 对静态方法使用<T>:
    public static Pair<T> create(T first, T last) {
        return new Pair<T>(first, last);
    }
}

上述代码会导致编译错误,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T

有些同学在网上搜索发现,可以在static修饰符后面加一个<T>,编译就能通过:

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 可以编译通过:
    public static <T> Pair<T> create(T first, T last) {
        return new Pair<T>(first, last);
    }
}

但实际上,这个<T>Pair<T>类型的<T>已经没有任何关系了。

对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可。对于上面的create()静态方法,我们应该把它改为另一种泛型类型,例如,<K>

public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 静态泛型方法应该使用其他类型区分:
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }
}

这样才能清楚地将静态方法的泛型参数和对象类型的泛型参数区分开。构造列表的Array.asList()方法就是这种静态泛型方法。

调用静态泛型方法和构造函数

多个泛型参数

还可以将多个类型作为泛型参数。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>

public class Pair<T, K> {
    private T first;
    private K last;
    public Pair(T first, K last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public K getLast() { ... }
}

使用的时候,需要指出两种类型:

Pair<String, Integer> p = new Pair<>("test", 123);

Java标准库的Map<K, V>就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。

泛型参数和操作符