`
rock
  • 浏览: 58160 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【转】java泛型

阅读更多

转自:http://blog.21ic.com/user1/1202/archives/2008/53356.html

1.        介绍

通 常,缺陷严重影响着大型程序和软件的使用。通过周密的设计、编码和测试,或许可以减少一些缺陷,但是缺陷对程序来讲简直就是无孔不入,特别是在要引入一些 新的特性或者程序越来越大越来越复杂的时候。值得我们高兴的是有些缺陷能很容易被发现,这给我的工作带来了极大的方便。例如编译时的缺陷能够立刻告诉我们 某个地方有错误,我们也可以通过编译输出的错误信息判断和找出错误所在,并且修改它。运行时的缺陷就没有那么好对付了,因为它们隐藏的很深,喜欢和我们玩 捉秘藏,往往很难被发现,有时即使在某些时候我们发现了它们,要找到产生它们原因的道路还很曲折。泛型 (Generics) 能够帮助我们在编译程序的时候就发现更多的缺陷。

下面代码定义了一个 Box 类,有两个方法 add() get()

       public class Box {

        private Object object;

        public void add(Object object) {

            this.object = object;

        }

        public Object get() {

            return object;

        }

       }

因为它的方法的参数是 Object ,所以我们可以随意传递任何参数到 add() 方法中,即使我们要想传递的不是原始类型也没有关系。然而事实上,我们应该严格限制传递的参数类型,但是目前我们只能做的就是在文档中加以说明或者在代码中添加相应的注释,编译器却对参数类型所作限制是不得而知的。

public class BoxDemo {

    public static void main(String[] args) {

        // ONLY place Integer objects into this box!

        Box integerBox = new Box();

 

        // Imagine this is one part of a large application

        // modified by one programmer.

        integerBox.add("10"); // note how the type is now String

 

        // ... and this is another, perhaps written

        // by a different programmer

        Integer someInteger = (Integer)integerBox.get();

        System.out.println(someInteger);

              }

                     }

       BoxDemo 中,没有将字符串 ”10” 转换成整数对象,很明显是个缺陷。但是在编译的时候并不能发现这个缺陷,从而造成在运行的时候出现错误。如果在定义 Box 类的时候能够使用泛型,那么这个错误在编译的时候就会被发现。

我们可以通过将 "public class Box" 修改为 "public class Box<T>" 而定义一个泛型,在这个定义中,使用了一个类型变量 (type variable) T ,而且 T 能够在 Box 类之内的任何地方被使用。这中定义的方法其实并不复杂,并且在接口 (interface) 中也被使用。实际上, T 可以看作是一种特殊的数据类型,它的值就是我们要传递给它的参数,参数的类型可以是类,也可以是接口,或者其他类型的变量,但是却不能是原始类型 (primitive) 的数据。

/**

  * Generic version of the Box class.

  */

public class Box<T> {

    private T t; // T stands for "Type"         

    public void add(T t) {

         this.t = t;

    }

    public T get() {

        return t;

       }

              }

       如上所示,我们用 T 代替了其中所有的 Object 。为了在我们的代码中能够使用泛型类,我们必须要用具体的类型比如 Integer 来代替 T ,这也叫做泛型调用 (generic type invocation)

实际上,泛型调用和普通的方法调用非常相似,所不同的是方法调用时传递的是参数,而泛型调用是传递的是一种具体的类型参数,比如 Integer 。泛型调用也叫做参数化类型 (parametersized type)

       为了实例化使用泛型技术定义的类,和通常一样要使用 new 关键字,如下所示:

              integerBox = new Box<Integer>();

       或者如果写的更加全面的话可以用下面的语句:

           Box<Integer> integerBox = new Box<Integer>();

       一旦 integerBox 被实例话了,我们就可以使用它的 get 方法而无需进行参数的转换。而且如果试图想加一个类型不符的参数到 box ,编译器就会报错。

       我们一定要记住,类型变量并不是真正的数据类型本身,上面的例子中,在文件中是找不到 T.java 或者 T.class 的,而且 T 也不是类名 Box 的一部分。实际上在编译的时候,所有和泛型有关的信息都会被去掉,从而最后只有 Box.class 文件,这会在后面进一步讨论。

       另外还要注意的是泛型可以多个类型参数,但是每个类型参数在所定义的类或者接口中不能重复。例如 Box<T, T> 则是有问题的,而 Box<T, U> 则是可以使用的。

       按照惯例,类型参数一般使用单个大写的字母表示。这样就可以普通变量的命名形成了明显的对比。如果不按照此惯例,就很难区分类型参数名和普通的类名或者接口名。

       通常使用的类型参数如下:

       E 元素 (Element)

       K 关键字 (Key)

       N (Number)

       T 类型 (Type)

       V (Value)

       S U V 2 个,第 3 个,第 4 个类型。

泛型方法和构造器

       如果在申明方法或者构造器的时候使用类型参数的话,就可以定义泛型方法和泛型构造器。这和定义一个普通的泛型基本上无二样,除了类型参数的作用范围只是在定义它的方法或者构造器之中。

/**

  * This version introduces a generic method.

  */

public class Box<T> {

    private T t;         

    public void add(T t) {

        this.t = t;

    }

    public T get() {

        return t;

    }

 

    public <U> void inspect(U u){

        System.out.println("T: " + t.getClass().getName());

        System.out.println("U: " + u.getClass().getName());

    }

    public static void main(String[] args) {

        Box<Integer> integerBox = new Box<Integer>();

        integerBox.add(new Integer(10));

        integerBox.inspect("some text");

    }

}

       在上面的例子中,我们定义了一个泛型方法 inspect ,而该泛型方法定义了一个类型参数 U 。这个方法接收输入的参数并将其类型输出,为了比较,它也将 T 的类型输出。这个类中还有一个 main 方法使得它可以直接运行,而且输出如下:

       T: java.lang.Integer

       U:java.lang.String

       通过输入不同的参数,输出也会跟着相应的变化。

受限的类型参数 (Bounded Type Parameters)

       有时候,我们要限制传递给类型参数的具体参数。例如,对数进行操作的方法就只能接受 Number 或者其子类的对象作为改方法的参数,而不能接受其他类型的参数。这也就是要对参数类型进行限制的原因。

       在申明一个类型参数的时候,如果在类型参数名后跟着 extends 关键字,而 extends 关键字后面又跟着类型参数的上限 (upper bound) ,例如这个上限可以是个数类 Number ,那么这个被申明的类型参数就是一个受限的参数类型。需要注意的是,这里的 extends 关键字可以是普通意义上类“继承”的意思,也可以是接口上“实现”的意思。

/**

  * This version introduces a bounded type parameter.

  */

public class Box<T> {

    private T t;         

    public void add(T t) {

        this.t = t;

    }

    public T get() {

        return t;

    }

    public <U extends Number > void inspect(U u){

         System.out.println("T: " + t.getClass().getName());

        System.out.println("U: " + u.getClass().getName());

    }

    public static void main(String[] args) {

        Box<Integer> integerBox = new Box<Integer>();

        integerBox.add(new Integer(10));

        integerBox.inspect("some text"); // error: this is still String!

    }

}

       如上代码所示, U 就是一个受限的类型参数,只能向其传递 Number 类或者 Number 子类的参数。如果对上面的代码进行编译的时候,就会报错,原因就是调用 inspect 方法的时候向其传递了一个 String 的参数 "some text"

       在定义受限类型参数的时候,如果还想要实现接口的话,就可以将要实现的接口使用 & 字符连接在类型参数上限 (upper bound) 的后面,如下所示:

       <U extends Number & MyInterface>

       要想实现多个接口的话,就用 & 依次将要实现的接口跟在后面就可以了,如下:

       <U extends Number & MyInterface1 & MyInterface2 … >

泛型的子类型

       只要两种类型能够相符,我们可以把一种类型的对象赋给另外一种类型的对象。例如,可以把一个 Integer 赋给一个 Object ,因为 Object Integer 的父类之一。

    Object someObject = new Object();

    Integer someInteger = new Integer(10);

    someObject = someInteger; // OK

       在面向对象的编程中, Integer Object 之间的这种关系叫做 ”is a” ,也就是说在本质上是相同的, Integer 是一种 Object ,所以才允许上面的这种赋值。由于 Integer 也是 Number 的一种,所以下面的赋值也是有效的:

    public void someMethod(Number n){

        // method body omitted

    }

 

    someMethod(new Integer(10)); // OK

    someMethod(new Double(10.1)); // OK

       对于泛型来讲,上面的讨论的这种关系也是适用的。在一个泛型调用中,我们可以传递 Number 作为它的参数,但是在后续调用 add 方法的时候,我们使用和 Number 相符的类型作为其参数:

    Box<Number> box = new Box<Number>();

    box.add(new Integer(10)); // OK

    box.add(new Double(10.1)); // OK

 

       我们现在考虑下面的方法:

    public void boxTest(Box<Number> n){

        // method body omitted

    }

       上面这个方法能够接受的参数是什么呢?实际上,我们可以看出它的参数是 Box<Number> 。那么我们能不能传递 Box<Integer> 或者 Box<Double> 作为它的参数呢?不能,原因就是 Box<Integer> Box<Double> 不是 Box<Number> 的子类型。

通配符

       在泛型中,我们可以用一个通配符 来代替一个未知的类型。例如,使用下面的代码为某种 animal 指定一个 cage

Cage<? extends Animal> someCage = ...;

       "? extends Animal" 表示一种未知的类型,它可能是 animal 的一种子类型,也可能是 animal 自己本身,总的来讲就是某种 animal 。上面的例子是一种受限通配符,它的上限就是 Animal 。如果需要装下某种 animal cage ,那么就可以被用作是 lion cage 或者 butterfly cage

如果使用 super 而不是 extends 则就可以为未知类型指定一个下限 (lower bound) 。例如, Cage<? super Animal> 表示的也是一种未知类型,其可能是 animal 的一种超类型 (supertype) ,也可能是 animal 自己本身。当然,如果我们用 <?> 来定义一个未知类型,那么这样的未知类型是不受限的。一个不受限的未知类型实质上就是 <? extends Object>

       虽然 Cage<Lion> Cage<Butterfly> 不是 Cage<Animal> 的子类型,但是却是 Cage<? extends Animal> 的子类型。上面已经定义了 someCage ,那么就可以进行如下赋值:

              Cage<Lion> lionCage = ...;

Cage<Butterfly> butterflyCage = ...;

someCage = lionCage; // OK

              someCage = butterflyCage; // OK

       但是我们还是不能把 butterflies lions 直接 add someCage

interface Lion extends Animal {}

Lion king = ...;

interface Butterfly extends Animal {}

Butterfly monarch = ...;

someCage.add(king);     // compiler-time error

       someCage.add(monarch);      // compiler-time error

       如果 someCage 是一个 butterfly cage ,那么它装入 butterfly 是没有问题的,但是却装不了 lion 。当然,如果 someCage 是一个 lion cage ,那么它装入 lion 是没有问题的,却装不了 butterfly 。也就是我们不能向 someCage 种装入任何 anmial ,那么是不是 someCage 就没有任何用了呢?其实不然,例如下面的代码就用到了 someCage

void feedAnimals(Cage<? extends Animal> someCage) {

       for (Animal a : someCage)

       a.feedMe();

       }

       这样一来,我们就可以把每种 animal 装入到对应独立的 cage 中,然后依次调用这个方法,如下:

feedAnimals(lionCage);

feedAnimals(butterflyCage);

       或者把所有的 animal cage 组合起来,然后可以用下面的代码进行代替:

feedAnimals(animalCage);

类型擦除 (Type Erasure)

       当我们实例化一个泛型的时候,编译器使用一种叫做类型擦除 (type erasure) 的技术。在类型擦除的过程中,编译器会去除掉 类与接口中所有和类型参数有关的信息。类型擦除使得用泛型的 java 应用程序能够和该泛型创建之前就存在的 java 库和应用程序相兼容。

       例如 Box<String> 在编译的时候产生一个叫做原型 (raw type) 的类型 Box ,而所谓原型就是没有任何参数的泛型类名或者接口名。这也就是说,在运行的时候我们不知道一个泛型类究竟是什么类型的对象。如下的代码在编译的时候就会报错:

public class MyClass<E> {

    public static void myMethod(Object item) {

        if (item instanceof E ) {  //Compiler error

            ...

        }

        E item2 = new E() ;   //Compiler error

        E[] iArray = new E[10] ; //Compiler error

        E obj = (E)new Object() ; //Unchecked cast warning

     }

}

       上面黑体代码之所以在编译的时候会报错是因为编译器去除了所有和参数 ( 由类型参数 E 代表 ) 相关的信息。

       有了类型擦除技术之后,就可以让新的代码和遗留的代码共存。但是无论如何,使用原型是一种不好的编程习惯,应该避免在程序中使用。当把遗留代码和泛型代码混合在一起的时候,我们可能会碰到类似于下面的告警:

Note: WarningDemo.java uses unchecked or unsafe operations.

Note: Recompile with -Xlint:unchecked for details.

       例如,我们用一个旧的 API ,但参数却用的是一个原型参数,如下的代码所示:

public class WarningDemo {

    public static void main(String[] args){

        Box<Integer> bi;

        bi = createBox();

    }

 

    static Box createBox(){

        return new Box();

    }

}

       我们用 -Xlint unchecked 重新编译就会显示出如下附加的信息:

WarningDemo.java:4: warning: [unchecked] unchecked conversion

found   : Box

required: Box<java.lang.Integer>

        bi = createBox();

                      ^

1 warning

分享到:
评论

相关推荐

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    Java泛型的用法及T.class的获取过程解析

    Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    1.java泛型定义.zip

    1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    4.java泛型的限制.zip

    4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip4.java泛型的限制.zip...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java 泛型类的类型识别示例

    综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...

    java泛型的内部原理及更深应用

    Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...

    java泛型学习ppt

    "Java 泛型学习" Java 泛型是 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的...

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...

    SUN公司Java泛型编程文档

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。这个特性极大地提高了代码的类型安全性和可读性,减少了在运行时出现ClassCastException的可能性。SUN公司的Java泛型编程文档,包括...

    Java泛型使用详细分析.pdf

    Java 泛型使用详细分析 Java 泛型是 Java 语言中的一种类型系统特性,允许开发者在编译期检查类型安全,以避免在运行时出现类型相关的错误。在本文中,我们将详细介绍 Java 泛型的使用方法和实现原理。 一、泛型的...

Global site tag (gtag.js) - Google Analytics