泛型总结
来源:互联网 发布:贪吃蛇大作战 数据 编辑:程序博客网 时间:2024/06/05 17:49
1介绍
Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。下面是一个不用泛型例子:
1 List myIntList=new LinkedList();3 myIntList.add(newInteger(0));5 Integer x=(Integer)myIntList.iterator().next();
这种转换不仅显得混乱,而且导致转换异常ClassCastException,运行时异常往往让人难以检测到。保证列表中的元素为一个特定的数据类型,这样就可以取消类型转换,减少发生错误的机会,这也是泛型设计的初衷。下面给出一个泛型的例子:
1 List<Integer> myIntList=newLinkedList<Integer>;3 myIntList.add(newInteger(0));5 Integer x=myIntList.iterator().next();
2 定义简单的泛型
下面是一个引用java.util包中的借口List和Iterator的定义,其中用到了泛型技术。
1 public interface List<E>{ 3 void add(E,x); 5 Iterator<E> iterator(); 7 } 9 public interface Iterator<E>{11 E next();13 boolean hasNext();15 }
也许可以这样认为,List<Integer>表示List中的类型参数E会被替换成Integer。
1 public interface List<Integer>{2 void add(Integer x);3 terator<Integer> iterator();4 }
类型擦除指的是通过类型参数的合并,将泛型类型实例关联到同一个字节码上,编译器只为泛型类型生成一个字节码,并将其关联到这上面,因此泛型类型中的静态变量是所有实例共享的。此外需要注意,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以若static方法需要使用泛型,必须使其成为泛型方法。
类型擦除的关键在于从泛型类中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如List<String>和List<Integer>是相同的类型。它们都被擦除成原始类型,即List。
因为编译的时候会有类型擦除,所以不能通过一个泛型类的实例来区分方法,如下面的例子编译会出错,因为类型擦除后,两个方法都是List类型的参数。因此不能根据泛型类的类型来区分方法。
1 /*会导致编译时出错*/2 public class Erasure{3 public void test(List<String> ls){4 System.out.println("String");5 }6 public void test(List<Integer> ls){7 System.out.println("Integer");8 }9 }
擦除在方法中的类型信息,所以在运行时的问题是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并出入转换代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译器检查,并插入对传递出去的值的转换。
3 泛型和子类型
为了彻底理解泛型,看个例子,(Apple为Fruit的子类)
1 List<Apple> apples=new ArrayList<Apple>();2 List<Fruit> fruits=apples;
通常来说,如果Foo是Bar的子类型,G是一种带泛型的类型,则G<Foo>不是G<Bar>的子类型。这是容易混淆的地方。
4 通配符
4.1 通配符?
先看一个打印集合所有元素的代码。
1 //不使用泛型2 void printCollection(Collection c){3 Iterator i=c.iterator();4 for(k=0;k<c.size();k++){5 System.out.println(i.next());6 }7 }
1 //使用泛型2 void printCollection(Collection c)<Object> c{3 for(Object e:c){4 System.out.println(e);5 }6 }
1 void printCollection(Collection c)<?> c{2 for(Object e:c){3 System.out.println(e);4 }5 }
这里使用了通配符?指定可以使用任何类型的集合作为参数。读取元素使用了Objectect类型表示,这是安全的,因为所有的类都是Object的子类。
又有另一个问题,如下面代码所示,如果试图往使用通配符?的集合中加入对象,会出错。需要注意,不管加入什么类型的对象都会出错。这是因为统配符表示该集合存储的元素类型未知,可以是任意的。
1 Collection<?> c=new ArrayList<String>();2 c.add(newObject());//编译出错,不管加入什么对象都出错,除了null外。3 c.add(null);//OK!
4.2 边界通配符
1)?extends通配符
假定有一个画图的应用,可以活各种形状。为了在程序里面表示,定义如下的类层次。
1 public abstract class Shape{ 2 public abstract void draw(Canvas c); 3 } 4 public class Circle extends Shape{ 5 private int x,y; 6 public void draw(Canvas c){...}; 7 } 8 public class Rectangle extends Shape{ 9 private int x,y,width,height;10 public void draw(Canvas c){...}11 }12 13 //原始版本14 public void drawAll(List<Shape> shapes){15 for(Shapes :shapes){16 s.draw(this);17 }18 }19 20 //使用边界通配符的版本21 public void drawALL(List<? extends Shape> shapes){22 for(Shapes :shapes){23 s.draw(this);24 }25 }
shapes.add(0, new Rectangle());//编译出错。
原因是:我们只知道shapes中的元素时Shapes类型的子类型。具体是什么子类不知道。所以不能加入任何类型的对象。不过我们在取出对象时,可以用Shape类型来取值,因为虽然不知道列表中元素是什么类型,但是它一定是Shape类的子类型。
2)?super通配符
这里还有一种边界通配符?super。如:
1 List<Shape> shapes=new ArrayList<Shape>();2 List<? super Cicle> cicleSupers=shapes;3 circleSupers.add(new Cicle());//OK,subclss of Cicle also OK4 cicleSupers.add(new Shape());//ERROR
3)边界通配符总结
<!--[if !supportLists]-->l <!--[endif]-->如果你想从一个数据类型里获取数据,使用 ? extends 通配符
<!--[if !supportLists]-->l <!--[endif]-->如果你想把对象写入一个数据结构里,使用 ? super 通配符
<!--[if !supportLists]-->l <!--[endif]-->如果你既想存,又想取,那就别用通配符。
5 泛型方法
考虑实现一个方法,该方法拷贝一个数组中的所有对象到集合中。下面是初始的版本。
1 static void fromArrayToCollection(Object[]a,Collection<?>c){2 for(Object o:a){3 c.add(o);//编译错误4 }5 }
1 static<T> void fromArrayToCollection(T[] a,Collection<T>c){2 for(T O:a){3 c.add(o);//OK4 }5 }
1 Object[] oa=new Object[100]; 2 Collection<Object>co=new ArrayList<Object>(); 3 fromArrayToCollection(oa,co);//T inferred to be Object 4 String[] sa=new String[100]; 5 Collection<String>cs=new ArrayList<String>(); 6 fromArrayToCollection(sa,cs);//T inferred to be String 7 fromArrayToCollection(sa,co);//T inferred to be Object 8 Integer[] ia=new Integer[100]; 9 Float[] fa=new Float[100];10 Number[] na=new Number[100];11 Collection<Number>cn=new ArrayList<Number>();12 fromArrayToCollection(ia,cn);//T inferred to be Number13 fromArrayToCollection(fa,cn);//T inferred to be Number14 fromArrayToCollection(na,cn);//T inferred to be Number15 fromArrayToCollection(na,co);//T inferred to be Object16 fromArrayToCollection(na,cs);//编译错误
1 public <T> void go(T t){ 2 System.out.println("generic function"); 3 } 4 public void go(String str){ 5 System.out.println("normal function"); 6 } 7 public static void main(String[] args){ 8 FuncGenric fg=new FuncGenric(); 9 fg.go("haha");//打印normal function10 fg.<String>go("haha");//打印normal function,String是多余的,并且不报错。11 fg.go(new Object());//打印generic function12 fg.<Object>go(new Object());//打印generic function13 }
6 需要注意的地方
1)方法重载
在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,确实可以的,如代码二,虽然形参经过类型擦除后都以List类型,但是返回类型不同,这是可以的。
1 //代码一:编译出错2 public class Erasure{3 public void test(int i){4 System.out.printlnl("String");5 }6 public void test(int i){7 System.out.println("Integer");8 }9 }
1 //代码二:正确2 public class Erasure{3 public void test(List<String>ls){4 System.out.println("String");5 } 6 public void test(List<Integer>li){7 System.out.println("Integer");8 }9 }
所有泛型类型的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList<String>和ArrayList<Integer>类型参数不同,但是它们都共享ArrayList类,所以结果会是true。
1 List<Sting>l1=mew ArrayList<String>();2 List<Integer>l2=mew ArrayList<Integer>();3 System.out.println(l1.getClass()==l2.getClass());//True
3)instanceof
不能对确切的泛型类型使用instanceof()操作,下面代码是违法的。
1 Collection cs=new CollectionList<String>();2 if (cs instanceof Collection<String>){....}//编译出错,如果改成instanceof Collection<?>则正确。
不能创建一个确切反省类型的数组,否则出错。
1 List<String>[] lsa=new ArrayList<String>[10];//错误。因为如果可以这样,那么考虑如下代码,会导致运行时错误。2 List<String>[] lsa=new ArrayList<String>[10];//实际上并不允许这样创建数组3 Object 0=lsa;4 Object[] oa=(Object[])o;5 List<Integer>li=new ArrayList<Integer>();6 li.add(new Integer(3));7 oa[1]=li;//unsound,but passes run time store check8 String s=lsa[1].get[0];//run-time error-classCastException
因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但倒数第二行代码必须显示的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。
1 List<?>[] lsa=new List<?>[10];2 Object 0=lsa;3 Object[] oa=(Object[])o;4 List<Integer>li=new ArrayList<Integer>();5 li.add(new Integer(3));6 oa[1]=li;/OK7 String s=lsa[1].get(0);//run-time error,but cast is explicit8 Integer it=(Integer)lsa[1].get(0);//OK
阅读全文
0 0
- 泛型学习总结
- c#泛型总结
- Java泛型总结
- 泛型总结
- c#泛型总结
- 泛型知识点总结
- 自定义泛型总结
- 泛型总结
- Java泛型总结
- JAVA泛型总结
- 泛型算法总结
- 泛型总结 day11
- Java泛型总结
- 泛型学习总结
- java 泛型总结
- java泛型总结
- 泛型的总结
- 泛型总结
- String的replzce源码解析
- class.getResources()和classLoader.getResources()
- linux网络编程--TCP分包 粘包 MTU 和MSS之间的关系分析
- Codeforces Round #427 (Div. 2) C. Star sky
- Java多线程(学习篇)
- 泛型总结
- eclispe转 idea 快捷键设置(包括文件搜索运用)修改插入和改写模式
- 顺序结构,选择结构,循环结构的概念,用法,实例
- I/O流
- height、clientHeight、scrollHeight、offsetHeight区别
- Intellij IDEA设置显示行号
- Vue.js中的图片引用路径的方式
- Recycler缓存机制
- 什么是软件测试?