java 泛型

本文总结了《The Java Programming Language》一书中关于第11章泛型的讲解,阐述了泛型引入的原因、自定义泛型类和泛型方法的使用,并详细解释了类型擦除、边界类型参数及实例化等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

这篇文章是对 The Java Programming Language 一书中第11章的总结。这本书是 Java 语言之父写的,每个 Java 从业人员都应该去看看这本书。

为什么引入泛型

举个例子就明白了!没有引入泛型之前,我们用单链表实现队列的代码如下,可以看到每个结点内部的元素是一个 Object 的引用。这给队列提供了很大的自由。然而,这样做会有2个缺陷。第一、假如你这个队列只存 String 对象,那么你每次从队列中取元素时都需要强转成 String 对象,这很不方便!第二、一个程序员很有可能不小心将其它类型的对象,比如 Integer,放到队列中,而这样的错误会一直运行到转换类型时才会被发现。

class Node {
    private Node next;
    private Object element;
    public Node(Object element) {
        this.element = element;
    }
    public Node(Object element, Node next) {
        this.element = element;
        this.next = next;
    }
    public Object getElement() {
        return element;
    }
    public void setElement(Object element) {
        this.element = element;
    }
    public Node getNext() {
        return next;
    }
    public void setNext(Node next) {
        this.next = next;
    }
}

public class SingleLinkQueue {
    protected Node head;
    protected Node tail;
    public void add(Object item) { /* ... */ }
    public Object remove() { /* ... */ }
}

为了解决这样的问题,你可能会为每个特定的类型写一个只存储这种类型的队列。但这会导致大量重复的代码,大量基本上一样的类(除了参数和返回类型),且这样做也容易出错。因此,泛型的出现正是解决这样的问题。有了泛型以后,代码就可以写成下面的样子了:

class Node < E > {
    private Node < E > next;
    private E element;
    public Node(E element) {
        this.element = element;
    }
    public Node(E element, Node < E > next) {
        this.element = element;
        this.next = next;
    }
    public E getElement() {
        return element;
    }
    public void setElement(E element) {
        this.element = element;
    }
    public Node < E > getNext() {
        return next;
    }
    public void setNext(Node < E > next) {
        this.next = next;
    }
}

因此,泛型的出现不仅提供了编译期的类型安全,从而让开发人员更少地出错,而且也大大简化了代码量。在 Java 集合类框架中泛型被广泛应用。

自定义泛型

泛型类

下面的代码定义了一个泛型类。从代码中可以看出,Box类可以得到复用,我们可以将T替换成任何我们想要的类型。这与上面介绍的好处是一样的。代码中的 <T> 叫做 type parameter,而 Box 类叫做 parameterized classes or parameterized types.

public class Box<T> {
   private T t;

   public void add(T t) {
      this.t = t;
   }

   public T get() {
      return t;
   }

   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<String> stringBox = new Box<String>();
    
      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

泛型方法

下面是一个泛型方法的代码。这就是制造一个泛型方法的语法,没什么可多说的。这样做的好处也一目了然。

 

public class GenericMethodTest {
   // generic method printArray
   public static < E > void printArray( E[] inputArray ) {
      // Display array elements
      for(E element : inputArray) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }

   public static void main(String args[]) {
      // Create arrays of Integer, Double and Character
      Integer[] intArray = { 1, 2, 3, 4, 5 };
      Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
      Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

      System.out.println("Array integerArray contains:");
      printArray(intArray);   // pass an Integer array

      System.out.println("\nArray doubleArray contains:");
      printArray(doubleArray);   // pass a Double array

      System.out.println("\nArray characterArray contains:");
      printArray(charArray);   // pass a Character array
   }
}

Bounded Type Parameters

There may be times when you'll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for. 为了让某个函数只接收某种类型的参数,我们用边界符来定义泛型。下面代码中的 maximum 方法就只能接收 Comparable 及其子类的类型了。

public class MaximumTest {
   // determines the largest of three Comparable objects
   
   public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
      T max = x;   // assume x is initially the largest
      
      if(y.compareTo(max) > 0) {
         max = y;   // y is the largest so far
      }
      
      if(z.compareTo(max) > 0) {
         max = z;   // z is the largest now                 
      }
      return max;   // returns the largest object   
   }
   
   public static void main(String args[]) {
      System.out.printf("Max of %d, %d and %d is %d\n\n", 
         3, 4, 5, maximum( 3, 4, 5 ));

      System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
         6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));

      System.out.printf("Max of %s, %s and %s is %s\n","pear",
         "apple", "orange", maximum("pear", "apple", "orange"));
   }
}

下面最后一行代码会被编译器报错。很多人可能会认为 Integer 是 Number 的子类型,根据多态的原理,将子类型传到 sum 中不应该出错呀!但不幸的是,List<Integer> 并不是 List<Number> 的子类型,你可以这样理解,Integer[] 数组 也不是 Number[] 数组的子类型。

static double sum(List <Number> list) {
    double sum = 0.0;
    for (Number n: list)
        sum += n.doubleValue();
    return sum;
}

List<Integer> l = new ArrayList<Integer>();
l.add(1);
double sum = sum(l);  // INVALID: won't compile

将上面的方法用通配符的方式去声明就可以了。

static double sum(List < ? extends Number > list) {
    double sum = 0.0;
    for (Number n: list)
        sum += n.doubleValue();
    return sum;
}

实际上,它们的继承关系如下图所示:

Java 泛型的类型擦除带来的问题

Java 泛型中的类型参数只保留在编译期间,保证代码的类型是正确的。但是在编译成 class 文件过后,类型参数就被擦除了,也就是说,在程序运行时,你不能知道具体是哪个类型参数。通过下面的代码就可以验证类型擦除的事实。一旦编译过后,无论是 ArrayList<Integer> 还是 ArrayList<Float>,最终都是 ArrayList,所以下面的 if 判断是 true.

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // evaluates to true
    System.out.println("Equal");
}

问题一:generic class cannot extend the Throwable class in any way, directly or indirectly

比如下面的第1行代码就是错误的。 Due to type erasure, the runtime will not know which catch block to execute, so this is prohibited by the compiler.

public class GenericException<T> extends Exception

try {
    throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
    System.err.println("Integer");
}
catch(GenericException<String> e) {
    System.err.println("String");
}

问题二:不能创建实例

下面有代码是错误的。Java generics generate only one compiled version of a generic class or function regardless of the number of parameterizing types used. Therefore, instantiating a Java class of a parameterized type is impossible because instantiation requires a call to a constructor, which is unavailable if the type is unknown. Java泛型详解 中如何用反射实例化的方案。

<T> T instantiateElementType(List<T> arg) {
     return new T(); //causes a compile error
}

问题三:type parameter cannot be used in the declaration of static variables or in static methods

Because there is only one copy per generic class at runtime, static variables are shared among all the instances of the class, regardless of their type parameter.

参考资料

Java - Generics

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值