1.为什么需要泛型

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52230032

泛型在Java中有很重要的地位,网上很多文章罗列各种理论,不便于理解,本篇将立足于代码介绍、总结了关于泛型的知识。

先看下面的代码:

  1. List list = new ArrayList();
  2. list.add("CSDN_SEU_Calvin");
  3. list.add(100);
  4. for (int i = 0; i < list.size(); i++) {
  5. String name = (String) list.get(i); //取出Integer时,运行时出现异常
  6. System.out.println("name:" + name);
  7. }
List list = new ArrayList();
list.add("CSDN_SEU_Calvin");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); //取出Integer时,运行时出现异常
System.out.println("name:" + name);
}

本例向list类型集合中加入了一个字符串类型的值和一个Integer类型的值(这样是合法的,因为此时list默认的类型为Object类型)。

在循环中,由于忘记了之前添加了Integer类型的值或其他原因,运行时会出现java.lang.ClassCastException。为了解决这个问题,泛型应运而生。

2.泛型的使用

Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。

只要在上例中将第1行代码改成如下形式,那么就会在编译list.add(100)时报错。

  1. List<String> list = new ArrayList<String>();
List<String> list = new ArrayList<String>();

通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在上例中的第6行中,无须进行强制类型转换,因为集合能够记住其中元素的类型信息,编译器已经能够确认它是String类型了。

3.泛型只在编译阶段有效

看下面的代码:

  1. ArrayList<String> a = new ArrayList<String>();
  2. ArrayList b = new ArrayList();
  3. Class c1 = a.getClass();
  4. Class c2 = b.getClass();
  5. System.out.println(c1 == c2); //true
ArrayList<String> a = new ArrayList<String>();
ArrayList b = new ArrayList();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); //true

上面程序的输出结果为true。因为所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施。

也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。

上述结论可通过下面反射的例子来印证:

  1. ArrayList<String> a = new ArrayList<String>();
  2. a.add("CSDN_SEU_Calvin");
  3. Class c = a.getClass();
  4. try{
  5. Method method = c.getMethod("add",Object.class);
  6. method.invoke(a,100);
  7. }catch(Exception e){
  8. e.printStackTrace();
  9. }System.out.println(a);
ArrayList<String> a = new ArrayList<String>();
a.add("CSDN_SEU_Calvin");
Class c = a.getClass();
try{
Method method = c.getMethod("add",Object.class);
method.invoke(a,100); }catch(Exception e){

e.printStackTrace();

}System.out.println(a);

因为绕过了编译阶段也就绕过了泛型,输出结果为:

  1. [CSDN_SEU_Calvin, 100]  
[CSDN_SEU_Calvin, 100]

4.泛型类和泛型方法

如下,我们看一个泛型类和方法的使用例子,和未使用泛型的使用方法进行了对比,两者输出结果相同,在这里贴出来方便读者体会两者的差异。泛型接口的例子有兴趣可以去找一些资料,这里就不赘述了。

(1)使用泛型的情况

  1. public static class FX<T> {  
  2.     private T ob; // 定义泛型成员变量  
  3.   
  4.     public FX(T ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public T getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showType() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.     public static void main(String[] args) {  
  17.         FX<Integer> intOb = new FX<Integer>(100);  
  18.         intOb.showType();  
  19.         System.out.println("value= " + intOb.getOb());  
  20.         System.out.println("----------------------------------");  
  21.   
  22.         FX<String> strOb = new FX<String>("CSDN_SEU_Calvin");  
  23.         strOb.showType();  
  24.         System.out.println("value= " + strOb.getOb());  
  25. }  
public static class FX<T> {

private T ob; // 定义泛型成员变量
public FX(T ob) {
this.ob = ob;
} public T getOb() {
return ob;
} public void showType() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}

}

public static void main(String[] args) {

FX<Integer> intOb = new FX<Integer>(100);

intOb.showType();

System.out.println("value= " + intOb.getOb());

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

	FX&lt;String&gt; strOb = new FX&lt;String&gt;("CSDN_SEU_Calvin");
strOb.showType();
System.out.println("value= " + strOb.getOb());

}

(2)不使用泛型的情况

  1. public static class FX {  
  2.     private Object ob; // 定义泛型成员变量  
  3.   
  4.     public FX(Object ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public Object getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showType() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.   
  17.     public static void main(String[] args) {  
  18.         FX intOb = new FX(new Integer(100));  
  19.         intOb.showType();  
  20.         System.out.println("value= " + intOb.getOb());  
  21.         System.out.println("----------------------------------");  
  22.   
  23.         FX strOb = new FX("CSDN_SEU_Calvin");  
  24.         strOb.showType();  
  25.         System.out.println("value= " + strOb.getOb());  
  26.     }  
public static class FX {

private Object ob; // 定义泛型成员变量
public FX(Object ob) {
this.ob = ob;
} public Object getOb() {
return ob;
} public void showType() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}

}

public static void main(String[] args) {
FX intOb = new FX(new Integer(100));
intOb.showType();
System.out.println("value= " + intOb.getOb());
System.out.println("----------------------------------"); FX strOb = new FX("CSDN_SEU_Calvin");
strOb.showType();
System.out.println("value= " + strOb.getOb());
}

两种写法输出结果均为:

  1. T的实际类型是: java.lang.Integer  
  2. value= 100  
  3. ----------------------------------  
  4. T的实际类型是: java.lang.String  
  5. value= CSDN_SEU_Calvin  
T的实际类型是: java.lang.Integer

value= 100 T的实际类型是: java.lang.String

value= CSDN_SEU_Calvin

5.通配符

为了引出通配符的概念,先看如下代码:

  1. List<Integer> ex_int= new ArrayList<Integer>();    
  2. List<Number> ex_num = ex_int; //非法的  
List<Integer> ex_int= new ArrayList<Integer>();

List<Number> ex_num = ex_int; //非法的

上述第2行会出现编译错误,因为Integer虽然是Number的子类,但List<Integer>不是List<Number>的子类。

假定第2行代码没有问题,那么我们可以使用语句ex_num.add(newDouble())在一个List中装入了各种不同类型的子类,这显然是不可以的,因为我们在取出List中的对象时,就分不清楚到底该转型为Integer还是Double了。因此,我们需要一个在逻辑上可以用来同时表示为List<Integer>和List<Number>的父类的一个引用类型,类型通配符应运而生。在本例中表示为List<?>即可。

下面这个例子也说明了这一点,注释已经写的很清楚了。

  1. public static void main(String[] args) {  
  2.     FX<Number> ex_num = new FX<Number>(100);  
  3.     FX<Integer> ex_int = new FX<Integer>(200);  
  4.     getData(ex_num);  
  5.     getData(ex_int);//编译错误  
  6. }  
  7.   
  8. public static void getData(FX<Number> temp) { //此行若把Number换为“?”编译通过  
  9.     //do something...  
  10. }  
  11.       
  12. public static class FX<T> {  
  13.     private T ob;   
  14.     public FX(T ob) {  
  15.         this.ob = ob;  
  16.     }  
  17. }  
public static void main(String[] args) {

FX<Number> ex_num = new FX<Number>(100);

FX<Integer> ex_int = new FX<Integer>(200);

getData(ex_num);

getData(ex_int);//编译错误

} public static void getData(FX<Number> temp) { //此行若把Number换为“?”编译通过

//do something...

} public static class FX<T> {

private T ob;

public FX(T ob) {

this.ob = ob;

}

}

6.上下边界

看了下面这个上边界的例子就明白了,下界FX<? supers Number>的形式就不做过多赘述了。

  1. public static void main(String[] args) {  
  2.     FX<Number> ex_num = new FX<Number>(100);  
  3.     FX<Integer> ex_int = new FX<Integer>(200);  
  4.     getUpperNumberData(ex_num);  
  5.     getUpperNumberData(ex_int);  
  6. }  
  7.   
  8. public static void getUpperNumberData(FX<? extends Number> temp){  
  9.       System.out.println("class type :" + temp.getClass());  
  10. }  
  11.       
  12. public static class FX<T> {  
  13.     private T ob;   
  14.     public FX(T ob) {  
  15.     this.ob = ob;  
  16.     }  
  17. }  
public static void main(String[] args) {

FX<Number> ex_num = new FX<Number>(100);

FX<Integer> ex_int = new FX<Integer>(200);

getUpperNumberData(ex_num);

getUpperNumberData(ex_int);

} public static void getUpperNumberData(FX<? extends Number> temp){

System.out.println("class type :" + temp.getClass());

} public static class FX<T> {

private T ob;

public FX(T ob) {

this.ob = ob;

}

}

7.泛型的好处

(1)类型安全。 

通过知道泛型定义的变量类型限制,编译器可以更有效地提高Java程序的类型安全。 

(2)消除强制类型转换。 

消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的。

(3)提高性能。 

  1. Lits list1 = new ArrayList();  
  2. list1.add("CSDN_SEU_Calvin ");  
  3. String str1 = (String)list1.get(0);  
Lits list1 = new ArrayList();

list1.add("CSDN_SEU_Calvin ");

String str1 = (String)list1.get(0);
  1. List<String> list2 = new ArrayList<String>();  
  2. list2.add("CSDN_SEU_Calvin ");  
  3. String str2 = list2.get(0);  
List<String> list2 = new ArrayList<String>();

list2.add("CSDN_SEU_Calvin ");

String str2 = list2.get(0);

对于上面的两段程序,由于泛型所有工作都在编译器中完成,javac编译出来的字节码是一样的(只是更能确保类型安全),那么何谈性能提升呢?是因为在泛型的实现中,编译器将强制类型转换插入生成的字节码中,但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来了可能。

8.泛型使用的注意事项

(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。

(2)泛型的类型参数可以有多个。

(3)不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

  1. if(ex_num instanceof FX<Number>){   
  2. }  
if(ex_num instanceof FX<Number>){	

}

(4)不能创建一个确切的泛型类型的数组。下面使用Sun的一篇文档的一个例子来说明这个问题:

<o:p></o:p>

<o:p></o:p>

  1. List<String>[] lsa = new List<String>[10]; // Not really allowed.    
  2. Object o = lsa;    
  3. Object[] oa = (Object[]) o;    
  4. List<Integer> li = new ArrayList<Integer>();    
  5. li.add(new Integer(3));    
  6. oa[1] = li; // Unsound, but passes run time store check    
  7. String s = lsa[1].get(0); // Run-time error: ClassCastException.    
List<String>[] lsa = new List<String>[10]; // Not really allowed.

Object o = lsa;

Object[] oa = (Object[]) o;

List<Integer> li = new ArrayList<Integer>();

li.add(new Integer(3));

oa[1] = li; // Unsound, but passes run time store check

String s = lsa[1].get(0); // Run-time error: ClassCastException.

这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList<Integer>而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

下面采用通配符的方式是被允许的:

  1. List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
  2. Object o = lsa;    
  3. Object[] oa = (Object[]) o;    
  4. List<Integer> li = new ArrayList<Integer>();    
  5. li.add(new Integer(3));    
  6. oa[1] = li; // Correct.    
  7. Integer i = (Integer) lsa[1].get(0); // OK  
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.

Object o = lsa;

Object[] oa = (Object[]) o;

List<Integer> li = new ArrayList<Integer>();

li.add(new Integer(3));

oa[1] = li; // Correct.

Integer i = (Integer) lsa[1].get(0); // OK

9.List与List<?>

(1)List实际上也是List<Object>。List实际上表示持有任何Object类型的原生List。

(2)而List<?>表示具有某种特定类型的非原生List,只是我们不知道那种类型是什么。

Java技术——Java泛型详解(转)的更多相关文章

  1. Java 中的泛型详解-Java编程思想

    Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限. 2.简单泛型 促成泛型出现最引人注目的一个原因就是为了创造容器类. 首先看一个只能持有单个对象的类,这个类可以明确指定其持有的 ...

  2. java 泛型详解(普通泛型、 通配符、 泛型接口)

    java 泛型详解(普通泛型. 通配符. 泛型接口) JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能---- ...

  3. java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

    对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 1. 概述 泛型在 ...

  4. Java泛型详解(转)

    文章转自  importNew:Java 泛型详解 引言 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理 ...

  5. 【转】java 泛型详解

    java 泛型详解 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考java 泛型详解.Java中的泛型方法. java泛型详解 ...

  6. Java的泛型详解(一)

    Java的泛型详解 泛型的好处 编写的代码可以被不同类型的对象所重用. 因为上面的一个优点,泛型也可以减少代码的编写. 泛型的使用 简单泛型类 public class Pair<T> { ...

  7. 【转载】Java泛型详解

    [转载]http://www.importnew.com/24029.html 对java的泛型特性的了解仅限于表面的浅浅一层,直到在学习设计模式时发现有不了解的用法,才想起详细的记录一下. 本文参考 ...

  8. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

  9. java ssl https 连接详解 生成证书 tomcat keystone

    java ssl https 连接详解 生成证书 我们先来了解一下什么理HTTPS 1. HTTPS概念 1)简介 HTTPS(全称:Hypertext Transfer Protocol over ...

  10. Java基础学习总结(24)——Java单元测试之JUnit4详解

    Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且是第一个运行 @Before  ...

随机推荐

  1. server用JDBC对mysql数据库进行操作

    1:获取数据库连接 Connection connection=getConnection(); 2:准备SQL语句 3:调用Connection的creatStatement()方法获取Statem ...

  2. CSS常用原子类base.css

    在写css文件时,一些常用的属性我们完全可以把它单独提出来,提高复用性,能增加开发效率,下面是一些网站推荐的常用原子类,也是零度逍遥常用的,规定了一些字体,内外边距和宽高属性,一般写在base.css ...

  3. 17.I/O系统访问方式和类型

    I/O方式 轮询  中断  DMA  通道

  4. POJ 3672 水题......

    5分钟写完 水水更开心 //By SiriusRen #include <cstdio> #include <iostream> #include <algorithm& ...

  5. 使用Visual Studio2012调试Redis源码

    Redis是一款C语言编写Key-Value存储系统,基于BSD协议开放源码,其源码托管在github上,大概有三万行. 源码地址:https://github.com/antirez/redis 源 ...

  6. 最标准的 Java MySQL 连接

    package com.runoob.test; import java.sql.*; public class MySQLDemo { // JDBC 驱动名及数据库 URL static fina ...

  7. 976 B. Lara Croft and the New Game

    You might have heard about the next game in Lara Croft series coming out this year. You also might h ...

  8. PL/SQL恢复默认窗口样式

    对于初学者来讲,使用PL/SQL时会不小心将窗体关闭,如下图 2. 怎么恢复呢?其实很简单 3. 搞定 转自:http://blog.csdn.net/hello_word2/article/deta ...

  9. Linux常用命令及其英文全称

      alias:给命令起别名 awk = "Aho Weiberger and Kernighan" ,三个作者的姓的第一个字母 bash:GNU Bourne-Again She ...

  10. python2中打印列表与字典内的中文字符

    在开发过程中,我们经常需要打印一些变量的值,便于调试.这个时候就会发现如果在列表与字典这些容器中,如果包含中文字符,不管是str类型,还是unicode类型,都打印不出来.如下: >>&g ...