本文分类:
Java

Java的泛型通过使用类型参数,使程序具有更好的可读性和安全性,本文总结了Java泛型的有关知识点,并分析了泛型的实现原理,最后分析了形如<T extends Comparable<? super T>>的复杂泛型声明。

泛型方法

泛型方法简洁易用,应该优先使用泛型方法。下面的代码定义了一个泛型方法,泛型参数放到返回值前面,这样设计的目的是避免某些情况下的歧义,如方法声明<T, U> T foo(T t, U u),假如类型参数放到返回值放到后面,T<T, U>foo(T t, U u),看起来就像用逗号分隔的两个表达式。

通常,编译器有足够的信息可以推断出类型参数信息,只需如第10行所示调用即可,如果编译器不能自动推断出类型,或者需要明确指定类型参数的类型时,语法如第11行,即把类型参数放到方法名前,点号之后,并用尖括号括起来,点号前面根据实际情况必须是this、对象名或类名。

public class GenericMethod {
    public static <T> T print(T t) {
        String info = (t == null ? "" : t.toString());
        System.out.println(info);
        return t;
    }

    public static void main(String[] args) {
        String s1 = "hiwzc.com";
        String s2 = print(s1);
        String s3 = GenericMethod.<String>print(s1);
    }
}

泛型构造方法

泛型构造方法是泛型方法的一个特例,非泛型类也可以有泛型构造方法,下面的例子演示了泛型构造方法的用法,代码无实在的意义。

public class GenericConstructor {
    private String value;

    public GenericConstructor() {
        this.value = "";
    }

    public GenericConstructor(Date value) {
        this();
        if (value != null) {
            this.value = String.valueOf(value.getTime());
        }
    }

    public <T extends Number> GenericConstructor(T value) {
        this();
        if (value != null) {
            this.value = value.toString();
        }
    }

    public String getValue() {
        return value;
    }

    public static void main(String[] args) {
        GenericConstructor g = new GenericConstructor();
        System.out.println(g.getValue());

        g = new GenericConstructor(new Date());
        System.out.println(g.getValue());

        g = new GenericConstructor(1);
        System.out.println(g.getValue());

        g = new GenericConstructor(3.14);
        System.out.println(g.getValue());
    }
}

泛型类

下面的代码演示了如何定义一个泛型类,该类是一个值容器,通过把传入的值设定为 volatile 类型保证值 set 对 get 的可见性。

public class Holder<T> {
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return this.value;
    }

    public static void main(String[] args) {
        Holder<String> holder = new Holder<>();
        holder.set("hiwzc.com");
        String value = holder.get();
        System.out.println(value);
    }
}

泛型接口

除了可以定义泛型方法和泛型类,还可以定义泛型接口。JDK 的 Comparable 接口就是一个泛型接口,代码如下:

package java.lang;
import java.util.*;

public interface Comparable<T> {
    public int compareTo(T o);
}

如果类实现了某种具体类型的泛型接口,那么类就不需要泛型化,否则,实现类也必须泛型化,把类型参数传递给接口,下面的Apple.java就没有必要泛型化。

public class Apple implements Comparable<Apple> {
    private int weight;

    public Apple(int weight) {
        this.weight = weight;
    }

    @Override
    public int compareTo(Apple o) {
        return Integer.compare(this.weight, o.weight);
    }
}

泛型的继承

泛型类可以有继承关系,但有继承关系的类作为类型参数的同一个泛型类之间没有任何关系。比如 Manager 是 Employee 的子类,那么 ArrayList<Manager>List<Manager> 的子类,ArrayList<Manager> 可以赋值给 List<Manager>ArrayList<Employee>List<Employee> 的子类,ArrayList<Employee> 可以赋值给 List<Employee>。然而 ArrayList<Manager> 不是 ArrayList<Employee> 的子类,也不是 List<Employee> 的子类。用代码描述如下:

class Employee {

}

class Manager extends Employee {

}

public class GenericInherit {

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<Employee>();
        List<Manager> managers = new ArrayList<Manager>();

        // List<Employee> employees2 = new ArrayList<Manager>(); // ERROR
        // ArrayList<Employee> employees3 = new ArrayList<Manager>(); // ERROR

        Employee[] employeeArray = new Manager[3];

        List raw = managers;
        List<Employee> trans = raw;
    }
}

上面注释掉的两行代码是不能通过编译的,这一点和数组不同,可以将Manager数组赋值给Employee数组,因为数组能够记住它的元素类型。可以将参数化类型转换为一个原始类型,再将原始类型赋值给参数化类型,但这不是类型安全的,因为任意不想关的两个类的泛型化容器都可以采用这种方式赋值,实践中应避免这种做法。

泛型与可变参数

下面的这段代码可以很清晰的理解泛型的继承规则,只有 param2 和 para4 是 Collection<Object> 的子类,pram 和 param3 与 Collection<Object> 没有任何关系。

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTest {
    public static void func(Object... param) {
        System.out.println("可变参数");
    }

    public static void func(Collection<Object> param) {
        System.out.println("集合");
    }

    public static void main(String[] args) {
        Collection<String> param = new ArrayList<>();
        func(param);

        Collection<Object> param2 = new ArrayList<>();
        func(param2);

        List<String> param3 = new ArrayList<>();
        func(param3);

        List<Object> param4 = new ArrayList<>();
        func(param4);
    }
}

输出为

可变参数
集合
可变参数
集合

泛型限定

泛型的限定有类型限定和通配符限定,类型限定是对泛型中类型变量的约束,是泛型声明的一部分,而通配符限定则用于变量的声明。比如类定义class Test<T extends Employee> extends Holder<T> {}表示Holder中只能保存一种类型T,这种类型是Employee或其子类。而方法声明private static void foo(Holder<? extends Employee> e);则表示参数e的Holder中可以持有任意Employee或其子类。

类型限定

类型限定用于对类型变量加以约束,限制类型变量必须是某个类的子类或实现了某些接口,或者两者,语法是 <T extends 限定列表>,限定列表中的各项用 & 分割,并最多只能出现一个类,如果限定列表中有类,则类必须写在第一个。下面的代码演示了类型限定的使用,泛型方法的参数必须是 Employee 以及子类,并且实现了 Serializable 接口。

public class TypeBounds {
    public <T extends Employee & Serializable> void doSomething(T t) {
        // do something ...
    }
}

子类限定通配符

类似泛型限定,<? extends Type> 表示所有 Type 类型的子类。如下代码所示,Holder<Manager>Holder<? extends Employee> 的子类,所以能够用 Holder<Manager> 调用 wirdcard 方法。但是通配符类型也有限制,extends 通配符只能读取而不能写入。对于读取,编译器知道读取的内容肯定是 Employee 的子类,所以读取的值可以赋值给 Employee,但是写入时,编译器只知道是 Employee 的子类,但不知道具体是哪个特定子类,不能调用set方法,除非set(null)。

public class GenericExtendWildcard {
    public static void wildcard(Holder<? extends Employee> arg) {
        Employee value = arg.get();
        arg.set(null);
        // arg.set(new Manager()); // ERROR
    }

    public static void generic(Holder<Employee> arg) {
        Employee value = arg.get();
        arg.set(null);
        arg.set(new Manager());
    }

    public static void main(String[] args) {
        Holder<Employee> he = new Holder<>();
        Holder<Manager> hm = new Holder<>();

        generic(he);
        // generic(hm);

        wildcard(he);
        wildcard(hm);
    }
}

超类限定通配符

与子类限定通配符类似,<? super Type> 用于限制类型参数为所有 Type 的超类,这种限定可以写入任意 Manager 对象或 Manager 的子类,但只能把读取的值赋值给 Object。

public class GenericSuperWildcard {
    public static void wildcard(Holder<? super Manager> arg) {
        Object value = arg.get();
        arg.set(null);
        arg.set(new Manager());
    }

    public static void generic(Holder<Manager> arg) {
        Employee value = arg.get();
        arg.set(null);
        arg.set(new Manager());
    }

    public static void main(String[] args) {
        Holder<Employee> he = new Holder<>();
        Holder<Manager> hm = new Holder<>();

        // generic(he);
        generic(hm);

        wildcard(he);
        wildcard(hm);
    }
}

无限定通配符

读取的值只能赋值给 Object,只能写入 null。由于无限定通配符操作受限,语法简洁,对于不需要知道实际类型的情况,无限定通配符可读性更好,也更安全。

public class GenericWildcard {
    public static boolean isNull(Holder<?> arg) {
        return arg.get() != null;
    }
}

通配符捕获

由于通配符不是一个类型,所以需要用到类型的时候,可以通过定义一个泛型辅助方法捕获通配符参数,比如下面的代码,func泛型方法可以捕获isNull中的通配符参数。

public class GenericWildcard2 {
    public static boolean isNull(Holder<?> arg) {
        if (arg.get() != null) {
            func(arg);
        }
        return arg.get() != null;
    }

    public static <T> void func(Holder<T> h) {
        T t = h.get();
        System.out.println(t.getClass().toString());
        h.set(h.get());
    }

    public static void main(String[] args) {
        Holder<String> h = new Holder<String>();
        h.set("www.hiwzc.com");
        System.out.println(isNull(h));
        System.out.println(isNull(new Holder<Integer>()));
    }
}

代码输出

class java.lang.String
true
false

使用泛型捕获,只有在编译期可以确定类型参数是一个确定的单个类型时才可以捕获,比如下面的代码不能使用通配符捕获,因为 List 里边可以保存多个不同类型的 Holder<?>。

public class GenericWildcard {
    public static void funcA(List<Holder<?>> arg) {
        // The method funcB(List<Holder<T>>) in the type GenericWildcard
        // is not applicable for the arguments (List<Holder<?>>)
        // funcB(arg);
    }

    public static <T> void funcB(List<Holder<T>> arg) {

    }
}

泛型的实现

类型擦除

Java 泛型是一种编译器现象,虚拟机本身不支持泛型,Java 通过编译器的类型擦除实现泛型。每个泛型类型都在编译时转换为原始类型(raw type),原始类型用第一个限定替换类型变量,如果类型变量没有限定,则使用Object替换,至于其他限定,编译器会在必要的时候插入强制类型转换,所以,为避免过多的强制类型转换,标签接口应该放在限定列表的最后。上文中的 Holder 类,用 jad 反编译后的代码如下,可以看出类型变量被Object替换。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   Holder.java

package com.hiwzc.java;

public class Holder
{

    public Holder()
    {
    }

    public void set(Object value)
    {
        this.value = value;
    }

    public Object get()
    {
        return value;
    }

    private volatile Object value;
}

下面再用另一段代码测试一下泛型限定的擦除。

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;

public class TypeErasure<T extends Closeable & Flushable> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public void close() throws IOException {
        value.close();
    }

    public void flush() throws IOException {
        value.flush();
    }
}

这里用jad工具反编译一下上面的代码编译后的class,结果如下,可以看到,类型参数T被替换为Closeable,而flush方法中插入了必要的强制类型转换。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   TypeErasure.java

package com.hiwzc.java;

import java.io.*;

public class TypeErasure
{

    public TypeErasure()
    {
    }

    public void set(Closeable value)
    {
        this.value = value;
    }

    public void close()
        throws IOException
    {
        value.close();
    }

    public void flush()
        throws IOException
    {
        ((Flushable)value).flush();
    }

    private Closeable value;
}

桥方法

类型擦除会带来一个多态保持的问题,考虑如下代码,该代码继承自上文中的 Holder 类,并覆盖了 Holder 类的两个方法。

public class DateHolder extends Holder<Date> {
    @Override
    public void set(Date date) {
        System.out.println("In DateHolder");
        super.set(date);
    }

    @Override
    public Date get() {
        System.out.println("In DateHolder");
        return super.get();
    }
}

由于类型擦除,Holder 中已经存在一个 public void set(Object value) 和 public Object get() 方法,这里又有一个 public void set(Date date) 和 public Date get() 方法,代码的本意是覆盖父类的方法,而这里却是重载的语法。为了解决这个问题,编译器自动生成了两个方法覆盖了父类的方法,其实现是调用子类的方法,编译器通过生成的这个方法保持了多态的特性,这个方法称为桥方法(bridge method)。上面的代码反编译后的代码如下,这里要注意一下两个get方法,这两个方法仅有返回值不同,Java语言中这是不合法的,但是Java虚拟机能够正确处理这个情况,Java虚拟机确定一个方法时会考虑方法的返回值。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name:   DateHolder.java

package com.hiwzc.java;

import java.io.PrintStream;
import java.util.Date;

// Referenced classes of package com.hiwzc.javase.generic:
//            Holder

public class DateHolder extends Holder
{

    public DateHolder()
    {
    }

    public void set(Date date)
    {
        System.out.println("In DateHolder");
        super.set(date);
    }

    public Date get()
    {
        System.out.println("In DateHolder");
        return (Date)super.get();
    }

    public volatile Object get()
    {
        return get();
    }

    public volatile void set(Object obj)
    {
        set((Date)obj);
    }
}

擦除冲突

如果类型擦除后,方法和已有的方法签名相同,则是一个编译错误。比如在 Holder 中加一个 equals 方法,由于方法 Holder 中已经有一个从 Objects 中继承过来的 equals 方法,而类型擦除后,两个equals方法相同,编译器会报错 Name clash: The method equals(T) of type Holder<T> has the same erasure as equals(Object) of type Object but does not override it。

public class Holder<T> {
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return this.value;
    }

    // public boolean equals(T value) { // ERROR
    //     ....
    //     return false;
    // }
}

解决的方法是 equals 改名,或者直接明确的设置为覆盖,如下:

public class Holder<T> {
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return this.value;
    }

    @Override
    public boolean equals(Object value) {
        // ....
        return false;
    }
}

为支持泛型擦除,泛型规范还要求一个类或类型参数不能是同一个接口的不同参数化接口的子类(To support translation by erasure, we impose the restriction that a class or type variable may not at the same time be a subtype of two interface types which are different parameterizations of the same interface),意思就是,一个类不能同时实现两个这样的接口,这两个接口是同一个接口的不同参数化。

举个例子,下面的 ErasureClashes 不能通过编译,编译器报错 The interface A cannot be implemented more than once with different arguments: A<String> and A<Date>,因为 B 和 C 中都生成了相同的桥方法,编译器无法区分到底用哪个桥方法。

interface A<T> {

}

interface B extends A<Date> {

}

interface C extends A<String> {

}

// public class ErasureClashes implements B, C { // ERROR
// }

复杂泛型示例

请看下面的示例:

class Dog implements Comparable<Dog> {
    @Override
    public int compareTo(Dog o) {
        return 0;
    }
}

class TeddyDog extends Dog {
}

class Cat {
}

class TomCat extends Cat implements Comparable<Dog> {
    @Override
    public int compareTo(Dog o) {
        return 0;
    }
}

class KittyCat extends Cat implements Comparable<TeddyDog> {
    @Override
    public int compareTo(TeddyDog o) {
        return 0;
    }
}

class Demo<T extends Comparable<T>> {
}

class Demo2<T extends Comparable<? super T>> {
}

public class ComplexGeneric {
    private static <T extends Comparable<T>> int search1(List<T> list, T key) {
        return 0;
    }

    private static <T extends Comparable<? super T>> int search2(List<T> list, T key) {
        return 0;
    }

    private static <T> int search3(List<? extends Comparable<T>> list, T key) {
        return 0;
    }

    private static <T> int search4(List<? extends Comparable<? super T>> list, T key) {
        return 0;
    }

    public static void main(String[] args) {
        // Demo<TeddyDog> p = null;
        Demo2<TeddyDog> p1 = null;

        Dog dog = new Dog();
        TeddyDog teddyDog = new TeddyDog();

        List<Dog> dogs = new ArrayList<>();
        List<TeddyDog> teddyDogs = new ArrayList<>();
        List<TomCat> tomCats = new ArrayList<>();
        List<KittyCat> kittyCats = new ArrayList<>();

        search1(dogs, dog);
        //search1(teddyDogs, teddyDog);

        search2(dogs, dog);
        search2(teddyDogs, teddyDog);

        search3(dogs, dog);
        search3(teddyDogs, dog);
        search3(tomCats, teddyDog);
        search3(tomCats, dog);

        search4(dogs, dog);
        search4(teddyDogs, dog);
        search4(tomCats, teddyDog);
        search4(tomCats, dog);

        // search3(kittyCats, dog);
        search3(kittyCats, teddyDog);
        // search4(kittyCats, dog);
        search4(kittyCats, teddyDog);
    }
}

首先,被注释掉的第52行报错,原因是Demo类要求其参数化类型必须实现了Comparable,并且只能和这个参数化类型比较,而TeddyDog并没有实现可以和TeddyDog比较的方法。第53行不会报错,原因是虽然Demo2类同样要求其参数化类型必须实现了Comparable,但只要能跟其父类比较就可以,TeddyDog虽然没有实现和TeddyDog比较的方法,但是从父类中继承了同Dog类比较的方法,所以第46行不会报错。

同样的道理,被注释掉的第64行报错,但67行不报错,因为search1要求list中的元素和key必须都是同一个类型,这个类型必须实现Comparable,且能和自身的类型比较。search2虽然要求list中的元素和key必须是同一个类型,但只要list中的元素实现了Comparable<? extends T>,也就是说list中的元素可以和T及其父类比较就可以。

注意,search1和search2是类型限定,而search3和search4是通配符类型。search3和search4都不要求list中的元素和key是同一个类型,search3要求list中的元素能和T比较,search4要求list中的元素能和T及其父类相比较。当list为kittyCats,T为Dog时,KittyCat类只能和TeddyCat比较,而不能和Dog比较,所以79和81行报错。这里需要特别注意的是,当list为tomCats,T为Teddy时,71和76行都不会报错,尤其是第71行,尽管Tomcat并不是Comparable<Teddy>的子类,也不会报错

本文来自 [时光记 - 王智超的个人空间](www.hiwzc.com),转载请注明出处。