您的位置:首页 > 编程语言 > Java开发

Java泛型详解

2017-02-25 22:03 260 查看
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.

通配泛型

假设我们要定义一个泛型方法,找出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)

通配泛型有三种形式:

?

? extends T

? 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语言程序设计 进阶篇
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: