java学习脚印: 泛型(Generic)认识之一
写在前面
介绍泛型的资料很多,尤其是http://www.angelikalanger.com上面近乎200多页的泛型FAQ非常突出,这些资料容易让初学者眼花缭乱。我希望能有一个清晰的思路,从这些权威资料中,整理一份让初学者不那么头疼的入门笔记。
1. 为什么要使用泛型?
使用泛型程序设计,可以使程序具有更好的可读性和安全性。
1)泛型在编译时提供更加严格的类型检查。
java编译器对泛型代码使用更严格的类型检查,指出代码中违背类型安全(type safety,见下文基本概念)的错误。
修复编译时的错误明显要比修复运行时错误简单。
2)避免了类型转换。
java SE 5.0之前的泛型设计使用继承来实现的,那么下面的代码需要强制类型转换:
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
在之后引入了类型参数(type parameter,见下文基本概念)之后,泛型编写为:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);
就不需要类型转换了。
3)泛型允许程序员使用泛型算法。
例如对一组数组中的元素进行排序,而不管其类型如何,只要提供了一个元素间比较大小的compareTo方法就可以实现数组的排序。这种情形使用泛型就可以将算法作用与多种不同类型之上(例如整数,字符串类型)了,并且代码更加类型安全和可读。
2.泛型的初步认识
2.1 基本概念
1) 参数化类型 也即parameterized type ,例如ArrayList<E>,这里ArrayList就是一个 参数化类型,其中的E是类型变量,代表不同的类型。
2)类型变量 也即type parameter或者type variables,例如ArrayList<E>中的E。
一般在泛型程序中使用的类型变量有:
E - Element java集合框架中大量使用的类型变量
K - Key 代表键
N - Number 代表数值
T - Type 代表类型
V - Value 代表值
S,U,V等等表示后续类型变量
这些大写字母可以在编写泛型时做参考。
3) 类型实参 也即type argument.例如ArrayList<String>中的String就是一个类型实参。
4) 原型 也即Raw Types,原型表示的是没有任何类型实参的泛型,例如ArrayList list = new ArrayList<String>(); 这里list就是一个原型,它引用了ArrayList<String>类型变量。
简单理解就是,通过给参数化类型传递一个类型实参,用类型实参取代类型变量,来表达一个具体的类型,如果没有类型实参就是原型。
5)类型安全 也即type safety 。java中如果编译时没有引起任何错误和警告,并且运行时没有引起意外的ClassCastException异常则认为是类型安全的。
2.2 定义简单泛型类
泛型类的定义基本形式如:
class name<T1, T2, ..., Tn> { /* ... */ },其中T是类型变量。
下面定义一个简单的Pair类来表达成对的数据,就像c++中的pair模板类一样。
代码清单: 2-1 Pair.java
package com.learningjava ; /** * represents Paired data */ public class Pair<T> { //unit test public static void main(String[] args) { Pair<String> stu = new Pair<>("jack","20130304"); System.out.println(stu); stu.setFirst("Smith"); System.out.println(stu); } //should make fields null before initialization public Pair() { this.first = null; this.second = null; } public Pair(T first, T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } @Override public String toString() { // TODO Auto-generated method stub return this.getClass().getName()+"[ "+first.toString() +","+second.toString()+" ]"; } private T first; private T second; }
进行单元测试,输出:
com.learningjava.Pair[ jack,20130304 ]
com.learningjava.Pair[ Smith,20130304 ]
这里之提供了一个类型变量,也可以写出Pair<K,V> 版本。
注意,不能定义Pair<T,T>版本,编译器提示重复的类型变量错误,也就是说声明类型变量时不可以重复。
2.3 定义简单泛型方法
除了类可以使用泛型外,方法也可以使用泛型,泛型方法定义格式如下:
Modifiers <T> returnType methodName(T parameters t),
方法的参数和返回值类型都可以是声明的类型变量的类型。
下面定义一个util类,它有取两者最大值和最小值的泛型方法。
程序清单: 2-2 Util.java
package com.learningjava; /** * Generic method */ public class Util { //unit test public static void main(String[] args) { System.out.println(Util.Max("hello!", "hey!")); System.out.println(Util.Min(Integer.valueOf(11), Integer.valueOf(100))); } //get max value public static <T extends Comparable<T>> T Max(T first,T second) { if(first.compareTo(second) < 0) { return second; } else { return first; } } //get min value public static <T extends Comparable<T>> T Min(T first,T second) { if( first.equals(Max(first,second)) ) { return second; } else { return first; } } }
单元测试运行结果:
hey!
11
这里使用的类型变量,T extends Comparable<T>,2.4部分会做介绍。
2.4 类型变量限定的初步认识
通过声明类型变量,那么就实现了基本的泛型,但是这种泛型对类型变量没有约束,容易引起误解和错误。例如在排序或者上例中取最大值方法中,要求传递给参数化类型的类型实参所代表的类,必须提供一个比较元素大小的compareTo方法,如果一个类没有这个方法就不允许进行元素大小比较。
这可以通过对类型变量设置限定(bound)来实现:
例如T extends Comparable<T>,表明,类型变量T必须实现了Comparable接口,该接口就有一个compareTo方法,指明元素比较大小的方法。
类型限定的格式一般为: <T extends BoudingType>,注意:
1)可以为一个类型变量设定多个限定例如: <T extends Comparable & Serializable>。
2)如果用一个类作为限定的话,这个类必须放在限定列表的最左边,否则编译器提示错误。
3)为了提高效率,应该将没有具体方法的标签接口,例如Cloneable接口,放在限定列表的末尾。
关于类型变量的限定,还有一些复杂些内容,留在《泛型(Generics)认识之三》中讨论。
2.5 编写一个实用的泛型类
这里编写,一个代表数值区间的泛型类,来作为本节练习。数值区间类NumberInterval,可以支持从Number类继承并且实现了Comparable接口的任意类的区间表示。
程序清单: 2-3 NumberInterval.java
package com.learningjava; /** * <P> The {@code NumberInterval} class represents a data interval.</P> * <p> any type argument pass to it must * be the type extends from {@code Number} and implements {@code Comparable} interface ,such as Integer.</p> * <p> The interval is limited by lower and upper value .</p> * <p>lower value must smaller than upper value,which we mean: lower.compareTo(upper) <0 </p> * <p> for example: * <p><code> {@code NumberInterval}<{@code Integer}> intRange = new {@code NumberInterval}<>(12,13) </code> is valid.</p> * <p> while <code> {@code NumberInterval}<{@code Integer}> intRange = new {@code NumberInterval}<>(14,13) </code> is illegal.</p> * * @param <T> the type varialble,such as Integer * * @throws IllegalArgumentException * if the given argument is invalid * * @throws NullPointerException * if the given argument is null * * @author wangdq * @version 1.3 2013-11-3 * */ public class NumberInterval<T extends Number & Comparable<T> > { public NumberInterval() { this.lower = null; this.upper = null; } public NumberInterval(T lower, T upper) { this.lower = lower; this.setUpper(upper); } public T getLower() { return lower; } /** * Set lower value of the interval * * @param lower the lower value of the interval * * @throws NullPointerException * if lower value is null * * @throws IllegalArgumentException * if lower.compareTo(upper) >= 0 */ public void setLower(T lower) { if(lower == null) { throw new NullPointerException("lower value must not be null"); } if(upper == null || lower.compareTo(upper) < 0) { this.lower = lower; } else { throw new IllegalArgumentException("lower value must be smaller than upper value:" +System.getProperty("line.separator")+"but found lower:"+lower+" >= upper:"+upper); } } public T getUpper() { return upper; } /** * Set upper value of the interval * * @param upper the upper value of the interval * * @throws NullPointerException * if the given argument value is null * * @throws IllegalArgumentException * if upper.compareTo(lower) <= 0 */ public void setUpper(T upper) { if(upper == null) { throw new NullPointerException("upper value must not be null"); } if(lower == null || upper.compareTo(lower) > 0) { this.upper = upper; } else { throw new IllegalArgumentException("upper value must be bigger than lower value:" +System.getProperty("line.separator")+"but found upper:"+upper+" <= lower:"+lower); } } /** * <p>Test if the given value is included in the interval.</p> * * @param value the value to be tested * * @throws NullPointerException if the given value is null * * @return <p>true if the given value is included in the interval ,else return false.</p> * <p>Note,if the lower or upper value is currently null ,it return false.</p> */ public boolean isBetweenRange(T value) { if(value == null ) { throw new NullPointerException("the value to be tested must not be null"); } if(lower == null || upper == null) { return false; } else { return (value.compareTo(lower) >= 0 && value.compareTo(upper) <=0); } } @Override public String toString() { return this.getClass().getName()+" ["+lower +","+upper+"]"; } private T lower; private T upper; }
程序清单: 2-4 NumberIntervalTestDrive.java
package com.learningjava; import java.math.BigInteger; /** * test NumberInterval * @author wangdq * */ public class NumberIntervalTestDrive { public static void main(String[] args) { //default constructor NumberInterval<Integer> intRange = new NumberInterval<>(); System.out.println(intRange); //set lower and upper value intRange.setLower(5); intRange.setUpper(14); System.out.println(intRange); //isBetweenRange test System.out.println("dose 13 between the int Range:"+ intRange.isBetweenRange(Integer.valueOf(13))); //illegalArgumentException test try { intRange.setLower(16); }catch(IllegalArgumentException e) { System.err.println(e); } //NullPointerException test try { intRange.isBetweenRange(null); }catch(NullPointerException e) { System.err.println(e); } //constructor with arguments NumberInterval<Double> doubleRange = new NumberInterval<>(3.14159,3.14169); System.out.println(doubleRange); NumberInterval<BigInteger> bigIntRange = new NumberInterval<> ( BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.valueOf(1)), BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.valueOf(2)) ); System.out.println(bigIntRange); } }
测试运行结果:
com.learningjava.NumberInterval [null,null]
com.learningjava.NumberInterval [5,14]
dose 13 between the int Range:true
java.lang.IllegalArgumentException: lower value must be smaller than upper value:
but found lower:16 >= upper:14
java.lang.NullPointerException: the value to be tested must not be null
com.learningjava.NumberInterval [3.14159,3.14169]
com.learningjava.NumberInterval [2147483648,2147483649]
3.参考资料
[1]: 《java核心技术:卷一》 第八版 机械工业出版社
[2]:http://www.angelikalanger.com/GenericsFAQ/FAQSections/Fundamentals.html#FAQ001
[3]:http://docs.oracle.com/javase/tutorial/java/generics/index.html