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

java学习之--java泛型

2014-04-13 16:21 363 查看
开始之前先说几句废话,公司有个可爱的人事要对刚入职的同事提供java的系统培训,邀我做一个关于泛型的讲解,其实我对泛型了解的也不是很透彻,加上有时候比较忙,就一直在拖着,怎奈我那可爱的同事,又找资料,又找我的。再这样拖着确也于心不忍,于是抽个周末查了些资料,参考网上的资料,猝成此文,不得不说自己真的好久没有写过文章了,感谢我那可爱的同事!下面开始。

Java泛型(generics)是JDK
5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。在没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。如

package com.demo.generice;

public class GenDemo2 {
public static void main(String[] args) {
// 定义类Gen2的一个Integer版本
Gen2 intOb = new Gen2(new Integer(88));
intOb.showTyep();
int i = (Integer) intOb.getOb();
System.out.println("value= " + i);

System.out.println("----------------------------------");

// 定义类Gen2的一个String版本
Gen2 strOb = new Gen2("Hello Gen!");
strOb.showTyep();
String s = (String) strOb.getOb();
System.out.println("value= " + s);
}
}

class Gen2 {
private Object ob; // 定义一个通用类型成员

public Gen2(Object ob) {
this.ob = ob;
}

public Object getOb() {
return ob;
}

public void setOb(Object ob) {
this.ob = ob;
}

public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
这种情况下对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。有了泛型上面的代码可以这样写:

package com.demo.generice;

public class GenDemo3 {
public static void main(String[] args) {
// 定义泛型类Gen的一个Integer版本
GenericeDemo<Integer> intOb = new GenericeDemo(88);
intOb.showTyep();
int i = intOb.getOb();
System.out.println("value= " + i);

System.out.println("----------------------------------");

// 定义泛型类Gen的一个String版本
GenericeDemo<String> strOb = new GenericeDemo("Hello Gen!");
strOb.showTyep();
String ss = strOb.getOb();
System.out.println("value= " + ss);

GenericeDemo<AA> sss = new GenericeDemo<AA>(new AA());
sss.showTyep();
System.out.println("value= " + sss.getOb());

}
}

class AA {

@Override
public String toString() {

return "I am "+this.getClass().getSimpleName();
}

}
class GenericeDemo<T>{
private T ob; // 定义泛型成员变量

public GenericeDemo(T ob) {
this.ob = ob;
}

public  T getOb() {
return ob;
}

public void setOb(T ob) {
this.ob = ob;
}

public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
上面的小例子说明了泛型带来的一个好处,代码简介,易读。另外在使用泛型的例子中如果你这么写:

// 定义泛型类Gen的一个String版本
GenericeDemo<String> strOb = new GenericeDemo("Hello Gen!");
strOb.setOb(99);//这句话会报错
编译会报错,可见泛型可以限定输入的类型,让编译器挡住原始程序的非法输入。

有了上面的介绍,也许你真的想要走进泛型的世界了,想要正确理解泛型首要前提是理解类型擦除(typeerasure)。Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:

package com.demo.generice;

public class MyString implements Comparable<String> {
public int compareTo(String str) {
return 0;
}
}


当类型信息被擦除之后,上述类的声明变成了class MyString implements Comparable。但是这样的话,类MyString就会有编译错误,因为没有实现接口Comparable声明的int compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。也就是所谓的桥接方法。由于这个类型擦除的存在,java的泛型有一些独特的地方。

java类型擦除带来的约束与缺陷(http://blog.csdn.net/lonelyroamer/article/details/7868820,这人的写的很详细)。

a,不能用基本类型实例化类型参数

不能用类型参数代替基本类型。因此,没有Pair<double>,只有Pair<Double>。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double值。

b,运行时类型查询只适用于原始类型

虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。

如:arrayList instanceof ArrayList<String>于arrayList instanceof ArrayList<Integer>无异。

同样的道理,getClass
方法总是返回原始类型。例如,

Pair<String> stringPair = ....;

Pari<Employee>
employeePair=...;

stringPair.getClass()==employeePair.getClass();

其比较的结果是true。

c,不能抛出也不能捕获泛型类实例

不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法,但是,在异常声明中可以使用类型变量,是合法的.

d,参数化类型的数组不合法

e,不能实例化类型变量

总之,需要记住有关Java 泛型转换的事实:

• 虚拟机中没有泛型,只有普通的类和方法。

• 所有的类型参数都用它们的限定类型替换。

• 桥方法被合成来保持多态。

• 为保持类型安全性,必要时插入强制类型转换。

说了这么多,泛型都有哪些应用呢?(http://blog.csdn.net/lonelyroamer/article/details/7864531,还是那个人写的,还不错)

1、泛型类的定义[b]和使用[/b]

一个泛型类(generic class)就是具有一个或多个类型变量的类。定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数:

[java] view
plaincopy

class Pair<T> {

private T value;

public Pair(T value) {

this.value=value;

}

public T getValue() {

return value;

}

public void setValue(T value) {

this.value = value;

}

}

现在我们就可以使用这个泛型类了:

[java] view
plaincopy

public static void main(String[] args) throws ClassNotFoundException {

Pair<String> pair=new Pair<String>("Hello");

String str=pair.getValue();

System.out.println(str);

pair.setValue("World");

str=pair.getValue();

System.out.println(str);

}

Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义Pair类,其中第一个域和第二个域使用不同的类型:

public class Pair<T,U>{......}

注意:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用临近的字母U和S)表示“任意类型”。

2、泛型接口的定义和使用 定义泛型接口和泛型类类似。

3[b]、[/b]泛型方法实际上,还可以定义一个带有类型参数的简单方法。如下

class ArrayAlg{
public static <T> T getMiddle(T[] a){
return a[a.length/2];
}
}


这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(这里是public static)的后面,返回类型的前面。泛型方法可以定义在普通类中,也可以定义在泛型类中。当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

String[] names = new String[]{"John","Q","public","private"};
//		String middle = ArrayAlg.<String>getMiddle(names);
String middle = ArrayAlg.getMiddle(names);//(这样也是可以的)
在这种情况(实际也是大多数情况)下,方法调用中可以省略 <String> 类型参数。编译器有足够的信息能够推断出所调用的方法。它用names 的类型(即String[ ] )与泛型类型T[ ] 进行匹配并推断出T 一定是String 。

泛型变量的类型限定

知道了泛型类,泛型方法我们很容易写出这样的代码,

[java] view
plaincopy

public static <T> T get(T t1,T t2) {

if(t1.compareTo(t2)>=0);//编译错误

return t1;

}

很遗憾上面的代码编译有错误,因为,在编译之前,也就是我们还在定义这个泛型方法的时候,我们并不知道这个泛型类型T,到底是什么类型,所以,只能默认T为原始类型Object。所以它只能调用来自于Object的那几个方法,而不能调用compareTo方法。可我们的本意就是要比较t1和t2,怎么办呢?这个时候,就要使用类型限定,对类型变量T设置限定(bound)来做到这一点。我们知道,所有实现Comparable接口的方法,都会有compareTo方法。所以,可以对<T>做如下限定:

[java] view
plaincopy

public static <T extends Comparable> T get(T t1,T t2) { //添加类型限定

if(t1.compareTo(t2)>=0);

return t1;

}

类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:

1、不管该限定是类还是接口,统一都使用关键字 extends

2、可以使用&符号给出多个限定,比如

[java] view
plaincopy

public static <T extends Comparable&Serializable> T get(T t1,T t2)

3、如果限定既有接口也有类,那么类必须只有一个,并且放在首位置

[java] view
plaincopy

public static <T extends Object&Comparable&Serializable> T get(T t1,T t2)

通配符类型(http://blog.csdn.net/lonelyroamer/article/details/7927212,还是那个家伙的文章,呵呵)

通过上面的例子,我们已经知道有些情况下我们需要对类型进行限定。java为我们提供了通配符类型。

通配符有三种:

1、无限定通配符 形式<?>

2、上边界限定通配符 形式< ? extends Number> //用Number举例

3、下边界限定通配符 形式< ? super Number> //用Number举例。

实例

在实际的应用中泛型有什么用呢?仅仅用一个例子说明,

现在有一个业务需求,收钱时需要是我们本公司的员工(继承User),同时亦需要具备收银员的功能(实现Cashier),此时我们当然可以想到接受1个User然后判断User是否实现了Cashier接口,但在泛型的世界中,我们可以怎么做?请看以下例子

1. public static <T extends User & Cashier> void CollectMoney(T t){
2.
3. }

是不是很简单。
好了以上就是我对泛型的一些简单的理解,如有不对的地方,欢迎大家指出。不得不说这篇文章,参考了网上很多同学的文章,甚至有些地方是直接拷贝的,在此表示感谢,希望他们不要介意。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: