学习集合框架相关内容之前还是要把泛型好好看下,要不各种源代码看得就很难受了,一遇到<? ><T> 这样的一些表述就头大了,这部分可结合着集合的相关内容一起了解。
泛型基本概念(Genetics)
就像圣思园视频里讲的,用一句比较好的话解释就是:变量类型的参数化。泛型基本思想与C++的模板中的思想比较类似,但是在还有一些区别的比如具体的实现方式上。
使用集合的时候比如按照下面的没有用泛型的方式(其实是<? Extends Object>):
List list=new ArrayList();
List.add(“abc”);list.add(new Integer(1));list.add(new Boolean(true))
String str=(String)list.get(0);Integer a=(Integer)list.get(1);String str2=(String)list.get(2)
上面的例子在编译的时候不会报错,在运行的时候会报错,因为index=2位置上的元素是一个Boolean类型的,这里却要强制转化成一个String类型的。
这就是之前的弊端,因为所有的类全是Object的类型的子类,因此全当做Object类型来处理,但是在取出的时候恢复到原来的类型就遇到了问题,需要进行强制的转换,这样操作虽然可以但是不能保证安全性,就像上面的例子那样。
可以说泛型的一个重要作用就是为了避免强制类型的转换,定义的时候逻辑与之前的相同,涉及到具体的类型信息的时候,用一个符号来代替(所谓类型的参数化),不变应万变。
比如下面一个简单的泛型例子:
package com.test.Genetics; class Genetics<T>{ //这里的T 可以看做是表示类型的参数 //代码中遇到类型的部分 就用T来替代就好 //T看做是一个变量 具体传入的类型只有在运行的时候才能知道 public T foo; public T getFoo() { return foo; } public void setFoo(T foo) { this.foo = foo; } } public class testGenetics <T> { public static void main(String[] args) { Genetics<Boolean> foo1=new Genetics<Boolean>(); Genetics<Integer> foo2=new Genetics<Integer>(); foo1.add("abc"); } }
可以看到上面的例子,在实际生成类的实例的时候要预先把类型信息填入其中,这样要是add了其他的类型的实例进去就会报错了,foo1.add(“abc”)这句在编译的时候就会报错,而不会等到运行的时候。实际的集合框架都是通过泛型来实现的,这样在声明的时候就要指定好类型信息,在取出的时候也不用再进行强制的类型转换了。
这个是传入两个泛型参数的例子:
package com.test.Genetics; //这个类里面有两个通过泛型表示的类型变量 public class testGeneticb <T1,T2> { private T1 foo1; private T2 foo2; public T1 getFoo1() { return foo1; } public void setFoo1(T1 foo1) { this.foo1 = foo1; } public T2 getFoo2() { return foo2; } public void setFoo2(T2 foo2) { this.foo2 = foo2; } public static void main(String[] args) { testGeneticb<Integer,Boolean> foo=new testGeneticb<Integer,Boolean>(); foo.setFoo1(10); foo.setFoo2(new Boolean(false)); System.out.println("foo1 is "+foo.getFoo1() +" foo2 is " + foo.getFoo2()); } }
下面这个是用泛型实现的一个简单集合,功能比较简单,主要是为了演示对于泛型的操作,注意声明泛型类的构造方法的时候不用加上<T>:
package com.test.Genetics; public class simpleCollection <T>{ private T[] objArr; private int index=0; public T[] getObjArr() { return objArr; } public void setObjArr(T[] objArr) { this.objArr = objArr; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } //默认参数的时候 数组空间大小为10 public simpleCollection(){ this.objArr=(T[])new Object[10]; } //泛型类的构造方法不用加<>标记了 public simpleCollection(int capacity) { //注意无法直接创建泛型数组 要先创建Object类型的数组之后再强制转换过来 this.objArr=(T[])new Object[capacity]; } //这里形参是一个T类型的引用 t public void add(T t){ this.objArr[index++]=t; } public int getLength(){ return this.index; } public T get(int i){ return this.objArr[i]; } public static void main(String[] args) { int i; simpleCollection<Integer> c=new simpleCollection<Integer>(); //存入元素再打印出来 for(i=0;i<5;i++){ c.add(i); } System.out.println("the length is "+c.getLength()); for(i=0;i<5;i++){ Integer in=c.get(i); System.out.println(in); } } }
下面这个例子用于演示泛型的嵌套
package com.test.Genetics; class tmpGenetics<T>{ private T foo; public T getFoo() { return foo; } public void setFoo(T foo) { this.foo = foo; } } public class wrapperFoo <T>{ private tmpGenetics<T> tmpfoo; public tmpGenetics<T> getTmpfoo() { return tmpfoo; } public void setTmpfoo(tmpGenetics<T> tmpfoo) { this.tmpfoo = tmpfoo; } public static void main(String[] args) { tmpGenetics<Integer>foo=new tmpGenetics<Integer>(); foo.setFoo(10); wrapperFoo<Integer>wrapper=new wrapperFoo<Integer>(); wrapper.setTmpfoo(foo); //将之前存进去的foo对象拿出来 赋给一个新的tmpGenetics<Integer> tmpGenetics<Integer>outfoo=wrapper.getTmpfoo(); System.out.println(outfoo.getFoo()); } }
下面这个主要演示Map框架中泛型的基本使用以及迭代器的时候泛型的使用(这个可以结合集合框架的那一部分对于Map中的entry的介绍具体来看)
package com.test.Genetics; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class testMap { public static void main(String[] args) { Map<String,String> map=new HashMap<String,String>(); map.put("a", "aa"); map.put("b", "bb"); map.put("c", "cc"); map.put("d", "dd"); System.out.println("using first kind iterator way"); Set<String>keyset=map.keySet(); for(Iterator<String>iter=keyset.iterator();iter.hasNext();){ String key=iter.next(); String value=map.get(key); System.out.println("the key is "+key+"the value is "+value); } //采用第二种方式迭代输出 //注意这里的set声明的写法 entry也是一个泛型类(是Map集合中的静态内部类) 其中包含了key value System.out.println("using second kind iterator way"); Set<Entry<String, String>>entryset=map.entrySet(); for(Iterator<Map.Entry<String, String>>iter=entryset.iterator();iter.hasNext();){ Map.Entry<String, String>entry=iter.next(); String key=entry.getKey(); String value=entry.getValue(); System.out.println("the key is "+key+"the value is "+value); } } }
关于限制泛型的作用类型
在实际的开发中要是对泛型的具体实现有限制怎么处理?
比如像下面这种类型声明:
public class Genetics<T extends someclass>这种是固定的语法,其中somclass这个地方表示希望限制的类的名字,即使泛型类在实际声明的时候,<>中只能是这个类及其子类的类型名称。比如<T extends List> 那么生成实例的时候HashMap就不能往<>中放了。
实际上直接声明的<T>相当于<T extends Object>因此在默认的情况下,任何类型都可以作为类型参数,在声明一个泛型类的实例的时候放在<>中。
还有一种情况比如希望声明一个泛型类的引用foo可以实现以下方式:
foo = new Genetics<ArrayList>;
foo = new Genetics<LinkedList>;
即是说foo这个引用不是那种一一对应的关系,声明一个泛型类的引用之后可以将这个引用指向多个泛型类的实例,这就需要用到通配符的方式即
public class Genetics<? extends List> { …}
这样生成引用 Genetics<? extends List> foo之后,foo可以指向类型参数为List或其子类的不同类型的实例。还可以通过<? extends List> 这种方式声明表示继承结构在List上面的,实际中用到的比较少。
注意两种限制方式的区别:
第一种在声明泛型引用的时候就定死了,是一一对应的关系,某种特定类型的引用只能指向对应的特定类型的泛型实例。
第二种是声明引用的时候没有定义好,或者是局部限定范围,是一对多的关系,某种引用可指向限制范围内的多个特定类型的泛型实例。第二种方式仅仅是在声明一个引用的时候才用到的,在声明泛型类的时候还是用之前的方式。
使用通配符的方式要注意的一点是,这种方式意味着只能通过这个引用来取得或者是移除(设为null)实例中的某些信息,但不能增加修改信息,因为只知道这个引用指向的是somclass的子类,但是具体指向的是哪种类型在编译期间并不知道,因此编译器不能让修改操作发生,若是可以发生,就是在编译之前已经知道了具体指向的是哪个类,这个就和泛型的初衷相违背了,比如下面这个例子:
package com.test.Genetics; class tmpGeneticsb<T>{ private T str; public T getStr() { return str; } public void setStr(T str) { this.str = str; } } public class testExtends { public static void main(String[] args) { tmpGeneticsb<String>ge=new tmpGeneticsb<String>(); ge.setStr("abc"); //注意采用通配符的方式 仅仅是在声明指向泛型类的引用的时候才用到的 //声明一个泛型类的时候并不能这样使用 //<? extends Object>表示了这个引用ge2可以指向所有类型的泛型实例 tmpGeneticsb<? extends Object>ge2; ge2=ge; //下面的操作就会无法通过编译 //ge2.setStr("cde"); } }