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)
通配泛型有三种形式:
- ?
- ? 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语言程序设计 进阶篇
- 【java 2】java泛型
- Java 泛型 Java generic
- Java Tutorials_Generics(java泛型)
- Java基础 Java 泛型
- java 泛型
- java泛型
- Java泛型
- Java泛型
- java泛型
- java泛型
- java泛型
- Java 泛型
- Java泛型
- Java 泛型
- JAVA 泛型
- java 泛型
- java泛型
- Java泛型
- 使用OEPE快速开发WebLogic Web Service服务端
- 解决AndroidStudio创建模拟器时Unknown Error问题
- iOS开发----键盘弹出和隐藏时移动视图,防止被键盘挡住
- 从C#到TypeScrip
- 关于消息队列的使用
- Java泛型
- express重定向
- git的入门练习(本地仓库)
- OpenCV2在图像中写汉字
- MySchool 03 编程上机练习 上机和打印直角三角形
- 学习笔记——Spring的Bean的装配
- 传球游戏(dp)
- 在JavaScript中利用ActiveXObject控制Excel的方法
- 北大 C++ 1.1 函数指针