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

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

2013-10-27 17:08 302 查看

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