关于泛型的基本使用
学习集合框架相关内容之前还是要把泛型好好看下,要不各种源代码看得就很难受了,一遇到<? ><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或其子类的不同类型的实例。还可以通过<? super List> 这种方式声明表示继承结构在List上面的,实际中用到的比较少。
注意两种限制方式的区别:
第一种在声明泛型引用的时候就定死了,是一一对应的关系,某种特定类型的引用只能指向对应的特定类型的泛型实例。
第二种是声明引用的时候没有定义好,或者是局部限定范围,是一对多的关系,某种引用可指向限制范围内的多个特定类型的泛型实例。第二种方式仅仅是在声明一个引用的时候才用到的,在声明泛型类的时候还是用之前的方式。
比如我声明一个泛型类
Class classA <T extends List>{}
之后我在main函数中生成这个类的引用的实例
classA<List> l=new classA<List>();(注意声明引用的时候写成classA<T extends List> l 这样是报错的 <>中只能写成实际的类型或者用通配符的那种表示方式)
l=new classA<LinkedList>();
这样显然就报错了。
如果我改成 classA<? extends List> l=new classA<List>(); l=new classA<LinkedList>();这样编译器就不会报错了。
还要注意一下,只是声明引用的时候才能用通配符的方式,声明一个泛型类的时候还要用原来的那种有参数的形式。
使用通配符的方式要注意的一点是,这种方式意味着只能通过这个引用来取得或者是移除(设为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"); } }
关于泛型方法:
之前还是忽略了关于泛型方法,做作业时候用到了,再补充一下,泛型方法不一定要定义在泛型类中,也可以是定义在普通的类中,类型变量要放在修饰符的后面,返回类型的前面。比如最基本的方式,像这样的声明:
public static <T> T getMiddle(T[]a){
return a[a.length/2]
}
其中<T>表示类型的变量,或者说是限制,这里一个<T>可以看成是<T extends Object>当然也可以有其他的限制,就像上面介绍的那样,比如要对继承变量加以限制,就继承对应的类,比如要在方法中实现比较的操作,就写成<T extends Comparable>(注意这里使用extends而不是implements这个是固定的语法),这样就限制了T应该是comparable的子类型,当然一个类型变量的限定可以有多个,比如<T extends Comparable&Serializable>
还要再补充一些
并不是只有集合的相关框架使用到了泛型,事实上java.lang.Class也是通过泛型来进行定义的,只不过平时用的时候不太在意,Class<T>这里面的这个泛型参数就表示Class对象所修饰的那个泛型类的类型,应该更多的还是出于对安全性的考虑。
比如Class<String> classtype=String.class;之后 classtype=Integer.class;这样的话编译器就肯定是报错了。如果不使用泛型的话,虽然不会报错,但是可能不太安全,比如Class classtype=String.class;classtype=Integer.class;这样编译器只会发出警告但是不会报错,但显然是不安全的使用方式。