java学习之--java泛型

开始之前先说几句废话,公司有个可爱的人事要对刚入职的同事提供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、泛型类的定义和使用

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

[java]  view plain copy
  1. class Pair<T> {  
  2.     private T value;  
  3.         public Pair(T value) {  
  4.                 this.value=value;  
  5.         }  
  6.         public T getValue() {  
  7.         return value;  
  8.     }  
  9.     public void setValue(T value) {  
  10.         this.value = value;  
  11.     }  
  12. }  
现在我们就可以使用这个泛型类了:
[java]  view plain copy
  1. public static void main(String[] args) throws ClassNotFoundException {  
  2.         Pair<String> pair=new Pair<String>("Hello");  
  3.         String str=pair.getValue();  
  4.         System.out.println(str);  
  5.         pair.setValue("World");  
  6.         str=pair.getValue();  
  7.         System.out.println(str);  
  8.     }  

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

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

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

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

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

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

[java]  view plain copy
  1. public static <T extends Comparable> T get(T t1,T t2) { //添加类型限定  
  2.         if(t1.compareTo(t2)>=0);  
  3.         return t1;  
  4.     }  
类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:

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

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

[java]  view plain copy
  1. public static <T extends Comparable&Serializable> T get(T t1,T t2)  
3、如果限定既有接口也有类,那么类必须只有一个,并且放在首位置

[java]  view plain copy
  1. 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. }  

是不是很简单。

好了以上就是我对泛型的一些简单的理解,如有不对的地方,欢迎大家指出。不得不说这篇文章,参考了网上很多同学的文章,甚至有些地方是直接拷贝的,在此表示感谢,希望他们不要介意。偷笑





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值