您的位置:首页 > 编程语言 > Java开发

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:

<U> Class<? extends U>

 

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 对象。

<A extends Annotation> 
A

 

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)不能抛出或捕获泛型类的实例。

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: