现在的位置: 首页 > 综合 > 正文

java学习脚印: 泛型(Generic)认识之一

2014年02月05日 ⁄ 综合 ⁄ 共 8102字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.