一、泛型的基本入门
泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map类允许您向一个Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如String)的对象。
因为Map.get()被定义为返回Object,所以一般必须将Map.get()的结果强制类型转换为期望的类型,如下面的代码所示:
要让程序通过编译,必须将get()的结果强制类型转换为String,并且希望结果真的是一个String。但是有可能某人已经在该映射中保存了不是String的东西,这样的话,上面的代码将会抛出ClassCastException。
理想情况下,您可能会得出这样一个观点,即m是一个Map,它将String键映射到String值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。
二、泛型深入接触
先贴段代码来分析一下:
输出结果为:abc
(1)因为泛型定义是给编译器看的,所以当编译器检查完毕后我们可以利用反射绕过泛型检查,来向集合中注入值。通过这里我们可以看出java虚拟机在编译的时候并不知道其定义的类型。好的,接着向下看
下面再来贴一段代码:
输出结果已在上面标注出来了。那大家知道这其中原理吗?
(2)在java编译器编译完成之后,会去掉泛型中规定的类型信息,两个对象是完全一模一样的,也就是他们的编译后的class字节码是同一份。
下面来看一下泛型中的几个特性:
1、参数化的类型与原始类型的兼容性:
(1)参数化的类型可以引用一个原始类型的对象,编译报告警告 例如:
Collection<String> c = new Vector();
(2)原始类型可以引用一个参数化类型的对象,编译报告警告 例如
Collection c = new Vector<String>();
2、参数化类型不考虑类型参数的继承关系
(1)Vector<String> v = new Vector<Object>();//错误
(2)Vector<Object> v = new Vector<String>();//也错误
3、参数化类型的对象只允许是引用类型的对象,基本类型(装箱/拆箱成引用类型)
(1)Vector<Integer> v = new Vector<Integer>();
v.add(2); //2被自动装箱成Integer类型的对象
4、在创建数组实例时,数组的元素不能使用参数化的类型,例如:下面的语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];//错误
通过上面的几点介绍:下面我们来一个思考题:
思考题:下面的代码会报错吗?
(1)Vector v1 = new Vector<String>();
(2)Vector<Object> v = v1;
//答案是不会的,因为(1)处是将原始类型引用一个参数化类型的对象,(2)处是参数化类型引用一个原始类型的对象,在这里千万不要混淆了。
三、泛型的通配符扩展
1、通配符的基本应用
先来看个问题:定义一个方法,该方法用于打印出任意参数化类型集合中的所有数据,该方法该如何定义?
也许我们会想到用下面的这种方式:(错误)
但是这种方式是错误,无法实现上面的功能的,原因很简单,因为泛型中是不支持继承关系
的,比如Collection<Integer> c1 与Collection<Object> c2 两个不同类型的对象。因此是不可以用Ojbect来代替所有类型的。
下面来看使用泛型中通配符的方式:
在上面的方法中,通过一些方法的调用,大家肯能也会发现了,使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要作用是做引用可以调用与参数化无关的方法,不可以调用与参数化有关的方法。
2、泛型中的?通配符的扩展
先来阐述一下Number类,它 是 BigDecimal、BigInteger、Byte、Double、Float、Integer、Long 和 Short 类等八种基本类型的父类。
(1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符总是包括自己
四、自定义泛型方法
1、基本应用
(1)根据自己的理解,简单写个测试例子,判断两个数是否相等
调用方法:
在这里的参数1,1分别是int类型的,但是在真正传值的时候是传的Integer类型的(自动装箱)
(2)交换任意给定类型数组的两个元素的顺序:
调用代码段:
总结:除了在应用泛型的时候可以用extends限定符,在定义泛型时也可以使用extends限定符
例如:Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,
如<V extends Serializable & cloneable> void method(){}
1、普通方法,构造方法和静态方法中都能使用泛型。
2、也可以用类型变量表示异常,成为参数化的异常,可以用于方法的throws列表中,但是不能用于catch
字句中。
3、在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分隔,例如:
下面利用泛型在来做一下上面我们所做过的例子:打印出任意参数化集合类型中的内容
2、类型推断
a、当某个类型只在整个参数列表中的所有参数和返回值中的一处被应用了。那么根据调用方法时该处的实际应用类型来确定
eg.swap(new String[3],3,4)----> static <E> void swap(E[],int i,int j)
b、当某个类型变量在某个参数列表中的所有参数和返回值中的多次被应用了。如果调用方法时这多处的实际应用类型都对应同一种类型类确定
eg.add(2,4)----->static <T> T add(T a, T b)
c、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且没有返回值,这时取这多个参数中的最大交集类型
eg. fill(new Integer[3],1.3f)----> static <T> void fill(T[] a,T v)
d、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且有返回值,这时根据其返回值的类型
eg. int x = add(3,2.3f)-----> static <T> T add(T a, T b)
f、参数类型推断具有传递性
eg.copy(new Integer[5],new String[5]) -----> static <T> void copy(T[] a,T[] b)
//编译通过,类推断实际类型参数为Object
copy(new Vector<String>(),new Integer[5])------> static <T> void copy(Collection<T> a, T[] b)
//编译出问题,原因是其根据参数化得Vector实例将变量类型直接确定为String,所以在Integer——>复制时将出现问题
五、自定义泛型类
当一个变量或方法被声明为泛型的时候,只能被实例变量和方法调用,而不能被静态变量和静态方法调用。因为静态成员变量是被所有参数化的类的对象所共享的。
最后我们来简单研究一下,java到底如何通过泛型得到已知参数类型的?
如:Vector<Date> v1 = new Vector<Date>();
下面我们如何通过反射来得到其v1对象中泛型参数类型呢?
首先我们来否定一种情况,取得class文件得到其对应的泛型参数是行不通的,原因他们是同一份字节码,所以无法实现,
下面我们就转变一下思想,我们可以通过反射得到某个方法中,同样也可以得到某个方法的参数、参数类型...那么我们就假设有个一个方法,并且参数为我们所要得到的定义泛型的参数类型对象,定义方法如下:
下面就通过反射来得到其参数的泛型定义的类型
具体代码如下:
泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map类允许您向一个Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如String)的对象。
因为Map.get()被定义为返回Object,所以一般必须将Map.get()的结果强制类型转换为期望的类型,如下面的代码所示:
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");
要让程序通过编译,必须将get()的结果强制类型转换为String,并且希望结果真的是一个String。但是有可能某人已经在该映射中保存了不是String的东西,这样的话,上面的代码将会抛出ClassCastException。
理想情况下,您可能会得出这样一个观点,即m是一个Map,它将String键映射到String值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。
Map<String, String> m = new HashMap<String, String>();
m.put("key", "blarg");
m.put("age",100);// 错误
String s = m.get("key");
二、泛型深入接触
先贴段代码来分析一下:
public static void main(String[] args) throws Exception {
//利用泛型规定只允许装Integer类型的对象
ArrayList<Integer> arrayAttr = new ArrayList<Integer>();
//利用反射向arrayAttr中加入String对象
arrayAttr.getClass().getMethod("add", Object.class).invoke(arrayAttr, "abc");
//依然能够正确的执行
System.out.println(arrayAttr.get(0));
}
输出结果为:abc
(1)因为泛型定义是给编译器看的,所以当编译器检查完毕后我们可以利用反射绕过泛型检查,来向集合中注入值。通过这里我们可以看出java虚拟机在编译的时候并不知道其定义的类型。好的,接着向下看
下面再来贴一段代码:
ArrayList<Integer> arrayAttr1 = new ArrayList<Integer>();
ArrayList<String> arrayAttr2 = new ArrayList<String>();
System.out.println(arrayAttr1.getClass() == arrayAttr2.getClass());//true
输出结果已在上面标注出来了。那大家知道这其中原理吗?
(2)在java编译器编译完成之后,会去掉泛型中规定的类型信息,两个对象是完全一模一样的,也就是他们的编译后的class字节码是同一份。
下面来看一下泛型中的几个特性:
1、参数化的类型与原始类型的兼容性:
(1)参数化的类型可以引用一个原始类型的对象,编译报告警告 例如:
Collection<String> c = new Vector();
(2)原始类型可以引用一个参数化类型的对象,编译报告警告 例如
Collection c = new Vector<String>();
2、参数化类型不考虑类型参数的继承关系
(1)Vector<String> v = new Vector<Object>();//错误
(2)Vector<Object> v = new Vector<String>();//也错误
3、参数化类型的对象只允许是引用类型的对象,基本类型(装箱/拆箱成引用类型)
(1)Vector<Integer> v = new Vector<Integer>();
v.add(2); //2被自动装箱成Integer类型的对象
4、在创建数组实例时,数组的元素不能使用参数化的类型,例如:下面的语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];//错误
通过上面的几点介绍:下面我们来一个思考题:
思考题:下面的代码会报错吗?
(1)Vector v1 = new Vector<String>();
(2)Vector<Object> v = v1;
//答案是不会的,因为(1)处是将原始类型引用一个参数化类型的对象,(2)处是参数化类型引用一个原始类型的对象,在这里千万不要混淆了。
三、泛型的通配符扩展
1、通配符的基本应用
先来看个问题:定义一个方法,该方法用于打印出任意参数化类型集合中的所有数据,该方法该如何定义?
也许我们会想到用下面的这种方式:(错误)
public static void printCollection(Collection<Object> coll) {
for(Object o : coll) {
System.out.println(o);
}
}
但是这种方式是错误,无法实现上面的功能的,原因很简单,因为泛型中是不支持继承关系
的,比如Collection<Integer> c1 与Collection<Object> c2 两个不同类型的对象。因此是不可以用Ojbect来代替所有类型的。
下面来看使用泛型中通配符的方式:
public static void printCollection(Collection<?> coll) {
//coll.add("abc");//不可以:因为你不知道<?>表示的是什么类型
//coll.size();//可以,因为这个方法和具体的参数没有关系
//coll = new HashSet<Date>()是可以的,因为你是将一个不确定参数类型变量指向一个Date参数类型对象
for(Object o : coll) {
System.out.println(o);
}
}
在上面的方法中,通过一些方法的调用,大家肯能也会发现了,使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要作用是做引用可以调用与参数化无关的方法,不可以调用与参数化有关的方法。
2、泛型中的?通配符的扩展
先来阐述一下Number类,它 是 BigDecimal、BigInteger、Byte、Double、Float、Integer、Long 和 Short 类等八种基本类型的父类。
(1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符总是包括自己
//通过利用泛型练习一下map类型集合的遍历方式
Map<String, Integer> maps = new HashMap<String, Integer>();
maps.put("lzh", 22);
maps.put("ghl", 21);
maps.put("cyf", 33);
//第一种遍历方式,遍历map中entrySet集合
Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();
for(Map.Entry<String, Integer> entry : entrySet){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
//第二种遍历方式,遍历map中keKey集合
Set<String> keySet = maps.keySet();
for(String key : keySet){
System.out.println(key + ":" + maps.get(key));
}
//第三种遍历方式,遍历map中values集合,但是不能遍历键值
Collection<Integer> values = maps.values();
for (Iterator iterator = values.iterator(); iterator.hasNext();) {
Integer value = (Integer) iterator.next();
System.out.println(value);
}
四、自定义泛型方法
1、基本应用
(1)根据自己的理解,简单写个测试例子,判断两个数是否相等
public static <T>T eq(T x, T y){
if(x.equals(y)){
return x;
}
return y;
}
调用方法:
在这里的参数1,1分别是int类型的,但是在真正传值的时候是传的Integer类型的(自动装箱)
int vs1 = eq(1, 1);//---1
Number vs2 = eq(1, 1.0);//---2
Object vs3 = eq(1, '1');//---3
Object vs4 = eq(1, "1");//---4
Number vs5 = eq(1, 1L);//---5
//通过上面的例子,我们可以看出在返回值的数据类型是两个参数类型的交集,
//就是这个类型必须把传递的两种参数的类型都包括了(类型推断)
(2)交换任意给定类型数组的两个元素的顺序:
public static <T>void swap(T[] a, int i, int j) {
T temp = a[i];
a[i] = a[j];
a[j] = temp;
System.out.println(Arrays.asList(a));
}
调用代码段:
swap(new String[]{"abc", "lzh","xyz"}, 1,2);
//swap(new int[]{1,2,3},1,2);//这里会报错,这是什么原因呢?
//原因是只有引用类型才能作为泛型方法的实际参数,在这里java并不会帮我们自动装箱,Int[]数组本身就是个对象
总结:除了在应用泛型的时候可以用extends限定符,在定义泛型时也可以使用extends限定符
例如:Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,
如<V extends Serializable & cloneable> void method(){}
1、普通方法,构造方法和静态方法中都能使用泛型。
2、也可以用类型变量表示异常,成为参数化的异常,可以用于方法的throws列表中,但是不能用于catch
字句中。
private static <T extends Exception> sayHello() throws T {
try {
//不能写catch(T e);
} catch(Exception e) {
throw (T)e;
}
}
3、在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分隔,例如:
public static<K,V> V getValue(return map.get(key));
下面利用泛型在来做一下上面我们所做过的例子:打印出任意参数化集合类型中的内容
public static <T>void printCollection2(Collection<T> coll, T obj2) {
for(Object obj : coll) {
System.out.println(obj);
}
coll.add(obj2);//只是告诉大家这里可以通过泛型进行此操作
}
2、类型推断
a、当某个类型只在整个参数列表中的所有参数和返回值中的一处被应用了。那么根据调用方法时该处的实际应用类型来确定
eg.swap(new String[3],3,4)----> static <E> void swap(E[],int i,int j)
b、当某个类型变量在某个参数列表中的所有参数和返回值中的多次被应用了。如果调用方法时这多处的实际应用类型都对应同一种类型类确定
eg.add(2,4)----->static <T> T add(T a, T b)
c、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且没有返回值,这时取这多个参数中的最大交集类型
eg. fill(new Integer[3],1.3f)----> static <T> void fill(T[] a,T v)
d、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且有返回值,这时根据其返回值的类型
eg. int x = add(3,2.3f)-----> static <T> T add(T a, T b)
f、参数类型推断具有传递性
eg.copy(new Integer[5],new String[5]) -----> static <T> void copy(T[] a,T[] b)
//编译通过,类推断实际类型参数为Object
copy(new Vector<String>(),new Integer[5])------> static <T> void copy(Collection<T> a, T[] b)
//编译出问题,原因是其根据参数化得Vector实例将变量类型直接确定为String,所以在Integer——>复制时将出现问题
五、自定义泛型类
public class GenericDao<T> {
public void add(T obj) {
}
public T find(int id) {
return null;
}
}
当一个变量或方法被声明为泛型的时候,只能被实例变量和方法调用,而不能被静态变量和静态方法调用。因为静态成员变量是被所有参数化的类的对象所共享的。
public class GenericDao<T> {
//原始方法
public void add(T obj) {
}
//是错误的,
public static void add(T obj) {
}
//修正上面的方法;
public static <T> void add(T obj) {
}
}
最后我们来简单研究一下,java到底如何通过泛型得到已知参数类型的?
如:Vector<Date> v1 = new Vector<Date>();
下面我们如何通过反射来得到其v1对象中泛型参数类型呢?
首先我们来否定一种情况,取得class文件得到其对应的泛型参数是行不通的,原因他们是同一份字节码,所以无法实现,
下面我们就转变一下思想,我们可以通过反射得到某个方法中,同样也可以得到某个方法的参数、参数类型...那么我们就假设有个一个方法,并且参数为我们所要得到的定义泛型的参数类型对象,定义方法如下:
public static void applyVector(Vector<Date> v1) {
}
下面就通过反射来得到其参数的泛型定义的类型
具体代码如下:
public static void main(String[] args) {
//通过反射得到指定名称的方法对象
Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
//得到这个方法的"泛型的参数化类型"返回一个数组,因为这个方法可能有多个参数
Type[] types = applyMethod.getGenericParameterTypes();
//因为我们知道我们的方法中只有一个参数所以:types[0]
ParameterizedType pType =(ParameterizedType) types[0];
//得到第一个参数的参数化类型(可以简单理解为:就是泛型),是一个数组,因为可能有多个例如:Map<K,V>
System.out.println(pType.getActualTypeArguments()[0]);
//得到第一参数的真实类型:Vector
System.out.println(pType.getRawType());
}