zoukankan      html  css  js  c++  java
  • java泛型

    编程中,先将类型参数指定为抽象类型,用一个占位符替代,在实例化时具体指定为需要类型。泛型的本质是参数化类型,所操作的数据类型被指定为一个参数。

    (将类型抽象成为一个参数,用占位符代表,实例化时由传入的类型参数具体指定)

    声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。泛型类是引用类型,是堆对象,主要引入了类型参数的概念。

     

    没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

    规则与限制:

    泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。泛型的类型参数可以有多个。泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");

    语法:

    使用<T>来声明占位类型名称,然后就可以把T当作一个类型代表来声明成员、参数和返回值类型。class GenericsFoo<T> 声明了一个泛型类,这个T没有任何限制,实际上相当于Object类型,实际上相当于 class GenericsFoo<T extends Object>。
    使用泛型所定义的类在声明和构造实例的时候,1,显示的声明占位类型:使用“<实际类型>”来一并指定泛型类型持有者的真实类型,如GenericsFoo<Double> douFoo=new GenericsFoo<Double>(new Double("33"));
    2,省略,默认使用强制转换,在构造对象时,不使用尖括号指定泛型类型,使用该对象的时强制转换。比如:GenericsFoo douFoo=new GenericsFoo(new Double("33"));实际上,当构造对象时不指定类型信息的时候,默认会使用Object类型,这也是要强制转换的原因。
    限制泛型
    由于没有限制class GenericsFoo<T>类型持有者T的范围,实际上这里的限定类型相当于Object,这和“Object泛型”实质是一样的。限制比如我们要限制T为集合接口类型。只需要这么做:
    class GenericsFoo<T extends Collection>,这样,泛型T只能是Collection接口的实现类,传入非Collection接口编译会出错。
    public class CollectionGenFoo<T extends Collection> {
    private T x;
    public CollectionGenFoo(T x) {
    this.x = x;
    }
    public T getX() {
    return x;
    }
    public void setX(T x) {
    this.x = x;
    }
    }
    实例化的时候可以这么写:
    public class CollectionGenFooDemo {
    public static void main(String args[]) {
    CollectionGenFoo<ArrayList> listFoo = null;
    listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
    //出错了,不让这么干。
    //原来作者写的这个地方有误,需要将listFoo改为listFoo1
    // CollectionGenFoo<Collection> listFoo1 = null;
    // listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());
    System.out.println("实例化成功!");
    }
    }
    当前看到的这个写法是可以编译通过,并运行成功。可是注释掉的两行加上就出错了,因为<T extends Collection>这么定义类型的时候,就限定了构造此类实例的时候T是确定的一个类型。
    实现 Collection接口的类很多很多,如果针对每一种都要写出具体的子类类型,那也太麻烦了,我干脆还不如用Object通用一下。所以泛型可以使用通配符泛型:
    为了解决类型被限制死了不能动态根据实例来确定的缺点,引入了“通配符泛型”,针对上面的例子,使用通配泛型格式为<? extends Collection>,“?”代表未知类型,这个类型是实现Collection接口。那么上面实现的方式可以写为:
    public class CollectionGenFooDemo {
    public static void main(String args[]) {
    CollectionGenFoo<ArrayList> listFoo = null;
    listFoo = new CollectionGenFoo<ArrayList>(new ArrayList());
    //现在不会出错了
    //原来作者写的这个地方有误,需要将listFoo改为listFoo1
    CollectionGenFoo<? extends Collection> listFoo1 = null;
    listFoo1=new CollectionGenFoo<ArrayList>(new ArrayList());
    System.out.println("实例化成功!");
    }
    }
    注意:
    1、如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类了。也就是任意类。
    2、通配符泛型不单可以向下限制,如<? extends Collection>,还可以向上限制,如<? super Double>,表示类型只能接受Double及其上层父类类型,如Number、Object类型的实例。
    3、泛型类定义可以有多个泛型参数,中间用逗号隔开,还可以定义泛型接口,泛型方法。这些都与泛型类中泛型的使用规则类似。
    是否拥有泛型方法,与其所在的类是否泛型没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。如:
    public class ExampleA {
    public <T> void f(T x) {
    System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
    ExampleA ea = new ExampleA();
    ea.f(" ");
    ea.f(10);
    ea.f('a');
    ea.f(ea);
    }
    }
    输出结果:
    java.lang.String
    java.lang.Integer
    java.lang.Character
    ExampleA
    使用泛型方法时,不必指明参数类型,编译器会自己找出具体的类型。泛型方法除了定义不同,调用就像普通方法一样。
    需要注意,一个static方法,无法访问泛型类的类型参数,所以,若要static方法需要使用泛型能力,必须使其成为泛型方法。
     
     
     
     
     
    Java 语言中的数组是协变的(covariant),也就是说,如果 Integer扩展了 Number(事实也是如此),那么不仅 IntegerNumber,而且 Integer[]也是Number[],在要求 Number[]的地方完全可以传递或者赋予 Integer[]。(更正式地说,如果NumberInteger的超类型,那么 Number[]也是 Integer[]的超类型)。您也许认为这一原理同样适用于泛型类型 ——List<Number>List<Integer>的超类型,那么可以在需要 List<Number>的地方传递List<Integer>。不幸的是,情况并非如此。所引发的问题参看:http://www.ibm.com/developerworks/cn/java/j-jtp01255.html
     
     
    ? extends 和 the ? super的具体讲解:http://blog.jobbole.com/885/
     
     
     
    泛型方法

    (在 类型参数 一节中)您已经看到,通过在类的定义中添加一个形式类型参数列表,可以将类泛型化。方法也可以被泛型化,不管它们定义在其中的类是不是泛型化的。

    泛型类在多个方法签名间实施类型约束。在 List<V> 中,类型参数 V 出现在 get()、add()、contains() 等方法的签名中。当创建一个 Map<K, V> 类型的变量时,您就在方法之间宣称一个类型约束。您传递给 add() 的值将与 get() 返回的值的类型相同。

    类似地,之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。例如,下面代码中的 ifThenElse() 方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:

    public <T> T ifThenElse(boolean b, T first, T second) {
    return b ? first : second;
    }

    注意,您可以调用 ifThenElse(),而不用显式地告诉编译器,您想要 T 的什么值。编译器不必显式地被告知 T 将具有什么值;它只知道这些值都必须相同。编译器允许您调用下面的代码,因为编译器可以使用类型推理来推断出,替代 T 的 String 满足所有的类型约束:

    String s = ifThenElse(b, "a", "b");

    类似地,您可以调用:

    Integer i = ifThenElse(b, new Integer(1), new Integer(2));

    但是,编译器不允许下面的代码,因为没有类型会满足所需的类型约束:

    String s = ifThenElse(b, "pi", new Float(3.14));

    为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:

    泛型方法是静态的时,这种情况下不能使用类类型参数。

    当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。


    有限制类型

    在前一屏 泛型方法 的例子中,类型参数 V 是无约束的或无限制的 类型。有时在还没有完全指定类型参数时,需要对类型参数指定附加的约束。

    考虑例子 Matrix 类,它使用类型参数 V,该参数由 Number 类来限制:

    public class Matrix<V extends Number> { ... }

    编译器允许您创建 Matrix<Integer> 或 Matrix<Float> 类型的变量,但是如果您试图定义 Matrix<String> 类型的变量,则会出现错误。类型参数 V 被判断为由 Number 限制 。在没有类型限制时,假设类型参数由 Object 限制。这就是为什么前一屏泛型方法 中的例子,允许 List.get() 在 List<?> 上调用时返回 Object,即使编译器不知道类型参数 V 的类型。

    三 一个简单的泛型

    编写基本的容器类

    此时,您可以开始编写简单的泛型类了。到目前为止,泛型类最常见的用例是容器类(比如集合框架)或者值持有者类(比如 WeakReference 或 ThreadLocal)。我们来编写一个类,它类似于 List,充当一个容器。其中,我们使用泛型来表示这样一个约束,即 Lhist 的所有元素将具有相同类型。为了实现起来简单,Lhist 使用一个固定大小的数组来保存值,并且不接受 null 值。

    Lhist 类将具有一个类型参数 V(该参数是 Lhist 中的值的类型),并将具有以下方法:

    public class Lhist<V> {
    public Lhist(int capacity) { ... }
    public int size() { ... }
    public void add(V value) { ... }
    public void remove(V value) { ... }
    public V get(int index) { ... }
    }

    要实例化 Lhist,只要在声明时指定类型参数和想要的容量:

    Lhist<String> stringList = new Lhist<String>(10);


    实现构造函数

    在实现 Lhist 类时,您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它:

    public class Lhist<V> {
    private V[] array;
    public Lhist(int capacity) {
    array = new V[capacity]; // illegal
    }
    }

    这似乎是分配后备数组最自然的一种方式,但是不幸的是,您不能这样做。具体原因很复杂,当学习到 底层细节 一节中的“擦除”主题时,您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现(该实现使用集合类所采用的方法):

    public class Lhist<V> {
    private V[] array;
    public Lhist(int capacity) {
    array = (V[]) new Object[capacity];
    }
    }


    另外,也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数 —— 一个类常量,比如 Foo.class。后面在 Class<T> 一节中将讨论类常量。


    实现方法

    实现 Lhist 的方法要容易得多。下面是 Lhist 类的完整实现:

    public class Lhist<V> {
    private V[] array;
    private int size;

    public Lhist(int capacity) {
    array = (V[]) new Object[capacity];
    }

    public void add(V value) {
    if (size == array.length)
    throw new IndexOutOfBoundsException(Integer.toString(size));
    else if (value == null)
    throw new NullPointerException();
    array[size++] = value;
    }

    public void remove(V value) {
    int removalCount = 0;
    for (int i=0; i<size; i++) {
    if (array[i].equals(value))
    ++removalCount;
    else if (removalCount > 0) {
    array[i-removalCount] = array[i];
    array[i] = null;
    }
    }
    size -= removalCount;
    }

    public int size() { return size; }

    public V get(int i) {
    if (i >= size)
    throw new IndexOutOfBoundsException(Integer.toString(i));
    return array[i];
    }
    }

    注意,您在将会接受或返回 V 的方法中使用了形式类型参数 V,但是您一点也不知道 V 具有什么样的方法或域,因为这些对泛型代码是不可知的。


    使用 Lhist 类

    使用 Lhist 类很容易。要定义一个整数 Lhist,只需要在声明和构造函数中为类型参数提供一个实际值即可:

    Lhist<Integer> li = new Lhist<Integer>(30);

    编译器知道,li.get() 返回的任何值都将是 Integer 类型,并且它还强制传递给 li.add() 或 li.remove() 的任何东西都是 Integer。除了实现构造函数的方式很古怪之外,您不需要做任何十分特殊的事情以使 Lhist 是一个泛型类。

     
  • 相关阅读:
    阅读 video in to axi4-stream v4.0 笔记
    python 字符串操作
    python 基本语句
    Python 算术运算符
    芯片企业研报阅读
    量化分析v1
    基于MATLAB System Generator 搭建Display Enhancement模型
    System Generator 生成IP核在Vivado中进行调用
    FPGA 中三角函数的实现
    System Generator 使用离散资源
  • 原文地址:https://www.cnblogs.com/cl1024cl/p/6205610.html
Copyright © 2011-2022 走看看