[Java] 泛型

泛型即指将类型参数化,使用泛型的目的是让编译器在编译时就检测到错误,而不是要等到运行时才发现,因此可以提高软件的可靠性和可读性。

从JDK1.5开始可以定义泛型类,接口和方法。
JDK 1.5 之前

package java.lang;
public interface Comparable {
    public int compareTo(Object o)
}
JDK 1.5 之后:
package java.lang;
public interface Comparable<T> {
    public int compareTo(T o)
}

如果没有泛型,必须用强制类型转换,否则不需要:

1 ArrayList<String> list = new ArrayList<>();
2 list.add("Red");
3 list.add("White");
String s = (String)(list.get(0)); // 可以简写成 String s = list.get(0);

泛型类型必须是引用类型,不能用原生类型,下例语句是错的:

ArrayList<int> intList = new ArrayList<>();  // Error! int 必须改成Integer

定义泛型类和接口

ArrayList类就是一个泛型类,Comparable接口就是一个泛型接口。
泛型栈:

public class GenericStack<E> {
    private java.util.ArrayList<E> list = new java.util.ArrayList<E>();

    public int getSize() {
        return list.size();
    }

    public E peek() {
        return list.get(getSize() - 1);
    }

    public void push(E o) {
        list.add(o);
    }

    public E pop() {
        E o = list.get(getSize() - 1);
        list.remove(getSize() - 1);
        return o;
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public String toString() {
        return "stack: " + list.toString();
    }
}

用法:

GenericStack<String> stack1 = new GenericStack<>();
stack1.push("London");
stack1.push("Paris");
stack1.push("Berlin");

GenericStack<Integer> stack2 = new GenericStack<>();
stack2.push(1); // autoboxing 1 to new Integer(1)
stack2.push(2);
stack2.push(3);

但是泛型类的构造函数不带类型:

public GenericStack<E>() // 错误! 构造函数不能带类型

正确的写法如下:

public GenericStack()

如果泛型类的参数多于1个,要写成下面这样,用逗号分隔:
<E1, E2, E3>

可以将一个接口或类定义为泛型类或接口的子类型,例如java.lang.String类的定义:

public class String implements Comparable <String>;

泛型方法

静态方法可以定义泛型类型, 不明白为什么一定要是静态类型??

public class GenericMethodDemo {
    public static void main(String[] args ) {
        Integer[] integers = {1, 2, 3, 4, 5};
        String[] strings = {"London", "Paris", "New York", "Austin"};

            GenericMethodDemo.<Integer>print(integers);
            GenericMethodDemo.<String>print(strings);
            // 或简写为:
            print(integers);
            print(strings);
        }

    public static <E> void print(E[] list) {
        for (int i = 0; i < list.length; i++)
            System.out.print(list[i] + " ");
            System.out.println();
    }
}

有界的泛型类型(bounded generic type):泛型类型为另一种类型的子类型。下面的例子:
绑定的泛型类型<E extends GeometricObject> 指定EGeometricObject的泛型子类型,调用equalArea时,必须传递两个GeometricObject对象的实例。

public class BoundedTypeDemo {
    public static void main(String[] args ) {
        Rectangle rectangle = new Rectangle(2, 2);
        Circle circle = new Circle(2);
        System.out.println("Same area? " + equalArea(rectangle, circle));
    }

    public static <E extends GeometricObject> boolean equalArea(E object1, E object2) {
        return object1.getArea() == object2.getArea();
    }
}

而无界的泛型类型(unbounded generic type)<E>等价于 <E extends Object>

为类定义泛型类型时, 将泛型类型放在类名后, 例如 GenericStack<E>, 而为方法定义泛型类型时,将泛型类型放在方法返回类型之前,例如<E> void max(E o1, E o2)

原始类型和向后兼容

泛型类如果在使用时不指定具体类型,就成为原始类型(raw type), 使用原始类型的原因是,需要实现向后兼容,因为Java的早期版本不支持泛型。

但是使用原始类型是不安全的。

GenericStack stack = new GenericStack(); // raw type

大致等价于:

GenericStack<Object> stack = new GenericStack<Object>();
public class Max {
    /** Return the maximum of two objects */
    public static Comparable max(Comparable o1, Comparable o2) {
        if (o1.compareTo(o2) > 0)
            return o1;
        else
            return o2;
    }
}

o1 和 o2 都被声明为原始类型,但是原始类型不安全,例如,写出这样的代码是有可能的:Max.max("Welcome", 23);

通配符泛型类型

可以使用无界的通配符,有界的通配符或下界的通配符来指定泛型类型的范围。

泛型的擦除及限制

有关泛型的信息由编译器使用,但在运行时不可用。这称为类型擦除。

泛型是使用称为类型擦除的方法实现的:编译器使用泛型类型信息来编译代码,但随后将其擦除。 因此,泛型信息在运行时不可用。 使用此方法,泛型代码能够与使用原始类型的遗产代码向后兼容。

泛型在编译时存在, 一旦编译器确认泛型类型的使用是安全的,它就会将泛型类型转换为原始类型。 例如,编译器检查以下的(a)中的代码是否正确地使用了泛型,然后将其翻译为等效的(b)中的代码以供运行时使用, (b)中的代码使用的是原始类型。…

// (a)
ArrayList<String> list = new ArrayList<>();
list.add("Oklahoma");
String state = list.get(0);
// (b)
ArrayList list = new ArrayList();
list.add("Oklahoma");
String state = (String)(list.get(0));

编译泛型类,接口和方法时,编译器将使用Object类型替换泛型类型。 例如,编译器会将(a)中的以下方法转换为(b):

// (a)
public static <E> void print(E[] list) {
    for (int i = 0; i < list.length; i++)
        System.out.print(list[i] + " ");
    System.out.println();
}
// (b)
public static void print(Object[] list) {
    for (int i = 0; i < list.length; i++)
        System.out.print(list[i] + " ");
    System.out.println();
}

如果泛型类型有界,则编译器将其替换为有界类型。 例如,编译器会将下面的(a)中的方法转换为(b)。

// (a)
public static <E extends GeometricObject> 
boolean equalArea(E object1, E object2) {
    return object1.getArea() == object2.getArea();
}
// (b)
public static boolean equalArea(GeometricObject object1, 
GeometricObject object2) {
    return object1.getArea() == object2.getArea();
}

值得注意的是,泛型类由其所有实例共享,而不管其实际具体类型如何。 假设list1list2创建如下:

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();

虽然ArrayList <String>ArrayList <Integer>在编译时是两种类型,在运行时,只有一个ArrayList类被加载到JVM中。list1list2都是ArrayList的实例,因此以下语句显示为true

System.out.println(list1 instanceof ArrayList);
System.out.println(list2 instanceof ArrayList);

但是,表达式list1 instanceof ArrayList <String>是错误的, 由于ArrayList <String>没有作为单独的类存储在JVM中,因此在运行时使用它是没有意义的。 由于泛型类型在运行时被擦除,因此对泛型类型的使用存在某些限制。 以下是一些限制:

限制 1: 不能使用 new E()
不能使用泛型类型参数创建实例。 例如,以下语句是错误的:

E object = new E(); // Error

原因是new E()在运行时执行,但泛型类型E在运行时不可用。

限制 2: 不能使用 new E[]
不能使用泛型类型参数创建数组。 例如,以下语句是错误的:

E[] elements = new E[capacity]; // Error

你可以通过创建Object类型的数组然后将其转换为E []来规避此限制,如下所示:

E[] elements = (E[])new Object[capacity];

但是,转换为(E [])会导致非检查的编译警告。 发生警告是因为编译器不确定在运行时能否成功转换。 例如,如果EStringnew Object []Integer对象的数组,(String [])(new Object [])将导致ClassCastException。 这种类型的编译警告是Java泛型的限制,是不可避免的。
也不允许使用泛型类创建泛型数组。 例如,以下代码是错误的:

ArrayList<String>[] list = new ArrayList<String>[10];

可以使用以下代码来规避此限制:

ArrayList<String>[] list = (ArrayList<String>[])new ArrayList[10];

但是,你仍会收到编译警告。

限制 3: 静态上下文中不允许使用类的泛型类型参数
因为泛型类的所有实例都具有相同的运行时类,因此泛型类的静态变量和方法将由其所有实例共享。(不是很明白,不能解释为运行时类型被擦除吗?因此不能使用),因此,在静态方法,字段或initializer中引用类的泛型类型参数是非法的。 例如,以下代码是非法的:

public class Test<E> {
    public static void m(E o1) { // 非法!
    }
    public static E o1; // 非法!
    static {
        E o2; // 非法!
    }
}

Restriction 4: 异常类不能是泛型的
泛型类不能继承java.lang.Throwable,因此以下类声明将是非法的:

public class MyException<T> extends Exception {
}

为什么? 如果允许,你将有一个MyException <T>catch语句块,如下所示:

try {
...
}
catch (MyException<T> ex) {
...
}

JVM必须检查从try语句块抛出的异常,以查看它是否与catch语句块中指定的类型匹配。 这是不可能的,因为类型信息在运行时不存在。

对象数组排序:

public class GenericSort {
    public static <E extends Comparable<E>> void sort(E[] list) {
        E currentMin;
        int iMinIndex;
        for (int i = 0; i < list.length; i++) {
            currentMin = list[i];
            iMinIndex = i;
            for (int j = i + 1; j < list.length; j++) {
                if (currentMin.compareTo(list[j]) > 0) {
                    currentMin = list[j];
                    iMinIndex = j;
                }
            }
            if (iMinIndex != i) {
                list[iMinIndex] = list[i];
                list[i] = currentMin;
            }
        } 
        System.out.println("\nObjects:");
        for (int i = 0; i < list.length; i++)
            System.out.print(list[i] + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] integers = {new Integer(4), new Integer(3), new Integer(2)};
        Double[] doubles = {new Double(1.3), new Double(3.4), new Double(-22.1)};
        Character[] chars = {new Character('J'), new Character('a'), new Character('r')};
        String[] strings = {new String("Susan"), new String("Kim"), new String("Tom")};
        sort(integers);
        sort(doubles);
        sort(chars);
        sort(strings);
    }
}

[1] Introduction to Java Programming Chapter 19. Generic Types

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值