Java泛型

来源:互联网 发布:加工中心打孔编程 编辑:程序博客网 时间:2024/06/06 12:44

Java泛型是JDK1.5加入的新特性。泛型是指参数化的能力。可以定义带泛型的类型的类或者方法,编译时期编译器会用具体的类型来代替它。Java泛型有泛型类、泛型接口和泛型方法。泛型的主要优点是能够在编译时期而不是在运行时期就检测出错误。

泛型的出现

在JDK1.5之前,java.lang.Comparable的定义如下所示:

public interface Comparable {    public int comparaTo(Object o);}

在JDK1.5之后,泛型的定义如下:

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

这里的<T>表示形式形式泛型类型,之后可以用一个实际的具体类型来替换它。替换泛型称为泛型实例化。按照惯例,像E或T这样的单个字母用于表示一个形式泛型类型。为了看到泛型的具体好处,我们来看具体的实例。
这里写图片描述
图1
这里写图片描述
图2
由于Date实现了Comparable接口,由Java的多态特性,我们可以用父类的指针指向子类,也就是我们可以new一个Date类型赋值给我们的Comparable接口类型。当我们调用Comparable接口的comparaTo()方法时。由于图1没有指定泛型,编译时期不会出现提示,但是在运行时期会报出:java.lang.String cannot be cast to java.util.Date的错误,提示信息提示String类型不能转换为Date进行比较。而使用了泛型了图2,在编译期间就提示错误,因为传递给compareTo方法的参数必须是Date类型。由于这个错误是在编译器而不是运行期被检测到,因而泛型使程序更加可靠。

泛型类、接口、方法的定义

现在我们来实现一个线性表list,命名为GenericArrayList,可以接收泛型数据。该类实现了add()添加元素的方法,size()获取元素个数的方法,和获取指定下标元素的get()方法。

public class GenericArrayList<E> {  Object[] objects=new Object[10];  int index=0;  public GenericArrayList(){      System.out.println("构造函数");  }  public void add(E o){   if(index==objects.length){      Object[] newObjects=new Object[objects.length*2];      System.arraycopy(objects, 0, newObjects, 0, objects.length);      objects=newObjects;    }     objects[index]=o;     index++;  }    public int size(){       return index;    }    public E get(int index) {        return (E) objects[index];    }}

下面代码片段将向list中添加三个城市名,然后再将城市名依次取出。

        GenericArrayList<String> ga1 = new GenericArrayList<String>();        ga1.add("北京");        ga1.add("贵阳");        ga1.add("重庆");        for(int i = 0; i < ga1.size(); i++) {            System.out.println(ga1.get(i));        }

同样的,可以向list中添加如数字10086,然后再将数字依次取出。

        GenericArrayList<Integer> ga2 = new GenericArrayList<Integer>();        ga2.add(1);        ga2.add(0);        ga2.add(0);        ga2.add(8);        ga2.add(6);        for(int i = 0; i < ga2.size(); i++) {            System.out.println(ga2.get(i));        }

注意:

1.上面创建的两个GenericArrayList对象ga1和ga2,他们创建的语法分别是:new GenericArrayList<String>()和new GenericArrayList<Integer>(),但是千万不要认为我的GenericArrayList类中分别对应两个这样的构造方法

  public GenericArrayList<String>(){      System.out.println("构造函数");  }
  public GenericArrayList<Integer>(){      System.out.println("构造函数");  }

而实际上,我的构造方法是在第7行定义的。

2.有时候泛型的参数有多个,那么我们可以把所有的参数一起放在间括号里面,如<E1,E2,E3>。

3.可以定义一个类或一个接口作为作为泛型或者接口的子类型。例如,在Java API中,java.lang.String类被定义为实现Comparable接口,如下所示:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence

定义泛型方法:

public class Test2 {    public static void main(String[] args) {        Integer[] arr1 = {1, 0, 0, 8, 6, 1, 1};        Test2.<Integer>pint(arr1);        String[] names = {"马云", "马化腾", "李彦宏"};        Test2.<String>pint(names);    }    public static <E> void pint(E[] arr) {        for(int i = 0; i < arr.length; i++) {            System.out.print(arr[i] + " ");        }        System.out.println();    }}

上诉代码定义了打印数组的print方法,arr1是一个整型的数组,而arr2是一个字符串类型的数组,当他们调用print时,分别将数组的内容输出。

为了调用泛型方法,需要将实际类型放在间括号作为方法名的前缀。如,
Test2.pint(arr1);
Test2.pint(names);
当然,也可以将间括号省略掉。

通配泛型

假设我们要定义一个泛型方法,找出list中的最大值。那么代码可以参考如下:

public class Test3 {    public static void main(String[] args) {        GenericArrayList<Integer> ga = new GenericArrayList<Integer>();        ga.add(1);        ga.add(2);        ga.add(3);        Test3.max(ga);    }    public static double max(GenericArrayList<Number> list) {        double maxValue = list.get(0).doubleValue();        for(int i = 0; i < list.size(); i++) {            double value = list.get(i).doubleValue();            if(value > maxValue) {                maxValue = value;            }        }        return maxValue;    }}

首先new出一个list对象,并向list里面添加元素1,2,3,然后调用max方法。max方法的逻辑是依次取出list里面的元素,与我们的标记maxValue对比,如果大于maxValue当前元素值,就把当前元素值赋值给maxValue。
但是,上面的代码编译会错误,因为ga不是GenericArrayList<Number&glt; 的对象,所以不能调用max()方法。
这里写图片描述
尽管Integer是Number的子类(除Integer之外,还有Short,Byte,Long,Float,Double等也是Number的子类),但是GenericArrayList<Integer>不是GenericArrayList<Number>的子类。
解决的方案是使用通配泛型。只需要把max的方法头改写如下即可:

public static <E> double max(GenericArrayList<? extends Number> list)

通配泛型有三种形式:

  1. ?
  2. ? extends T
  3. ? super T

第一种称为非受限制通配,和? extends Oject是一样的。第二种称为受限制通配,表示T或T的一个未知子类型。第三种称为下限通配,表示T或T的的一个父类。
第二种通配泛型上面的案例已经使用过,下面我们来看第一种类型。案例如下:

public class Test4 {    public static void main(String[] args) {        GenericArrayList<Integer> ga = new GenericArrayList<Integer>();        ga.add(1);        ga.add(2);        ga.add(3);        Test4.print(ga);        GenericArrayList<Person> ga1 = new GenericArrayList<Person>();        ga1.add(new Person("马云"));        ga1.add(new Person("李彦宏"));        ga1.add(new Person("马化腾"));        Test4.print(ga1);    }    public static void print(GenericArrayList<?> list) {        for(int i = 0; i < list.size(); i++) {            System.out.print(list.get(i) + " ");        }        System.out.println();    }}

Person类定义如下:

public class Person {    private String name;    public Person(String name) {        this.name = name;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public String toString() {        return "Person [name=" + name + "]";    }}

为了输出我们的Person对象,需要对Person的toString()方法重写。main方法中new了两个GenericArrayList对象,一个的实际参数是Integer型list,另一个是Person对象的list。案例的输出如下:

构造函数
1 2 3
构造函数
Person [name=马云] Person [name=李彦宏] Person [name=马化腾]

这里如果把?换成Object则报错。众所周知:无论是Integer还是Person都继承自Object,因为Obejct是所有类的父类。但是,GenericArrayList<Person>不是GenericArrayList<Object>的子类。

现在来看看第三种通配泛型的用法。

public class Test5 {    public static void main(String[] args) {        GenericArrayList<Object> ga = new GenericArrayList<Object>();        ga.add(1);        ga.add(2);        ga.add(3);        GenericArrayList<Person> ga1 = new GenericArrayList<Person>();        ga1.add(new Person("马云"));        ga1.add(new Person("李彦宏"));        ga1.add(new Person("马化腾"));        Test5.add(ga1, ga);        //调用Test4的泛型输出方法        Test4.print(ga);    }    //该方法的功能是将list1添加到list2    public static <T> void add(GenericArrayList<T> list1, GenericArrayList<? super T> list2) {        for(int i = 0; i < list1.size(); i++) {//          list1.add(list2.get(i));            list2.add(list1.get(i));        }    }}

上诉代码,我们想将一个Person的List追加到Integer的List中去。先创建ga对象,该对象的实际类型是Object,赋值1,2,3的时候自动装箱编程Integer,属于Object的子类。ga1的实际类型是Person,属于Object,符合Person super Object。控制台输出如下:

构造函数
构造函数
1 2 3 Person [name=马云] Person [name=李彦宏] Person [name=马化腾]

控制台的第一行和第二行“构造函数”是在我们new GenericArrayList对象的时候打印的。第三行,成功的将合并后的list打印出来,前三个元素是整型元素,后三个为Person对象的属性值。

类型擦除

泛型是使用一种称为类型擦除的方法来实现的,编译器使用泛型类型信息来编译代码,然后会查擦除它。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

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

尽管编译时期,ArrayList<String>和ArrayList<Integer> 是两个不同的类型,但是编译成字节码之后,只有一中类型ArrayList。因此以下两行输入都为true;

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

参考资料:
Java深度历险(五)——Java泛型
Java语言程序设计 进阶篇

5 1