黑马程序员——泛型
一、概述
1、JDK1.5版本以后出现的新特性。用于解决安全问题,是一个类型安全机制。
2、JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据。
3、泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
4、由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。
5、没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象,这样更安全;并且当从集合获取一个对象时,编译器也可以知道这个对象的类型,不需要对对象进行强制类型转换,这样更方便。泛型就是把原来的类名进行了延长!在JDK 1.5中,你还可以按原来的方式将各种不同类型的数据装到一个集合中,但编译器会报告unchecked警告。
6、格式:
通过<>来定义要操作的引用数据类型
如:ArrayList<String> //定义要存入集合中的元素指定为String类型
7、好处
a、将运行时期出现的问题ClassCastException,转移到了编译时期。方便于程序员解决问题。让运行时期问题减少、安全。
b、避免了强制转换的麻烦。如在实现某一个接口时,指定传入接口方法的实参的类型的话,在复写该接口方法时就可以直接使用指定类型,而不需要强制转换。
泛型研究:
只有类被定义成了泛型,才可以对其进行参数化应用。
下面程序的main方法中的第二行代码和注释中的两行代码表达的意思完全相同,注释中的两行代码不能通过编译,而第二行(采用方法调用链)却可以顺利通过编译。
- <span style="font-size:14px;">public class Test
- {
- public void func()
- {
- System.out.println("func");
- }
- public static void main(String args[]) throws Exception
- {
- Object obj = new Test();
- //下面这行可以成功编译
- ((Test)obj).getClass().newInstance().func();
- //下面这两行无法通过编译
- /*Class c = ((Test)obj).getClass();
- c.newInstance().func(); */
- }
- }
- </span>
因为Generic,编译器可以在编译期获得类型信息所以可以编译这类代码。将下面那两行改成:
Class<?extends Test> c = ((Test)obj).getClass();
c.newInstance().func();
就能通过编译了。
在JDK 1.5中引入范型后,Object.getClass()方法的定义如下:
public final Class<? extends Object>getClass()
这说明((Test)obj).getClass()语句返回的对象类型为Class<? extendsTest>,而Class<T>的newInstance()方法的定义如下:
public T newInstance() throwsInstantiationException,IllegalAccessException
即对于编译器看来,Class<Test>的newInstance()方法的对象类型为Test,而((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,所以,编译器认为((Test)obj).getClass().newInstance()返回的对象类型为Test。
下面这两行代码之所以无法通过编译
- <span style="font-size:14px;">Class c = ((Test)obj).getClass();
- c.newInstance().func();
- </span>
是因为((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,但是我们在第一行将结果强制转换成了Class,然后再去调用Class的newInstance方法,而不是去调用Class<Test>的newInstance方法,编译器当然不再认为Class的newInstance方法返回的对象为Test了。
二、泛型定义中的术语:
如:ArrayList<E>类和ArrayList<Integer>
1、ArrayList<E>整个称为泛型类型
2、ArrayList<E>中的E称为类型变量或类型参数
3、整个ArrayList<Integer>称为参数化类型
4、ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
5、ArrayList<Integer>中的<>称为typeof
6、ArrayList称为原始类型
参数化:parametered,已经将参数变为实际类型的状态。
三、在使用java提供的对象时,什么时候写泛型?
通常在集合框架中很常见,只要见到<>就要定义泛型。
其实<>就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
四、关于参数化类型的几点说明:
1、参数化类型与原始类型的兼容性
第一、参数化类型可引用一个原始类型的对象,编译只是报警告。
如:Collection<String>coll = new Vector();
第二、原始类型可引用一个参数化类型的对象,编译报告警告
如:Collectioncoll = new Vector<String>();
原来的方法接受一个集合参数,新类型也要能传进去。
2、参数的类型不考虑类型参数的继承关系:
Vector<String> v = newVector<Objec>();//错误的
不写Object没错,写了就是明知故犯
Vector<Objec> v = newVector<String>();//错误的
3、编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型
如:Vector<Integer>vectorList[] = new Vector<Integer>[10];//错误的
五、通配符
1、当传入的类型不确定时,可以使用通配符?。也可以理解为占位符。使用通配符的好处是可以不用明确传入的类型,这样在使用泛型类或者泛型方法时,提高了扩张性。
Collection<?> a可以与任意参数化的类型匹配,但到底匹配的是什么类型,只有以后才知道,所以:a=newArrayList<Integer>和a=new ArrayList<String>都可以,但a.add(new Date())或a.add(“abc”)都不行。
问题:
定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
错误方式:
- <span style="font-size:14px;">public static void printCollection(Collection<Object> cols) {
- for(Object obj:cols) {
- System.out.println(obj);
- }
- /* cols.add("string");//没错
- cols = new HashSet<Date>();//会报告错误!*/
- }
- </span>
正确方式:
- <span style="font-size:14px;">public static void printCollection(Collection<?> cols) {
- for(Object obj:cols) {
- System.out.println(obj);
- }
- //cols.add("string");//错误,因为它不知自己未来匹配就一定是String
- cols.size();//没错,此方法与类型参数没有关系
- cols = new HashSet<Date>();
- }
- </span>
总结:
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
2、泛型限定
对于一个范围内的一类事物,可以通过泛型限定的方式定义,有两种方式:
1)? extends E:可接收E类型或E类型的子类型;称之为上限。
如:ArrayList<? extends Number>x = new ArrayList<Integer>();
2)? super E:可接收E类型或E类型的父类型;称之为下限。
如:ArrayList<? super Integer>x = new ArrayList<Number>();
示例:
- <span style="font-size:14px;">import java.util.*;
- //人 父类
- class Person
- {
- private String name;
- Person(String name)
- {
- this.name = name;
- }
- public String getName()
- {
- return name;
- }
- }
- //学生 继承父类
- class Student extends Person
- {
- Student(String name)
- {
- super(name);
- }
- }
- class Demo
- {
- public static void main(String[] args)
- {
- ArrayList<Person> al = new ArrayList<Person>();
- al.add(new Person("abc1"));
- al.add(new Person("abc2"));
- al.add(new Person("abc3"));
- printColl(al);//父类对象的元素集合可以调用
- ArrayList<Student> al1 = new ArrayList<Student>();
- al1.add(new Student("abc--1"));
- al1.add(new Student("abc--2"));
- al1.add(new Student("abc--3"));
- printColl(al1); //子类对象的元素集合也可以调用
- }
- //定义一个上限的泛型方法
- public static void printColl(Collection<? extends Person> al)
- {
- Iterator<? extends Person> it = al.iterator();
- while(it.hasNext())
- {
- System.out.println(it.next().getName());
- }
- }
- }
- </span>
六、泛型类
1、若类实例对象中要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。
2、什么时候定义泛型类
当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展。
3、泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所以要操作的类型就已经固定了。
4、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String>dao = null;
newgenericDao<String>();
5、语法格式:
class Utils<XX>
{
private XX s;
public void setxx(XX s)
{
this.s=s;
}
public XX getXX()
{
return s;
}
}
注意:
1、在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
2、当一个变量被声明为参数时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。
七、泛型方法
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
如:
- <span style="font-size:14px;">class Demo<Q>
- {
- public void run(Q q){}
- public <T> void show(T t) {}
- public <E> void print(E t){}
- public static <W> void method(W t){}
- }
- </span>
其中方法中上的泛型可以不和类泛型相同。
特殊之处:
静态方法不可以访问类上定义的泛型。如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
泛型方法的特点:
1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
2、只有引用类型才能作为泛型方法的实际参数。
3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable& cloneable> void method(){}。
4、普通方法、构造函数和静态方法中都可以使用泛型。
5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。例如:
public static <K,V> V getValue(K key) { return map.get(key);}
八、看代码
1、
- <span style="font-size:14px;">public static<?> void printColl(ArrayList<?> al)
- {
- Iterator<?> it = al.iterator();
- while(it.hasNext())
- {
- System.out.println(it.next().toString());
- }
- }
- </span>
2、
- <span style="font-size:14px;">public static<T> void printColl(ArrayList<T> al)
- {
- Iterator<T> it = al.iterator();
- while(it.hasNext())
- {
- //T t=it.next();//T代表具体类型,可以操作和接收这个类型
- System.out.println(it.next().toString());
- }
- }
- </span>
上面两段代码中T和?有什么区别:
1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
2、?为通配符,也可以接收任意类型但是不可以定义变量。
但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。
通配符方案要比泛型方法更有效,当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用,而不是仅在签名的时候使用,才需要使用泛型方法。
小结:
对泛型的定义:
第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型。
第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类。
第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法。
九、类型参数的类型推断
1、编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
2、根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4)
——> static <E> voidswap(E[] a, int i, int j)
2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5)
——> static<T> T add(T a, T b)
3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f)
——> static<T> void fill(T[] a, T v)
4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
intx =(3,3.5f)
——> static<T> T add(T a, T b)
5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(newInteger[5],new String[5])
——> static<T> void copy(T[] a,T[] b);
copy(newVector<String>(), new Integer[5])
——> static<T> void copy(Collection<T> a , T[] b);
十、通过反射获得泛型的参数化类型
示例代码:
- <span style="font-size:14px;">Class GenericalReflection {
- private Vector<Date> dates = new Vector<Date>();
- public void setDates(Vector<Date> dates) {
- this.dates = dates;
- }
- public static void main(String[] args) {
- Method methodApply = GenericalReflection.class.getDeclaredMethod("applyGeneric", Vector.class);
- ParameterizedType pType = (ParameterizedType)(methodApply .getGenericParameterTypes())[0];
- System.out.println("setDates("
- + ((Class) pType.getRawType()).getName() + "<"
- + ((Class) (pType.getActualTypeArguments()[0])).getName()
- + ">)" );
- }
- }
- </span>
泛型DAO的应用:
- <span style="font-size:14px;">public abstract class DaoBaseImpl<T> implements DaoBase<T> {
- protected Class<T> clazz;
- public DaoBaseImpl() {
- Type type = this.getClass().getGenericSuperclass();
- ParameterizedType pt = (ParameterizedType) type;
- this.clazz = (Class) pt.getActualTypeArguments()[0];
- System.out.println("clazz = " + this.clazz);
- }
- }
- public class ArticleDaoImpl extends DaoBaseImpl<Article> implements ArticleDao {
- }
- </span>