15泛型_15.11问题

15.11问题

本节将阐述在使用Java泛型时会出现的各类问题。

15.11.1任何基本类型都不能作为类型参数

正如本章早先提到过的,你将在Java泛型中发现的限制之一是,不能将基本类型用作类型参数。因此不能创建ArrayList<int>之类的东西。
解决之道是使用甚本类型的包装器类以及Java SE5的自动包装机制。如果创建一个ArrayList<Integer>,并将基本类型int应用于这个容器,那么你将发现自动包装机制将自动地实现int到Integer的双向转换——因此,这几乎就像是有一个ArrayList<int>一样:

//: generics/ListOfInt.java
// Autoboxing compensates for the inability to use
// primitives in generics.
import java.util.*;

public class ListOfInt {
  public static void main(String[] args) {
    List<Integer> li = new ArrayList<Integer>();
    for(int i = 0; i < 5; i++)
      li.add(i);
    for(int i : li)
      System.out.print(i + " ");
  }
} /* Output:
0 1 2 3 4
*///:~

注意,自动包装机制甚至允许用foreach语法来产生int。

通常,这种解决方案工作得很好—能够成功地存储和读取int,有一些转换碰巧在发生的同时会对你屏蔽掉。但是,如果性能成为了问题,就需要使用专门适配基本类型的容器版本。

//: generics/ByteSet.java
import java.util.*;

public class ByteSet {
  Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };
  Set<Byte> mySet =
    new HashSet<Byte>(Arrays.asList(possibles));
  // But you can't do this:
  // Set<Byte> mySet2 = new HashSet<Byte>(
  //   Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
} ///:~

注意,自动包装机制解决了一些问题,但并不是解决了所有问题。下面的示例展示了一个泛型的Generator接口,它指定next()方法返回一个具有其参数类型的对象。FArray类包含一个泛型方法,它通过使用生成器在数组中填充对象(这使得类泛型在本例中无法工作,因为这个方法是静态的)。Generator实现来自第16章,并且在main()中,可以看到FArray.fill()使用它在数组中填充对象:

//: generics/PrimitiveGenericTest.java
import net.mindview.util.*;

// Fill an array using a generator:
class FArray {
  public static <T> T[] fill(T[] a, Generator<T> gen) {
    for(int i = 0; i < a.length; i++)
      a[i] = gen.next();
    return a;
  }
}   

public class PrimitiveGenericTest {
  public static void main(String[] args) {
    String[] strings = FArray.fill(
      new String[7], new RandomGenerator.String(10));
    for(String s : strings)
      System.out.println(s);
    Integer[] integers = FArray.fill(
      new Integer[7], new RandomGenerator.Integer());
    for(int i: integers)
      System.out.println(i);
    // Autoboxing won't save you here. This won't compile:
    // int[] b =
    //   FArray.fill(new int[7], new RandIntGenerator());
  }
} /* Output:
YNzbrnyGcF
OWZnTcQrGs
eGZMmJMRoE
suEcUOneOE
dLsmwHLGEa
hKcxrEqUCB
bkInaMesbt
7052
6665
2654
3909
5202
2209
5458
*///:~

由于RandomGenerator.Integer实现了Generator<Integer>,所以我的希望是自动包装机制可以自动地将next()的值从Integer转换为int。但是,自动包装机制不能应用于数组,因此这无法工作。

15.11.2实现参数化接口

一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:

//: generics/MultipleInterfaceVariants.java
// {CompileTimeError} (Won't compile)

interface Payable<T> {}

class Employee implements Payable<Employee> {}
class Hourly extends Employee
  implements Payable<Hourly> {} ///:~

Hourly不能编译,因为擦除会将Payable<Employee>Payable<Hourly>简化为相同的类Payable,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从Payable的两种用法中都移除掉泛型参数〔就像编译器在擦出阶段所做的那样)这段代码就可以编译。
在使用某些更基本的Java接口,例如Comparable时,这个问题可能会变得十分令人恼火,就像你在本节稍后就会看到的那样。

15.11.3转型和警告

使用带有泛型类型参数的转型或instanceof不会有任何效果。下面的容器在内部将各个值存储为Object,并在获取这些值时,耳将它们转型回T:

//: generics/GenericCast.java

class FixedSizeStack<T> {
  private int index = 0;
  private Object[] storage;
  public FixedSizeStack(int size) {
    storage = new Object[size];
  }
  public void push(T item) { storage[index++] = item; }
  @SuppressWarnings("unchecked")
  public T pop() { return (T)storage[--index]; }
}   

public class GenericCast {
  public static final int SIZE = 10;
  public static void main(String[] args) {
    FixedSizeStack<String> strings =
      new FixedSizeStack<String>(SIZE);
    for(String s : "A B C D E F G H I J".split(" "))
      strings.push(s);
    for(int i = 0; i < SIZE; i++) {
      String s = strings.pop();
      System.out.print(s + " ");
    }
  }
} /* Output:
J I H G F E D C B A
*///:~

如果没有@SuppressWarnings注解,编译器将对pop()产生“unchecked cast’警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且pop()方法实际上并没有执行任何转型。这是因为,T被擦除到它的第一个边界,默认情况下是Object, 因此pop()实际上只是将Object转型为Object。

有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如:

//: generics/NeedCasting.java
import java.io.*;
import java.util.*;

public class NeedCasting {
  @SuppressWarnings("unchecked")
  public void f(String[] args) throws Exception {
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream(args[0]));
    List<Widget> shapes = (List<Widget>)in.readObject();
  }
} ///:~

正如你将在下一章学到的那样,readObject()无法知道它正在读取的是什么,因此它返的是必须转型的对象。但是当注释掉@SuppressWarnings注解,并编译这个程序时,就会得到下面的警告:

Note: NeedCasting.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

如果遵循这条指示,使用-Xliint: unchecked来重新编译:

NeedCasting.java:12: warning: [unchecked] unchecked cast found:java.lang.Object
required: java.util.List<Widget>
    List<Shape> shapes = (List<Widget>)in.readObjlect():

你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用在Java SE5中引入的新的转型形式,既通过泛型类来转型:

//: generics/ClassCasting.java
import java.io.*;
import java.util.*;

public class ClassCasting {
  @SuppressWarnings("unchecked")
  public void f(String[] args) throws Exception {
    ObjectInputStream in = new ObjectInputStream(
      new FileInputStream(args[0]));
      // Won't Compile:
//    List<Widget> lw1 =
//    List<Widget>.class.cast(in.readObject());
    List<Widget> lw2 = List.class.cast(in.readObject());
  }
} ///:~

但是,不能转型到实际类型(List<Widget>)。也就是说,不能声明:

    List<Widget>.class.cast(in.readObject())

甚至当你添加一个像下面这样的另一个转型时:

    (List<Widget>)List.class.cast(in.readObject())

仍旧会得到一个警告。

15.11.4重裁

下面的程序是不能编译的,即使编译它是一种合理的尝试:

//: generics/UseList.java
// {CompileTimeError} (Won't compile)
import java.util.*;

public class UseList<W,T> {
  void f(List<T> v) {}
  void f(List<W> v) {}
} ///:~

由于擦除的原因,重载方法将产生相同的类型签名。
与此不同的是,当被擦除的参数不能产生唯一的参数列表时。必须提供明显有区别的方法名:

//: generics/UseList2.java
import java.util.*;

public class UseList2<W,T> {
  void f1(List<T> v) {}
  void f2(List<W> v) {}
} ///:~

幸运的是,这类问题可以由编译器探测到。

15.11.5 基类劫持了接口

假设你有一个Pet类,它可以与其他的Pet对象进行比较(实现了Comparable接口):

//: generics/ComparablePet.java

public class ComparablePet
implements Comparable<ComparablePet> {
  public int compareTo(ComparablePet arg) { return 0; }
} ///:~

对可以与ComparablePet的子类比较的类型进行窄化是有意义的,例如,一个Cat对象就只能与其他的Cat对象比较:

//: generics/HijackedInterface.java
// {CompileTimeError} (Won't compile)

class Cat extends ComparablePet implements Comparable<Cat>{
  // Error: Comparable cannot be inherited with
  // different arguments: <Cat> and <Pet>
  public int compareTo(Cat arg) { return 0; }
} ///:~

遗憾的是,这不能工作。一旦为Comparable确定了ComparablePet参数,那么其他任何实现类都不能与ComparablePet之外的任何对象比较:

//: generics/RestrictedComparablePets.java

class Hamster extends ComparablePet
implements Comparable<ComparablePet> {
  public int compareTo(ComparablePet arg) { return 0; }
}

// Or just:

class Gecko extends ComparablePet {
  public int compareTo(ComparablePet arg) { return 0; }
} ///:~

Hamster说明再次实现ComparablePet中的相同接口是可能的,只要她们精确地相同,包括参数类型在内。但是,这只是与覆盖基类中的方法相同,就像在Geeko中看到的那样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值