一、泛型简单介绍
1.泛型
JDK1.5版本出现的新特性,用于解决安全问题。是一个类型安全机制。
ArrayList<String> al = new ArrayList<String>();
// 定义一个 ArrayList 的容器,这个容器中元素类型是 String 。
泛型可以定义在类上、方法上、接口上、集合(对象)上
2.优点
1) 将运行时期出现的问题 ClassCastException (类型 转换异常),转移到了编译时期。方便于程序员解决问题。让运行时问题减少,安全。
2 ) 避免了强制转换麻烦。
3.格式
通过<>来定义要操作的引用数据类型。
eg:ArrayList<String> al = new ArrayList<String>;
4.在使用java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,
只要见到 <> 就要定义泛型。 其实 <> 就是用来接收类型的。
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
eg:
class GenericDemo { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<String>(new LenComparator()); //需要 按长度排序 ts.add("abcd"); ts.add("cc"); ts.add("cba"); ts.add("aaa"); ts.add("z"); ts.add("hahaha"); Iterator<String> it = ts.iterator(); while(it.hasNext()) { String s = it.next(); System.out.println(s); } } } class LenComparator implements Comparator<String> { public int compare(String o1,String o2) { int num = new Integer(o2.length()).compareTo(new Integer(o1.length())); if(num==0) return o2.compareTo(o1); return num; } }
5.泛型类
简单理解,类上带泛型。定义在类名后。
泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
什么时候定义泛型类?
当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展。
eg:
class Worker { } class Student { //泛型类。 class Utils<QQ> { private QQ q; public void setObject(QQ q) { this.q = q; } public QQ getObject() { return q; } } class GenericDemo { public static void main(String[] args) { Utils<Worker> u = new Utils<Worker>(); u.setObject(new Student()); Worker w = u.getObject();; } }
6.泛型方法
定义 在修饰符 (包括static )后, 返回值 类型前
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
静态 方法泛型 :
静态方法不可以访问类上定义的泛型。
如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
eg:
class Demo<T> { public void show(T t) { System.out.println("show:"+t); } public <Q> void print(Q q) { System.out.println("print:"+q); } public static <W> void method(W t) { System.out.println("method:"+t); } } class GenericDemo { public static void main(String[] args) { Demo <String> d = new Demo<String>(); d.show("haha"); //d.show(4); d.print(5); d.print("hehe"); Demo.method("hahahahha"); } }
7.泛型接口
定义在接口名后。实现也在接口名后。
eg:
//泛型定义在接口上。 interface Inter<T> { void show(T t); } class InterImpl<T> implements Inter<T> { public void show(T t) { System.out.println("show :"+t); } } class GenericDemo { public static void main(String[] args) { InterImpl<Integer> i = new InterImpl<Integer>(); i.show(4); } }
8.泛型限定
泛型 限定: (高级 应用 :用于 泛型扩展 。 )
泛型 的通配符为 ? 。也可以理解为占位符。
类型不明确时使用。
不能使用类型特有方法。不可以使用length()方法。
T 代表 具体类型,可以接收并操作这个类型。
? extends E: 可以接收 E 类型或者 E 的子类型。上限。
? super E: 可以接收 E 类型或者 E 的父类型。下限。
eg:
以下是错误的写法。集合(对象)的泛型的类型左右两边要一致。通过上限或者下限来解决。
前提:自定义类Student extends Person
ArrayList<Person> al1 = new ArrayList<Student>();
TreeSet< Student > al = new TreeSet <Person>();
示例代码:
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 GenericDemo { 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); //ArrayList<? extends Person> al = new ArrayList<Student>(); error } public static void printColl(Collection<? extends Person> al) { Iterator<? extends Person> it = al.iterator(); while(it.hasNext()) { System.out.println(it.next().getName()); } } /* public static void printColl(ArrayList<?> al)//ArrayList al = new ArrayList<Integer>();error { Iterator<?> it = al.iterator(); while(it.hasNext()) { System.out.println(it.next().toString()); } } */ }
二、深入理解泛型
1.深入理解泛型
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,eg:用反射得到集合,再调用其add方法即可。
ArrayList<E> 类定义和 ArrayList<Integer> 类引用:
1 ) 整个称为ArrayList<E> 泛型类型
2 ) ArrayList<E>中的 E 称为 类型变量或类型参数
3)ArrayList<Integer>称为 参数化类型
4)ArrayList<Integer>中 Integer 称为 类型参数的实例或实际类型参数
5 )<>读typeof
6)ArrayList称为原始类型。
2.泛型注意事项
1) 参数化类型与原始类型兼容。
eg :
Collection<String> c = new Vector();//编译报告警告,但不是错误。
Collection c = new Vector<String>();//编译报告警告,但不是错误。
2 )参数 化类型不考虑类型参数的继承关系。
eg Vector <String> v = new Vector <Object> (); //错误。
Vector <Object> v = new Vector <String> (); //错误。
3)编译器 不允许 创建泛型变量的 数组 。即在创建数组实例时,数组的元素不能使用参数化的类型
e g : Vector <Integer> vectorList [] = new Vector <Integer>[10] ; // 错误
4) 易错示例:
e g : Vector v1 = new Vector<String>(); //正确 的
Vector<Object> v = v1;
//正确的。因为编译器是一行一行读代码。v1是是原始类型的,参数化类型与原始类型兼容。
3.通配符
3.1 通配符
通配符:?,表示任意类型。
Note: 使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
(即:当使用泛型?的集合作为参数在方法上传递时,在该方法中不能调用与参数类型有关的方法 。比如:add()方法不可以调用。而size()可以被调用。)
3.2 限定通配符
限定通配符:限定通配符总是包括自己。?只能用作引用,不能给其他变量赋值。
限定通配符的上边界:
eg : Vector <? extends Number> x = new Vector <Integer> (); //正确
Vector <? extends Number> x = new Vector <String> (); // 错误
限定通配符的下边界:
eg : Vector <? super Integer> x = new Vector <Number> (); //正确
Vector <? super Integer> x = new Vector <Byte> (); // 错误
(Number类 是Integer的父类。)
Note :
1) 只有引用类型才能作为泛型方法的实际参数。
2 ) 除了在应用泛型时可以使用 extends 限定符,在定义泛型时也可以使用 extends 限定符,并且可以用 & 来指定多个边界。
3 ) 普通方法、构造方法和静态方法中都可以使用泛型。
4) 在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分隔。
5 ) 可以用类型变量表示异常,称为参数化的异常,可以用于方法的 throws 列表中,但是不能用于 catch 子句中。
6)通配符方案要比范型方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系时,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用范型方法。
4.类型推断
4.1 类型参数的类型推断
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1) 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即 直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型
eg : swap(new String[3],3,4)
--> static <E> void swap(E[] a, int i, int j)
2) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来。
eg : add(3,5)
--> static <T> T add(T a, T b)
3) 当某个类型变量在整个参数列表中的所有参数和返回值中的 多处被应用 了,如果调用方法时这多处的实际应用类型 对应到了不同的类型 ,且 没有 使用 返回值 ,这 时 候 取多个参数中的最大交集类型 ,例如,下面语句实际对应的类型就是 Number 了,编译没问题,只是运行时出问题:
eg : fill(new Integer[3],3.5f)
--> static <T> void fill(T[] a, T v)
4) 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是 Integer 了,编译将报告错误,将变量 x 的类型改为 float ,对比 eclipse 报告的错误提示,接着再将变量 x 类型改为 Number ,则没有了错误:
eg : int x =(3,3.5f)
--> static <T> T add(T a, T b)
5) 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为 Object ,编译没有问题,而第二种情况则根据参数化的 Vector 类实例将类型变量直接确定为 String 类型,编译将出现问题:
eg : copy(new Integer[5],new String[5])
--> static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5])
--> static <T> void copy(Collection<T> a , T[] b);
4.2 JDK 8 改进了泛型的目标类型推断
Java 8 中泛型的目标类型推断主要有2个方面:
1)支持通过方法上下文推断泛型目标类型
2)支持在方法调用链路当中,泛型类型推断传递到最后一个方法
提升了Java编译器推断泛型和在泛型方法调用中减少显式类型参数的能力。
JDK8以前的写法:
foo(Utility.<Type>bar()); Utility.<Type>foo().bar();
JDK8改进后的写法:(参数和调用链推断)
foo(Utility.bar()); Utility.foo().bar();
Note:其实泛型目标类型推断改进主要是在使用Lambda表达式时用来推断合法的Lambda表达式的类型的上下文 。
上面内容只是简单介绍,详情请点击《JDK8新特性:泛型的目标类型推断》
http://blog.csdn.net/sun_promise/article/details/51323241
5.定义泛型类型
1) 如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
2) 类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
N ote :
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
6.通过反射获得泛型的参数化类型