Java泛型编程是JDK1.5版本后引入的.泛型让编程人员可以使用类型抽象,一般用于集合里面.最大的特色是泛型中的属性能够由外部决定. java
类的泛型声明格式: 编程
class 类名称<泛型类型, 泛型类型... ...>{} 数组
示例: 安全
List myIntList=new LinkedList(); myIntList.add(new Integer(0)); Integer x=(Integer)myIntList.iterator().next(); // next()返回的是Object,因此必须强转注意第 3行 ,存储在 List里面的对象类型是 Integer,可是在返回列表中元素时 ,仍是必须强制转换类型 ,这是为何呢?缘由在于 ,编译器只能保证迭代器的 next()方法返回的是 Object类型的对象 ,为保证 Integer变量的类型安全 ,因此必须强制转换 .
这种转换不只显得混乱,更可能致使类型转换异常ClassCastException,为保证操做安全,减小转换发生错误, 而泛型使取出变得很是容易,不须要再使用向下转型.这就是泛型设计的初衷. 数据结构
示例: app
List<Integer> myIntList=newLinkedList<Integer>(); myIntList.add(new Integer(0)); Integer x=myIntList.iterator().next();在第 1行代码中 指定List 中存储的对象类型为Integer,这样在获取列表中的对象时 ,没必要强制转换类型了 .
下面是一个引用自java.util包中的接口List和Iterator的定义,其中用到了泛型技术. 函数
示例: 学习
public interface List<E> { //类型由外部决定 void add(E x); //add的类型与设置类型保持一致 Iterator<E> iterator(); } public interface Iterator<E> { //类型由外部决定 E next(); //返回的类型与传入类型保持一致 boolean hasNext(); }这跟原生类型没有什么区别 ,只是在接口后面加入了一个尖括号 ,尖括号里面是一个类型参数 .
List<Integer>表示List中的类型参数E被替换成Integer类型.和以下代码等价: ui
public interface IntegerList { void add(Integer x) Iterator<Integer> iterator(); }
类型擦除指的是经过类型参数合并,将泛型类型实例关联到同一份字节码上.在使用时若是没有指定泛型类型,则表示擦除泛型类型.擦除后按Object接收.通常不要擦除泛型,由于没有什么实际意义,同时以保证操做的安全性. this
编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,所以泛型类型中的静态变量是全部实例共享的.故一个static方法,没法访问泛型类的类型参数,由于类尚未实例化,因此,若static方法须要使用泛型能力,必须使其成为泛型方法.
类型擦除的关键在于从泛型类型中清除类型参数的相关信息,而且在必要的时候添加类型检查和类型转换的方法.在使用泛型时,任何具体的类型都被擦除,惟一知道的是你在使用一个对象.好比:List<String>和List<Integer>在运行事实上是相同的类型.他们都被擦除成他们的原生类型,即List.
由于编译的时候会有类型擦除,因此不能经过同一个泛型类的实例来区分方法,由于类型擦除后,两个方法都是List类型的参数,所以并不能根据泛型类的类型来区分方法.
示例:
public class Info<T> { private T msg; public Info(T msg) { this.msg = msg; } public T getMsg() { return msg; } public void setMsg(T msg) { this.msg = msg; } } public class GenDemo { public static void main(String[] args) { Info info0 = new Info(1) ; // 没有指定泛型类型,但写代码时会有警告 Info<Object> info1 = new Info<Object>(1) ; // 没有指定泛型类型 Info<Object> info2 = new Info<Object>("1") ; // 没有指定泛型类型 //注意的是 info0, info1, info2的类型都是Info,并非Integer和String型 System.out.println(info0.getClass() == info1.getClass()); //True } }
那么这就有个问题了 ,既然在编译的时候会在方法和类中擦除实际类型的信息 ,那么在返回对象时又是如何知道其具体类型的呢?如 List<String>编译后会擦除掉 String信息 ,那么在运行时经过迭代器返回 List中的对象时 ,又是如何知道 List中存储的是 String类型对象呢?
擦除在方法体中移除了类型信息,因此在运行时的问题就是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点.泛型中的全部动做都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型.
泛型不支持咱们以前接触过的向上转型,为了完全理解泛型,这里看个例子:(Apple为Fruit的子类)
示例:
List<Apple> apples = new ArrayList<Apple>(); // 向上转型 List<Fruit> fruits = apples;
第 1行代码显然是对的 ,可是第 2行在编译的时候会出错 .这会让人比较纳闷的是一个苹果是水果 ,为何一箱苹果就不是一箱水果了呢?能够这样考虑 ,假定第 2行代码没有问题 ,那么咱们可使用语句 fruits.add(new Strawberry())在 fruits中加入草莓了 ,可是这样的话 ,一个 List中装入了各类不一样类型的子类水果 ,这显然是不能够的 ,由于咱们在取出 List中的水果对象时 ,就分不清楚到底该转型为苹果仍是草莓了 .
一般来讲,若是Foo是Bar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型.这也是泛型学习里面最让人容易混淆的一点.
先看一个打印集合中全部元素的代码.
示例:
//不使用泛型 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的子类 .
这里就又出现了另一个问题,若是试图往使用通配符?的集合中加入(add)对象,就会在编译时出现错误.
须要注意的是,这里无论加入什么类型的对象都会出错.这是由于通配符?表示该集合存储的元素类型未知,能够是任何类型.往集合中加入元素须要是一个未知元素类型的子类型,正由于该集合存储的元素类型未知,因此咱们无法向该集合中添加任何元素.惟一的例外是null,由于null是全部类型的子类型,因此尽管元素类型不知道,可是null必定是它的子类型.
示例:
Collection<?> c=new ArrayList<String>(); c.add(new Object()); //compile time error,无论加入什么对象都出错,除了null外。 c.add(null); //OK另外一方面 ,咱们能够从 List<?> lists中获取对象 ,虽然不知道 List中存储的是什么类型 ,可是能够确定的是存储的类型必定是 Object的子类型 ,因此能够用 Object类型来获取值 .如 for(Object obj: lists),这是合法的 .
1)?extends通配符
即泛型的上限,如今假定有一个画图的应用,能够画各类形状的图形,如矩形和圆形等.
示例:
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) { ... } }为了画出集合中全部的形状 ,咱们能够定义一个函数 ,该函数接受带有泛型的集合类对象做为参数 .可是不幸的是 ,咱们只能接收元素类型为 Shape的 List对象 ,而不能接收类型为 List<Cycle>的对象 ,这在前面已经说过 .为了解决这个问题 ,因此有了 边界通配符的概念 .这里能够采用 public void drawAll(List<? extends Shape> shapes)来知足条件 ,这样就能够接收元素类型为 Shape子类型的列表做为参数了 .
示例:
//原始版本 public void drawAll(List<Shape> shapes) { for (Shape s:shapes) { s.draw(this); } }
示例:
//使用边界通配符的版本 public void drawAll(List<?exends Shape> shapes) { for (Shape s:shapes) { s.draw(this); } }这里就又有个问题要注意了 ,若是咱们但愿在 List<? exends Shape> shapes中加入一个矩形对象 ,以下所示:
shapes.add(0, new Rectangle()); //compile-time error那么这时会出现一个编译时错误 ,缘由在于:咱们只知道 shapes中的元素时 Shape类型的子类型 ,具体是什么子类型咱们并不清楚 ,因此咱们不能往 shapes中加入任何类型的对象 . 不过咱们在取出其中对象时, 可使用Shape类型来取值 ,由于虽然咱们不知道列表中的元素类型具体是什么类型 ,可是咱们确定的是它必定是 Shape类的子类型 .
2)?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这表示 cicle Supers列表存储的元素为 Cicle的超类 ,所以咱们能够往其中加入 Cicle对象或者 Cicle的子类对象 ,可是不能加入 Shape对象 .这里的缘由在于列表 cicle Supers存储的元素类型为 Cicle的超类 ,可是具体是 Cicle的什么超类并不清楚 .可是咱们能够肯定的是只要是 Cicle或者 Circle的子类 ,则必定是与该元素类别兼容 .
3)边界通配符总结
若想从一个数据类型里获取数据,使用 ? extends 通配符
若想把对象写入一个数据结构里,使用 ? super 通配符
若既想存,又想取,那就别用通配符.
考虑实现一个方法,该方法拷贝一个数组中的全部对象到集合中.下面是初始的版本:
示例:
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类型的实参调用函数 ,则会调用泛型方法 .
1)方法重载
在JAVA里面方法重载是不能经过返回值类型来区分的,好比代码一中一个类中定义两个以下的方法是不允许的.可是当参数为泛型类型时,倒是能够的.以下面代码二中所示,虽然形参通过类型擦除后都为List类型,可是返回类型不一样,这是能够的.
示例1:
/*代码一:编译时错误*/ public class Erasure{ public void test(int i){ System.out.println("Sting"); } public int test(int i){ System.out.println("Integer"); } }
示例2:
/*代码二:正确 */ 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
3)instanceof
不能对确切的泛型类型使用instanceOf操做.以下面的操做是非法的,编译时会出错.
示例:
Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>){…}// compile error. //若是改为instanceof Collection<?>则不会出错.
不能建立一个确切泛型类型的数组.以下面代码会出错.
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://qiemengdao.iteye.com/blog/1525624
20150419
JAVA学习笔记系列
--------------------------------------------
联系方式
--------------------------------------------
Weibo: ARESXIONG
E-Mail: aresxdy@gmail.com
------------------------------------------------