`
qiemengdao
  • 浏览: 272599 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Java泛型编程最全总结

阅读更多

由于发到iteye上面格式乱了,需要的朋友可以下载附件。

 

JAVA泛型编程笔记

1介绍

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

 

List myIntList=new LinkedList(); //1
myIntList.add(newInteger(0)); //2
Integer x=(Integer)myIntList.iterator().next(); //3
 

注意第3行代码,但这是让人很不爽的一点,因为程序员肯定知道自己存储在List里面的对象类型是Integer,但是在返回列表中元素时,还是必须强制转换类型,这是为什么呢?原因在于,编译器只能保证迭代器的next()方法返回的是Object类型的对象,为保证Integer变量的类型安全,所以必须强制转换。

这种转换不仅显得混乱,更可能导致类型转换异常ClassCastException,运行时异常往往让人难以检测到。保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会, 这也是泛型设计的初衷。下面是一个使用了泛型的例子:

 

List<Integer> myIntList=newLinkedList<Integer>(); //1’
myIntList.add(newInteger(0)); //2’
Integerx=myIntList.iterator().next(); //3’
 

       在第1行代码中指定List中存储的对象类型为Integer,这样在获取列表中的对象时,不必强制转换类型了。

 

2定义简单的泛型

下面是一个引用自java.util包中的接口ListIterator的定义,其中用到了泛型技术。

 

public interface List<E> {
	void add(E x);
	Iterator<E> iterator();
}
public interface Iterator<E> {
	E next();
	boolean hasNext();
}
 

       这跟原生类型没有什么区别,只是在接口后面加入了一个尖括号,尖括号里面是一个类型参数(定义时就是一个格式化的类型参数,在调用时会使用一个具体的类型来替换该类型)。

       也许可以这样认为,List<Integer>表示List中的类型参数E会被替换成Integer

 

public interface IntegerList {
	void add(Integer x)
	Iterator<Integer> iterator();
}
 

       类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。此外,需要注意的是,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以,若static方法需要使用泛型能力,必须使其成为泛型方法类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List<String>List<Integer>在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List因为编译的时候会有类型擦除,所以不能通过同一个泛型类的实例来区分方法,如下面的例子编译时会出错,因为类型擦除后,两个方法都是List类型的参数,因此并不能根据泛型类的类型来区分方法。

 

/*会导致编译时错误*/ 
 public class Erasure{
            public void test(List<String> ls){
                System.out.println("Sting");
            }
            public void test(List<Integer> li){
                System.out.println("Integer");
            }
  }
 

       那么这就有个问题了,既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?如List<String>编译后会擦除掉String信息,那么在运行时通过迭代器返回List中的对象时,又是如何知道List中存储的是String类型对象呢?

       擦除在方法体中移除了类型信息,所以在运行时的问题就是边界即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。

 

3.泛型和子类型

为了彻底理解泛型,这里看个例子:(AppleFruit的子类)

 

List<Apple> apples = new ArrayList<Apple>(); //1
List<Fruit> fruits = apples; //2
 

1行代码显然是对的,但是第2行是否对呢?我们知道Fruit fruit = new Apple(),这样肯定是对的,即苹果肯定是水果,但是第2行在编译的时候会出错。这会让人比较纳闷的是一个苹果是水果,为什么一箱苹果就不是一箱水果了呢?可以这样考虑,我们假定第2行代码没有问题,那么我们可以使用语句fruits.add(new Strawberry())StrawberryFruit的子类)在fruits中加入草莓了,但是这样的话,一个List中装入了各种不同类型的子类水果,这显然是不可以的,因为我们在取出List中的水果对象时,就分不清楚到底该转型为苹果还是草莓了。

通常来说,如果FooBar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型。这也许是泛型学习里面最让人容易混淆的一点。

 

4.通配符

4.1通配符?

先看一个打印集合中所有元素的代码。

 

//不使用泛型
void printCollection(Collection c) {                
	Iterator i=c.iterator();
	for (k=0;k < c.size();k++) {
		System.out.println(i.next());
	}
}
 

 

//使用泛型
void printCollection(Collection<Object> c) {
for (Object e:c) {
System.out.println(e);
}
}
 

     很容易发现,使用泛型的版本只能接受元素类型为Object类型的集合如ArrayList<Object>();如果是ArrayList<String>,则会编译时出错。因为我们前面说过,Collection<Object>并不是所有集合的超类。而老版本可以打印任何类型的集合,那么如何改造新版本以便它能接受所有类型的集合呢?这个问题可以通过使用通配符来解决。修改后的代码如下所示:

 

//使用通配符?,表示可以接收任何元素类型的集合作为参数
void printCollection(Collection<?> c) {
	for (Object e:c) {
		System.out.println(e);
	}
}
 

这里使用了通配符?指定可以使用任何类型的集合作为参数。读取的元素使用了Object类型来表示,这是安全的,因为所有的类都是Object的子类。这里就又出现了另外一个问题,如下代码所示,如果试图往使用通配符?的集合中加入对象,就会在编译时出现错误。需要注意的是,这里不管加入什么类型的对象都会出错。这是因为通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素。唯一的例外是null,因为null是所有类型的子类型,所以尽管元素类型不知道,但是null一定是它的子类型。

 

Collection<?> c=new ArrayList<String>();
c.add(newObject()); //compile time error,不管加入什么对象都出错,除了null外。
c.add(null); //OK
 

另一方面,我们可以从List<?> lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Object obj: lists),这是合法的。

4.2边界通配符

   1extends通配符

假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:

 

public abstract class Shape {
	public abstract void draw(Canvas c);
}

public class Circle extends Shape {
	private int x,y,radius;
	public void draw(Canvas c) { ... }
}

public class Rectangle extends Shape
	private int x,y,width,height;
	public void draw(Canvasc) { ... }
}

 

为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。但是不幸的是,我们只能接收元素类型为ShapeList对象,而不能接收类型为List<Cycle>的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。这里可以采用public void drawAll(List<? extends Shape> shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。

 

//原始版本
public void drawAll(List<Shape> shapes) {
	for (Shapes:shapes) {
		s.draw(this);
	}
}
 

 

//使用边界通配符的版本
public void drawAll(List<?exends Shape> shapes) {
	for (Shapes:shapes) {
		s.draw(this);
	}
}
 

这里就又有个问题要注意了,如果我们希望在List<exends Shape> shapes中加入一个矩形对象,如下所示:

shapes.add(0, new Rectangle()); //compile-time error

那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。

 

2)?super通配符

       这里还有一种边界通配符为?super。比如下面的代码:

 

List<Shape> shapes = new ArrayList<Shape>();
List<? super Cicle> cicleSupers = shapes;
cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK
cicleSupers.add(new Shape()); //ERROR
 

       这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象。这里的原因在于列表cicleSupers存储的元素类型为Cicle的超类,但是具体是Cicle的什么超类并不清楚。但是我们可以确定的是只要是Cicle或者Circle的子类,则一定是与该元素类别兼容。

 

3)边界通配符总结

<!--[if !supportLists]-->l         <!--[endif]-->如果你想从一个数据类型里获取数据,使用 ? extends 通配符

<!--[if !supportLists]-->l         <!--[endif]-->如果你想把对象写入一个数据结构里,使用 ? super 通配符

<!--[if !supportLists]-->l         <!--[endif]-->如果你既想存,又想取,那就别用通配符。

 

5.泛型方法

考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本:

 

static void fromArrayToCollection(Object[]a, Collection<?> c) {
	for (Object o:a) {
		c.add(o); //compile time error
	}
}
 

可以看到显然会出现编译错误,原因在之前有讲过,因为集合c中的类型未知,所以不能往其中加入任何的对象(当然,null除外)。解决该问题的一种比较好的办法是使用泛型方法,如下所示:

 

static <T> void fromArrayToCollection(T[] a, Collection<T>c){
	for(T o : a) {
		c.add(o);// correct
	}
}
 

注意泛型方法的格式,类型参数<T>需要放在函数返回值之前。然后在参数和返回值中就可以使用泛型参数了。具体一些调用方法的实例如下:

 

Object[] oa = new Object[100];
Collection<Object>co = new ArrayList<Object>();
fromArrayToCollection(oa, co);// T inferred to be Object
String[] sa = new String[100];
Collection<String>cs = new ArrayList<String>();
fromArrayToCollection(sa, cs);// T inferred to be String
fromArrayToCollection(sa, co);// T inferred to be Object
Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number>cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);// T inferred to be Number
fromArrayToCollection(fa, cn);// T inferred to be Number
fromArrayToCollection(na, cn);// T inferred to be Number
fromArrayToCollection(na, co);// T inferred to be Object
fromArrayToCollection(na, cs);// compile-time error
 

注意到我们调用方法时并不需要传递类型参数,系统会自动判断类型参数并调用合适的方法。当然在某些情况下需要指定传递类型参数,比如当存在与泛型方法相同的方法的时候(方法参数类型不一致),如下面的一个例子:

 

public  <T> void go(T t) {
	System.out.println("generic function");
}
public void go(String str) {
	System.out.println("normal function");
}
public static void main(String[] args) {
		FuncGenric fg = new FuncGenric();
		fg.go("haha");//打印normal function
		fg.<String>go("haha");//打印generic function
        fg.go(new Object());//打印generic function
		fg.<Object>go(new Object());//打印generic function
}
 

如例子中所示,当不指定类型参数时,调用的是普通的方法,如果指定了类型参数,则调用泛型方法。可以这样理解,因为泛型方法编译后类型擦除,如果不指定类型参数,则泛型方法此时相当于是public void go(Object t)。而普通的方法接收参数为String类型,因此以String类型的实参调用函数,肯定会调用形参为String的普通方法了。如果是以Object类型的实参调用函数,则会调用泛型方法。

6.其他需要注意的小点

1)方法重载

JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。

 

/*代码一:编译时错误*/ 
public class Erasure{
            public void test(int i){
                System.out.println("Sting");
            }
            public int test(int i){
                System.out.println("Integer");
            }
  }
 

 

/*代码二:正确 */
 public class Erasure{
            public void test(List<String> ls){
                System.out.println("Sting");
            }
            public int test(List<Integer> li){
                System.out.println("Integer");
            }
  }
 

 

2)泛型类型是被所有调用共享的

       所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList<String>ArrayList<Integer>类型参数不同,但是他们都共享ArrayList类,所以结果会是true

      

 

List<String>l1 = new ArrayList<String>();
List<Integer>l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass()); //True
 

 

3instanceof

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

 

Collection cs = new ArrayList<String>();
if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>则不会出错。
 

 

4)泛型数组问题

不能创建一个确切泛型类型的数组。如下面代码会出错。

List<String>[] lsa = new ArrayList<String>[10]; //compile error.

       因为如果可以这样,那么考虑如下代码,会导致运行时错误。

 

List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组
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
 

       因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。

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
String s = (String) lsa[1].get(0);// run time error, but cast is explicit
Integer it = (Integer)lsa[1].get(0); // OK 

  

参考资料:

http://www.aqee.net/java-generics-quick-tutorial/

http://www.infoq.com/cn/articles/cf-java-generics

http://blog.csdn.net/daniel_h1986/article/details/5708605

http://www.cnblogs.com/stephen-liu74/archive/2012/01/20/2228938.html

sun官方文档:generics-tutorial.pdf

 

 

分享到:
评论
6 楼 tuspark 2016-08-14  
总结的不错,只是格式太规范。如果说最全面的泛型内容总结,我推荐这个系列:《站在巨著之上谈泛型》,感觉写的不错,排版也清晰,分析的也很透彻。
5 楼 huihui_0218 2015-07-14  
泛型方法go的调用

fg.<String>go("haha");//打印generic function 

有问题。 应该打印normal function

且上面调用go方法中<String>多余,虽然编译不报错。
4 楼 fantaxy025025 2015-05-24  
楼主总结的不错~赞一个!
3 楼 lijunwyf41 2013-09-26  
public static void main(String[] args) {
FuncGenric fg = new FuncGenric();
fg.go("haha");//打印normal function
fg.<String>go("haha");//打印generic function
        fg.go(new Object());//打印generic function
fg.<Object>go(new Object());//打印generic function
}

这一段代码报错,fg.<String>go这个可以这样写吗?
2 楼 宇宙幻影 2013-09-17  
搂着写的很好
1 楼 sonaive 2012-08-01  
import java.util.*;
import java.lang.reflect.*;
public class GenericsTest {

	/**
	 * 定义了泛型集合,向集合中添加该类型的子类对象依然成功
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		List<Ax>  list = new ArrayList<Ax>();
		list.add(new Ax());
		list.add(new Bx());
		list.add(new Cx());
		Iterator<Ax> iterator = list.iterator();
		while(iterator.hasNext()) {
			Object obj = iterator.next();
			Method fooMethod = obj.getClass().getMethod("foo");
			fooMethod.invoke(obj);
		}
	}

}
class Ax{
    public void foo() {
    	System.out.println("Ax object is in the list");
    }
}
class Bx extends Ax{
    public void foo() {
    	System.out.println("Bx object is in the list");
    }
}
class Cx extends Ax{
    public void foo() {
    	System.out.println("Cx object is in the list");
    }
}

博主,我用反射的话还是能用分辨出对象的类型并且调用相应的方法。这样做有什么好处呢或者坏处。我不明白为什么要用反射。

相关推荐

    Java基础知识点总结.docx

    Java学习更是如此,知识点总结目录如下: 目录 一、 Java概述 3 二、 Java语法基础 5 数据类型 5 运算符号 14 语句 15 函数 15 方法重载(Overloadjing)与重写(Overriding) 16 数组 17 总结 18 三、 常见关键字 ...

    C++转JAVA入门总结

    1. 内置数据类型 2. string类 3. 数组 4. 循环分支 5. 工具类(数据容器、日期、正则表达式……) 6. JAVA流、文件、IO 7. JAVA异常 ...3. JAVA泛型编程 4. JAVA序列化 5.JAVA网络与多线程 6. JAVA类生命周期

    孙卫琴java面向对象编程(答案及源码)

    本书采用由浅入深、与实际应用紧密结合的方式,利用大量经典的实例,详细讲解Java面向对象的编程思想、编程语法和设计模式,介绍常见Java类库的用法,总结优化Java编程的各种宝贵经验,深入阐述Java虚拟机执行Java...

    java技能总结.docx

    掌握Java的高级特性:掌握Java的反射、泛型、注解、并发编程等高级特性,以及Java集合框架和并发包的使用。 掌握Java Web开发:掌握Java Web开发的相关技术,如Servlet、JSP、Spring、Hibernate等,能够开发基于Web...

    实验5 JAVA常用类.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    实验9 Java输入输出流.doc

    本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。

    java语言程序设计 java编程笔记 由浅入深的笔记 共32份 全套资源.rar

    泛型.docx 封装和继承以及多态部分.docx 接口和抽象类以及实现类.docx 枚举enum.docx 设计模式.docx 数组.docx 网络编程.docx 线程和内部类.docx 循环和类对象.docx 异常.docx 正则表达式.docx 总结.docx

    免费超全面的Java基础类型,容器,并发,IO流,面向对象,Web编程等代码总结

    Java基础类型,容器,并发,IO流,面向对象,Web编程等代码总结。 2、分类文档 JVM虚拟机 JVM特点,结构与执行周期 JVM类加载机制 JVM运行时区数据 JVM执行引擎和垃圾回收 基础语法 理解Java中对象基础Object类 ...

    java oop总结

    Java面向对象编程概述 Java类定义 内部类和继承 继承的使用 异常处理 数组 Java常用类 集合类 泛型化的集合框架 使用Java的输入/输出类 Java GUI和Swing 事件委托模型.....的详细概述

    Java面向对象知识点梳理(思维导图)

    这个资源是一个Java面向对象知识点的思维导图,它涵盖了Java中面向对象编程的核心概念和重要知识点。导图中包含了类、对象、继承、多态、封装等基本概念,同时也包括了接口、抽象类、异常处理、泛型等高级特性。这个...

    代码随想录最新第三版-最强八股文

    C++基础、C++ STL、C++泛型编程、C++11新特性、《Effective STL》 2. Java Java基础、Java内存模型、Java面向对象、Java集合体系、接口、Lambda表达式、类加载机制、内部类、代理类、Java并发、JVM、Java后端编译、...

    Java基础核心总结.pdf

    Java核心基础知识总结,含思维导图,包含Java基本语法,面向对象,接口和抽象类,异常,内部类,集合,泛型,反射,枚举,I/O,注解等Java基础核心知识,总结全面,内容丰富,欢迎下载。 如果对你有用,麻烦点个收藏...

    从0开始学Java第一天-Java学习资料-源码基础-Java源码-总结

    第一天了解Java语言发展史,Java是一门具有面向对象思想,并且支持跨平台,并且支持泛型的高级编程语言。1.学习并掌握如何从0搭建Java环境(JDK的下载,安装,卸载);2.学会HelloWorld案例编写,知道如何解释该程序...

    Thinking in java4(中文高清版)-java的'圣经'

    第15章 泛型 第16章 数组 第17章 容器深入研究 第18章 Java I/O系统 第19章 枚举类型 第20章 注解 第21章 并发 第22章 图形化用户界面 附录A 补充材料 可下载的补充材料 Thinking in C:Java的基础 Java编程思想 ...

    阿里Java开发完整版手册

    阿里内部Java工程师所遵循的开发规范,涵盖编程规约、单元测试规约、异常日志规约、MySQL规约、工程规约、安全规约等,这是近万名阿里Java技术精英的经验总结. 还包含java基础泛型、反射、注解,java高级特性、设计...

    Java基础文档

    Java入门基础知识总结 第1章 、 数据类型和数组 1 第2章 、 运算符和表达式与语句 4 第3章 、 类和对象 5 第4章 、 继承、接口和泛型 11 第5章 、 字符串和正则表达式 15 第6章 、 常用的实用类 17 第7章 、 线程 ...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    JAVA基础课程讲义

    第一个JAVA程序的总结和提升 20 常用Java开发工具 20 常用dos命令 21 本章笔试作业 21 本章上机操作 21 第二章(1) 编程的基本概念 22 注释 22 标识符 22 关键字/保留字 23 变量(variable) 24 常量(Constant) 25 命名...

Global site tag (gtag.js) - Google Analytics