Java泛型

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语言程序设计 进阶篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值