jdk1.5新特性之泛型(三)
2015-08-09 21:20
671 查看
Jdk1.5新特性之泛型(三)
在前面两章介绍了泛型概念、使用泛型、自定义泛型等等,在这一章,我们会更加深入的剖析泛型机制。
1.通配符
1.1使用类型通配符
假设有一个方法的参数接受一个集合,但是该集合里面存入的对象是不确定的,那我们又该怎么办呢?
我们会想到直接这样定义:public void test(List c){}
是的,这样定义没有错,但是编译器会给我们一个警告,还有好的办法吗?
我们可能会这样想,既然集合存入的对象类型不确定,那所有的对象都属于Object,那么我们就会想到这样定义:public void test(List<Object> c ){}, 但是我们仔细想想真的可以吗,这样的定义表面上看起来没有问题,这个方法声明也没有问题。但是我们调用该方法传入实际参数值时就会出现问题了。
如果这样调用: List<String> list = new ArrayList<String>();
Test(list);
那么问题就来,编译器会报错,为什么?在前面,我们说过,参数化类型不考虑类型参数的继承关系,也就是说这样子调用,相当于
List<Object> c = new ArrayList<String>();这样子编译就不过了。
为了解决这样的问题,java定义了一个类型通配符,即<?>
上面代码可以改成:public void test(List<?> c){}
这个 ? 就表示通配符,它的元素类型可以匹配任意类型。
但是用 ? 还是有局限的,看一下代码:
看以上代码,您可能觉得有点不能理解,程序为什么会报错呢?
当我们试图给集合添加一个字符串对象时,编译器报错了。
这是因为使用通配符之后,我们并不知道list集合到底装的是什么类型,它可能是String类型,也可能是Integer类型等等,所以不能向其中添加对象。查看jdk帮助文档,我们可以知道,add方法有类型参数E作为集合的元素类型,所以我们传给add的参数必须是E类型对象或其子类型对象,但是我们不知道E是什么类型,所以无法添加对象进去,不过,有一个例外,就是null类型,因为它是任何引用类型的子类型。
那么在集合中有多少方法不能用呢?查看API,只要方法接受的参数是E类型的就都不能使用。
1.2 设定通配符的上限
上面刚讲到,通配符可以匹配任意类型,也就是说List<?>可以是任何泛型List的父类(虽然参数化类型没有继承关系,但可以类比)。但是,现在我们不想匹配任何泛型List,只想匹配某类或某类的子类泛型List。
比如说,我们需要把动物类或者动物类的子类装进List集合,那该怎么做呢?可能有些人会这样子:因为Animal(动物类)是Cat(猫类)的父类,那就直接把Cat类交给Animal类就可以了。但是这样做真的可以吗?代码如下:
package wj.wcj.testwildcard;
//--------------------①
import java.util.ArrayList;
import java.util.List;
class Animal{
private String name;
public Animal(){}
public Animal(String name){
this.name=name;
}
@Override
public String toString() {
return "动物类。。。"+name;
}
}
class Cat extends Animal{
private String name;
public Cat(String name){
this.name=name;
}
@Override
public String toString() {
return "猫类。。。"+name;
}
}
class Dog extends Animal{
private String name;
public Dog(String name){
this.name=name;
}
@Override
public String toString() {
return "狗类。。。"+name;
}
}
public class WildCardTest {
public static void main(String[] args) {
List<Animal> animal List = new Array<>();
animalList.add(new Animal("小青"));
animalList.add(new Animal("强子"));
printAnimal(animalList);
List<Cat> CatlList = new ArrayList<>();
CatlList.add(new Cat("小白"));
CatlList.add(new Cat("小喵喵"));
printAnimal(CatlList); ☜
/*
* 报的错误:
* The method printAnimal(List<Animal>) in the type
* WildCardTest is not applicable for the arguments
* (List<Cat>)
*/
}
//--------------------②
public static void printAnimal(List<Animal> l) {
for(Animal animal : l){
System.out.println(l+",");
}
}
}
可以看到程序在☜这个位置报错了。这是我们忽略了一个细节,即数化类型没有继承关系(jdk1.5新特性(一)有讲到),你怎么可以把List<Cat> 对象赋给List<Animal> 对象呢。
为了解决这样的需求,java泛型提供了中被限制的通配符。可以表示为 List<? extends Animal> ,这表示该集合可以接收Animal类以及Animal的子类对象。
下面把第②段代码改为:
public static void printAnimal(List<? extends Animal> l) {
for(Animal animal : l){
System.out.println(l+",");
}
}
上面的错误就会消失了。List<? extends Animal>中的 ? 号代表一个未知类型,同样对于List<? extends Animal>集合,我们不能对它进行与类型(类型参数是E的)相关的操作,如add方法等。
1.3 设定通配符的下限
通配符的下限和通配符的上限类似,对上一个案例可以表示为
List<? super Cat>,它表示这个List可以接受Cat类以及Cat类的父类对象。代码案例可以对上面代码进行修改,这里就不给出了。
2.泛型方法与方法重载
泛型可以指定通配符的上限,也可以指定通配符的下限。那么有两个方法,一个用上限表示,另一个用下限表示,这两个是方法的重载吗?
public class TestUtils {
/* 报的错误:
* Method copy(Collection<T>, Collection<? extends T>) has the same
* erasure copy(Collection<E>, Collection<E>) as
* another method in type TestUtils
*/
public static <T> void copy(Collection<T> dest,Collection<? extends T> src){
}
/* 报的错误:
* Method copy(Collection<? super T>, Collection<T>) has the same
* erasure copy(Collection<E>, Collection<E>) as
* another method in type TestUtils
*/
public static <T> T copy(Collection<? super T> dest,Collection<T> src){
}
}
虽然我的英语不怎么好,但是我还是理解了这个是说这两个方法是相同的。也就是说,这两个方法并不是重载。那怎么会这样呢,跟我想象的完全不一样。这就牵扯到下面要说的,泛型的擦除了。因为在编译时,编译器会去掉类型化,那么两个方法的参数完全一样了,即都是Collection对象类型。
3.泛型与数组
Java的泛型有一个重要的设计原则,就是如果一段代码在编译时没有产生:“[unchecked]未经检查的转换”警告,那么程序在运行时就不会发生类型转换异常。正是因为这个,所以数组元素的类型不能包含类型变量或类型形参,不过无上限的类型通配符是可以的。虽然不能创建ArrayList<Integer>[10]这样的数组,但是给出ArrayList<String>[]的声明还是可以的。
如果java支持创建这样的泛型数组,那么有这样的程序:
//声明并创建一个List数组,而每个List对象放的是字符串对象
List<String>[] lsa =new ArrayList<String>[10];
//把List数组转换为Object数组
Object[] obj=(Object[])lsa;
//声明并创建一个List集合,存放Integer对象
List<Integer> intList = new ArrayList<Integer>();
//给集合添加一个Integer对象
intList.add(new Integer(5));
//将List集合对象作为Object数组的第一个元素存入数组中
obj[0]=intList;
//取出lsa数组的第一个元素List集合,并取出集合中的第一个元素
String s = lsa[0].get(0); //
如果第一句是成立的话,那么当程序执行到最后一句话时,会出现
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,然而这么做违背泛型的设计原则。
Java允许创建无上限的通配符泛型数组,将上面代码改为:
//声明并创建一个List数组,而每个List对象放的是字符串对象
List<?>[] lsa = new ArrayList<?>[10];
//把List数组转换为Object数组
Object[] obj=(Object[])lsa;
//声明并创建一个List集合,存放Integer对象
List<Integer> intList = new ArrayList<Integer>();
//给集合添加一个Integer对象
intList.add(new Integer(5));
//将List集合对象作为Object数组的第一个元素存入数组中
obj[0]=intList;
//取出lsa数组的第一个元素List集合,并取出集合中的第一个元素
String s = (String)lsa[0].get(0);
可以看到在这时程序不得不进行强制类型转换(最后一句),最后一句运行时会引发类型转化异常。所以这是我们应该用instanceof运算符保证它的数据类型。
最后一句代码改为:
Object target=lsa[0].get(0);
if(target instanceof String){
String s = (String)target;
}
4.泛型与反射
4.1.Java在泛型出来时,反射包也被翻新了,来为参数化类型和方法提供参数信息。可以查看javaAPI:
4.2利用反射越过编译器对字节码操作
我们都知道如果一个集合声明并创建为这样:
List<String> listStr = new ArrayList<>();
那么我们就不能向List集合中添加Integer类型的对象了,但是真的一点办法都没有吗?当然不是,利用反射就可以做到,看以下代码:
List<Integer> listStr = new ArrayList<Integer>();
Class clazz = listStr.getClass();
Method m=clazz.getMethod("add",Object.class);
m.invoke(listStr, "java");
System.out.println(listStr.get(0));
这段代码最后打印的是字符串 java ,也就是说把字符串添加到了List<Integer> 集合里了。
这是怎么做到了呢?我们知道泛型的概念是在编译期存在的,在编译时编译器会将泛型擦除,所以在虚拟机中List<Integer>就变成了List原始类型了,当然原始类型的List可以存储任意对象。而反射就越过编译期,自然就不存在类型检查的问题了。
5.泛型的约束与局限性
(1)不能用基本类型实例化类型参数。也就是说没有List<int>,只有List<Integer> ,当然这原因就是类型擦除,擦除后List含有Object类型的域,而Object不能存int(当然自动封装是另一回事)。
(2)运行时类型查询只适用于原始类型。
(3)不能创建参数化类型数组。
(4)不能实例化类型变量。也就是说不能使用像new T(...),
new T[...]或T.class这样的表达式的类型变量。
(5)泛型类的静态上下文中类型变量无效,即不能在静态域或方法中引用类型变量。
(6)不能抛出或捕获泛型类的实例。
在前面两章介绍了泛型概念、使用泛型、自定义泛型等等,在这一章,我们会更加深入的剖析泛型机制。
1.通配符
1.1使用类型通配符
假设有一个方法的参数接受一个集合,但是该集合里面存入的对象是不确定的,那我们又该怎么办呢?
我们会想到直接这样定义:public void test(List c){}
是的,这样定义没有错,但是编译器会给我们一个警告,还有好的办法吗?
我们可能会这样想,既然集合存入的对象类型不确定,那所有的对象都属于Object,那么我们就会想到这样定义:public void test(List<Object> c ){}, 但是我们仔细想想真的可以吗,这样的定义表面上看起来没有问题,这个方法声明也没有问题。但是我们调用该方法传入实际参数值时就会出现问题了。
如果这样调用: List<String> list = new ArrayList<String>();
Test(list);
那么问题就来,编译器会报错,为什么?在前面,我们说过,参数化类型不考虑类型参数的继承关系,也就是说这样子调用,相当于
List<Object> c = new ArrayList<String>();这样子编译就不过了。
为了解决这样的问题,java定义了一个类型通配符,即<?>
上面代码可以改成:public void test(List<?> c){}
这个 ? 就表示通配符,它的元素类型可以匹配任意类型。
但是用 ? 还是有局限的,看一下代码:
看以上代码,您可能觉得有点不能理解,程序为什么会报错呢?
当我们试图给集合添加一个字符串对象时,编译器报错了。
这是因为使用通配符之后,我们并不知道list集合到底装的是什么类型,它可能是String类型,也可能是Integer类型等等,所以不能向其中添加对象。查看jdk帮助文档,我们可以知道,add方法有类型参数E作为集合的元素类型,所以我们传给add的参数必须是E类型对象或其子类型对象,但是我们不知道E是什么类型,所以无法添加对象进去,不过,有一个例外,就是null类型,因为它是任何引用类型的子类型。
那么在集合中有多少方法不能用呢?查看API,只要方法接受的参数是E类型的就都不能使用。
1.2 设定通配符的上限
上面刚讲到,通配符可以匹配任意类型,也就是说List<?>可以是任何泛型List的父类(虽然参数化类型没有继承关系,但可以类比)。但是,现在我们不想匹配任何泛型List,只想匹配某类或某类的子类泛型List。
比如说,我们需要把动物类或者动物类的子类装进List集合,那该怎么做呢?可能有些人会这样子:因为Animal(动物类)是Cat(猫类)的父类,那就直接把Cat类交给Animal类就可以了。但是这样做真的可以吗?代码如下:
package wj.wcj.testwildcard;
//--------------------①
import java.util.ArrayList;
import java.util.List;
class Animal{
private String name;
public Animal(){}
public Animal(String name){
this.name=name;
}
@Override
public String toString() {
return "动物类。。。"+name;
}
}
class Cat extends Animal{
private String name;
public Cat(String name){
this.name=name;
}
@Override
public String toString() {
return "猫类。。。"+name;
}
}
class Dog extends Animal{
private String name;
public Dog(String name){
this.name=name;
}
@Override
public String toString() {
return "狗类。。。"+name;
}
}
public class WildCardTest {
public static void main(String[] args) {
List<Animal> animal List = new Array<>();
animalList.add(new Animal("小青"));
animalList.add(new Animal("强子"));
printAnimal(animalList);
List<Cat> CatlList = new ArrayList<>();
CatlList.add(new Cat("小白"));
CatlList.add(new Cat("小喵喵"));
printAnimal(CatlList); ☜
/*
* 报的错误:
* The method printAnimal(List<Animal>) in the type
* WildCardTest is not applicable for the arguments
* (List<Cat>)
*/
}
//--------------------②
public static void printAnimal(List<Animal> l) {
for(Animal animal : l){
System.out.println(l+",");
}
}
}
可以看到程序在☜这个位置报错了。这是我们忽略了一个细节,即数化类型没有继承关系(jdk1.5新特性(一)有讲到),你怎么可以把List<Cat> 对象赋给List<Animal> 对象呢。
为了解决这样的需求,java泛型提供了中被限制的通配符。可以表示为 List<? extends Animal> ,这表示该集合可以接收Animal类以及Animal的子类对象。
下面把第②段代码改为:
public static void printAnimal(List<? extends Animal> l) {
for(Animal animal : l){
System.out.println(l+",");
}
}
上面的错误就会消失了。List<? extends Animal>中的 ? 号代表一个未知类型,同样对于List<? extends Animal>集合,我们不能对它进行与类型(类型参数是E的)相关的操作,如add方法等。
1.3 设定通配符的下限
通配符的下限和通配符的上限类似,对上一个案例可以表示为
List<? super Cat>,它表示这个List可以接受Cat类以及Cat类的父类对象。代码案例可以对上面代码进行修改,这里就不给出了。
2.泛型方法与方法重载
泛型可以指定通配符的上限,也可以指定通配符的下限。那么有两个方法,一个用上限表示,另一个用下限表示,这两个是方法的重载吗?
public class TestUtils {
/* 报的错误:
* Method copy(Collection<T>, Collection<? extends T>) has the same
* erasure copy(Collection<E>, Collection<E>) as
* another method in type TestUtils
*/
public static <T> void copy(Collection<T> dest,Collection<? extends T> src){
}
/* 报的错误:
* Method copy(Collection<? super T>, Collection<T>) has the same
* erasure copy(Collection<E>, Collection<E>) as
* another method in type TestUtils
*/
public static <T> T copy(Collection<? super T> dest,Collection<T> src){
}
}
虽然我的英语不怎么好,但是我还是理解了这个是说这两个方法是相同的。也就是说,这两个方法并不是重载。那怎么会这样呢,跟我想象的完全不一样。这就牵扯到下面要说的,泛型的擦除了。因为在编译时,编译器会去掉类型化,那么两个方法的参数完全一样了,即都是Collection对象类型。
3.泛型与数组
Java的泛型有一个重要的设计原则,就是如果一段代码在编译时没有产生:“[unchecked]未经检查的转换”警告,那么程序在运行时就不会发生类型转换异常。正是因为这个,所以数组元素的类型不能包含类型变量或类型形参,不过无上限的类型通配符是可以的。虽然不能创建ArrayList<Integer>[10]这样的数组,但是给出ArrayList<String>[]的声明还是可以的。
如果java支持创建这样的泛型数组,那么有这样的程序:
//声明并创建一个List数组,而每个List对象放的是字符串对象
List<String>[] lsa =new ArrayList<String>[10];
//把List数组转换为Object数组
Object[] obj=(Object[])lsa;
//声明并创建一个List集合,存放Integer对象
List<Integer> intList = new ArrayList<Integer>();
//给集合添加一个Integer对象
intList.add(new Integer(5));
//将List集合对象作为Object数组的第一个元素存入数组中
obj[0]=intList;
//取出lsa数组的第一个元素List集合,并取出集合中的第一个元素
String s = lsa[0].get(0); //
如果第一句是成立的话,那么当程序执行到最后一句话时,会出现
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,然而这么做违背泛型的设计原则。
Java允许创建无上限的通配符泛型数组,将上面代码改为:
//声明并创建一个List数组,而每个List对象放的是字符串对象
List<?>[] lsa = new ArrayList<?>[10];
//把List数组转换为Object数组
Object[] obj=(Object[])lsa;
//声明并创建一个List集合,存放Integer对象
List<Integer> intList = new ArrayList<Integer>();
//给集合添加一个Integer对象
intList.add(new Integer(5));
//将List集合对象作为Object数组的第一个元素存入数组中
obj[0]=intList;
//取出lsa数组的第一个元素List集合,并取出集合中的第一个元素
String s = (String)lsa[0].get(0);
可以看到在这时程序不得不进行强制类型转换(最后一句),最后一句运行时会引发类型转化异常。所以这是我们应该用instanceof运算符保证它的数据类型。
最后一句代码改为:
Object target=lsa[0].get(0);
if(target instanceof String){
String s = (String)target;
}
4.泛型与反射
4.1.Java在泛型出来时,反射包也被翻新了,来为参数化类型和方法提供参数信息。可以查看javaAPI:
| asSubclass(Class<U> clazz) 强制转换该 Class 对象,以表示指定的 class 对象所表示的类的一个子类。 | |
T | cast(Object obj) 将一个对象强制转换成此 Class 对象所表示的类或接口。 | |
static Class<?> | forName(String className) 返回与带有给定字符串名的类或接口相关联的 Class 对象。 | |
static Class<?> | forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器,返回与带有给定字符串名的类或接口相关联的 Class 对象。 | |
| getAnnotation(Class<A> annotationClass) 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。 | |
Class<?>[] | getClasses() 返回一个包含某些 Class 对象的数组,这些对象表示属于此 Class 对象所表示的类的成员的所有公共类和接口。 | |
Class<?> | getComponentType() 返回表示数组组件类型的 Class。 | |
Constructor<T> | getConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 | |
Constructor<?>[] | getConstructors() 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 | |
Constructor<T> | getDeclaredConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 | |
Constructor<?>[] | getDeclaredConstructors() 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。 | |
Class<?> | getDeclaringClass() 如果此 Class 对象所表示的类或接口是另一个类的成员,则返回的 Class 对象表示该对象的声明类。 | |
Class<?> | getEnclosingClass() 返回底层类的立即封闭类。 | |
Constructor<?> | getEnclosingConstructor() 如果该 Class 对象表示构造方法中的一个本地或匿名类,则返回 Constructor 对象,它表示底层类的立即封闭构造方法。 | |
T | newInstance() 创建此 Class 对象所表示的类的一个新实例。 |
4.2利用反射越过编译器对字节码操作
我们都知道如果一个集合声明并创建为这样:
List<String> listStr = new ArrayList<>();
那么我们就不能向List集合中添加Integer类型的对象了,但是真的一点办法都没有吗?当然不是,利用反射就可以做到,看以下代码:
List<Integer> listStr = new ArrayList<Integer>();
Class clazz = listStr.getClass();
Method m=clazz.getMethod("add",Object.class);
m.invoke(listStr, "java");
System.out.println(listStr.get(0));
这段代码最后打印的是字符串 java ,也就是说把字符串添加到了List<Integer> 集合里了。
这是怎么做到了呢?我们知道泛型的概念是在编译期存在的,在编译时编译器会将泛型擦除,所以在虚拟机中List<Integer>就变成了List原始类型了,当然原始类型的List可以存储任意对象。而反射就越过编译期,自然就不存在类型检查的问题了。
5.泛型的约束与局限性
(1)不能用基本类型实例化类型参数。也就是说没有List<int>,只有List<Integer> ,当然这原因就是类型擦除,擦除后List含有Object类型的域,而Object不能存int(当然自动封装是另一回事)。
(2)运行时类型查询只适用于原始类型。
(3)不能创建参数化类型数组。
(4)不能实例化类型变量。也就是说不能使用像new T(...),
new T[...]或T.class这样的表达式的类型变量。
(5)泛型类的静态上下文中类型变量无效,即不能在静态域或方法中引用类型变量。
(6)不能抛出或捕获泛型类的实例。
相关文章推荐
- 代理服务器的理解(1):Windows环境下的代理服务器设置
- java多线程
- java垃圾回收
- java中String的常用方法
- SPRING依赖注入
- Java学习--(七)类
- LIS 最长递增子序列 Java实现
- JAVA强制类型转换
- JAVA反射机制实际代码解释
- 【leetcode】【Single Number题目】java 异或运算解决数字出现偶数次还是奇数次问题||HashMap
- java使用dbcp操作数据库的用法
- Spring注解
- Java文件删除问题
- Java基础学习总结---------异常Exception(3) -JVM默认如何处理异常
- PAT A 1002. A+B for Polynomials
- 大数相乘、大数相加、大数相减Java版本
- web.xml的组成理解
- Java Serializable(序列化)的理解和总结
- Java栈的实例模拟
- SSH三大框架整合(Hibernate,Spring,struts2)