《Java编程思想》笔记15-泛型

泛型方法

如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。

对于一个 static 方法,无法访问泛型类的类型参数。

public <T> void function(T i){}  //将泛型参数列表置于返回类型之前

类型参数推断

public class New {
  public static <K,V> Map<K,V> map() {
    return new HashMap<K,V>();
  }
  public static void f(Map<Person, List<? extends Pet>> petPeople) {}

  public static void main(String[] args) {
    Map<String, List<String>> sls = New.map();
    Map<Person, List<? extends Pet>> petPeople = New.map();
    f(petPeople);
    //f(New.map()); // 无法编译
    f(New.<Person, List<? extends Pet>>map()); // 显示指明类型
  }
}

类型推断只对赋值操作有效,其他时候不一定有作用,将 New.map() 作为参数传递给另一个方法,并不会执行类型推断。

Java7 的泛型类型推断改进

Java 8新特性探究(六)泛型的目标类型推断

在以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。例如:

Map<String, String> myMap = new HashMap<String, String>();

在Java SE 7中,这种方式得以改进,现在你可以使用如下语句进行声明并赋值:

Map<String, String> myMap = new HashMap<>(); //注意后面的 "<>",加上这个 "<>" 才表示是自动类型推断

但是:Java7 在创建泛型实例时的类型推断是有限制的:只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。例如:下面的例子在 Java7 无法正确编译(但现在在 Java8 里面可以编译,因为根据方法参数来自动推断泛型的类型):

List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());

Java8 的泛型类型推断改进

上文中 f(New.map()); 可以编译通过。

static void processStringList(List<String> stringList) {
    // process stringList
}

processStringList(Collections.emptyList());
processStringList(Collections.<String> emptyList()); //低版本

擦除

public class HasF {
  public void f() { System.out.println("HasF.f()"); }
}
class Manipulator<T> {
  private T obj;
  public Manipulator(T x) { obj = x; }
  // Error: cannot find symbol: method f():
  public void manipulate() { obj.f(); } // 编译不通过
}

public class Manipulation {
  public static void main(String[] args) {
    HasF hf = new HasF();
    Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
    manipulator.manipulate();
  }
}

上面的代码没有通过编译,就是由于擦除,会将 T 替换为 Object,这样就没法调用 f() 方法了。可以使用边界来解决这个问题。

class Manipulator<T extends HasF> {
  private T obj;
  public Manipulator(T x) { obj = x; }
  public void manipulate() { obj.f(); }
}

擦除的问题

泛型不能用于显式地引用运行时类型的操作之中,例如转型、instanceof 操作和 new 表达式(不能 new T())。因为类型信息会丢失,必须时刻提醒自己,只是看起来像拥有有关参数的类型信息而已。

在整个类的各个地方,类型 T 都在被替换,无论何时,必须时刻提醒自己“它只是个 Object”。

泛型中的所有动作都发生在边界处————对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。这有助于澄清对擦除的混淆,记住,“边界就是发生动作的地方”。

擦除的补偿

Java 泛型在 instanceof、创建类型实例,创建数组、转型时都会有问题。

public class Erased<T> {
  public void f(Object arg) { // 书中
    if(arg instanceof T) {}          // Error
    T var = new T();                 // Error
    T[] array = new T[10];         // Error
    T[] array = (T)new Object[10]; // Unchecked warning
  }
}

有时必须通过引入类型标签(即你的类型的 Class 对象)进行补偿。使用动态的 isInstance() 方法,而不是 instanceof。

class Building {}
class House extends Building {}

public class ClassTypeCapture<T> {
  Class<T> kind;

  public ClassTypeCapture(Class<T> kind) {
    this.kind = kind;
  }

  public boolean f(Object arg) {
    return kind.isInstance(arg);
  }

  public static void main(String[] args) {
    ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
    System.out.println(ctt1.f(new Building()));
    System.out.println(ctt1.f(new House()));
    ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
    System.out.println(ctt2.f(new Building()));
    System.out.println(ctt2.f(new House()));
  }
} /* Output:
true
true
false
true
*///:~

创建类型实例

解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是 Class 对象。

class ClassAsFactory<T> {
  T x;
  public ClassAsFactory(Class<T> kind) {
    try {
      x = kind.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

class Employee { }

public class InstantiateGenericType {
  public static void main(String[] args) {
    ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
    print("ClassAsFactory<Employee> succeeded");
    try {
      ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class);
    } catch (Exception e) {
      print("ClassAsFactory<Integer> failed");
    }
  }
}

但是对于没有默认构造器的类,上述方法不能奏效了。可以使用显示的工厂。

interface FactoryI<T> {
  T create();
}

public class Foo2<T> {
  private T x;
  public <F extends FactoryI<T>> Foo2(F factory) {
    x = factory.create();
  }
  public static void main(String[] args) {
    new Foo2<Integer>(new IntegerFactory());
    new Foo2<Widget>(new Widget.Factory());
  }
}

class IntegerFactory implements FactoryI<Integer> {
  public Integer create() {
    return new Integer(0);
  }
}

class Widget {
  public static class Factory implements FactoryI<Widget> {
    public Widget create() {
      return new Widget();
    }
  }
}

另一种方式是模板方法设计模式。

abstract class GenericWithCreate<T> {
  final T element;
  GenericWithCreate() { element = create(); }
  abstract T create();
}

class X {}

class Creator extends GenericWithCreate<X> {
  X create() { return new X(); }
  void f() {
    System.out.println(element.getClass().getSimpleName());
  }
}

public class CreatorGeneric {
  public static void main(String[] args) {
    Creator c = new Creator();
    c.f();
  }
} /* Output:
X
*///:~

泛型数组

解决方案是在任何想要创建泛型数组的地方都使用 ArrayList

如果非要用泛型数组,可以创建Object数组,然后转型。但是如果返回该泛型数组还是需要再进行一次转型。

T[] array;
public Constructor(int sz){
  array = (T[]) new Object[sz];
}

使用类型标记

T[] array;
public Constructor(Class<T> type, int sz){
    array = (T[]) Array.newInstance(type, sz);
}

边界

interface HasColor { }
class Colored<T extends HasColor> { }
class Dimension { }

// 编译不通过,class 必须在前
// class ColoredDimension<T extends HasColor & Dimension> {

// Multiple bounds:
class ColoredDimension<T extends Dimension & HasColor> { }

interface Weight { }

// 只能有一个类,可以有多个接口
class Solid<T extends Dimension & HasColor & Weight> { }

通配符

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class CovariantArrays {
  public static void main(String[] args) {
    Fruit[] fruit = new Apple[10]; // 向上转型不合适这里
    fruit[0] = new Apple(); // OK
    fruit[1] = new Jonathan(); // OK

    fruit[0] = new Fruit(); // ArrayStoreException
    fruit[0] = new Orange(); // ArrayStoreException

    List<Fruit> flist = new ArrayList<Apple>(); // 编译错误

    List<? extends Fruit> flist = new ArrayList<Apple>();
    // 添加任何类型,都是编译失败
    // flist.add(new Apple());
    // flist.add(new Fruit());
    // flist.add(new Object());
    flist.add(null); // 合法
    Fruit f = flist.get(0);

    List<? extends Fruit> flist2 = Arrays.asList(new Apple());
    Apple a = (Apple) flist2.get(0);
    flist2.contains(new Apple()); // contains() 和 indexOf() 方法接受 Object 类型的参数
    flist2.indexOf(new Apple());
  }
}

逆变

超类型通配符。可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 <? super MyClass>,可以使用类型参数:<? super T> (不能声明类型参数为<T super MyClass>)

List<? super Apple> flist = new ArrayList<>();
flist.add(new Apple());
flist.add(new Jonathan());
//flist.add(new Fruit()); //Compile Error

Apple 是下界,这样你就知道向其中添加 Apple 或 Apple 的子类型是安全的,而添加 Fruit 是不安全的了。

无界通配符

无界通配符 <?> 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。

List 实际上表示“持有任何 Object 类型的原生 List”,而 List<?> 表示“具有某种特定类型的非原生 List,只是我们不知道那种类型是什么。”

问题

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

解决之道是自动包装机制。但是自动包装机制不能作用于数组。

类泛型无法在静态方法中工作。

public class ByteSet {
  Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };
  Set<Byte> mySet = new HashSet<Byte>(Arrays.asList(possibles));
  // 不能这样
  // Set<Byte> mySet2 = new HashSet<Byte>(Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
} ///:~

专门适配基本类型的容器版本,org.apache.commons.collections.primitives

实现参数化接口

一个类不能同时实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会变成相同的接口。

interface Payable<T>{}
class Employee implements Payable<Employee> {}
class Hourly extends Employee implements Payable<Hourly> {} // 编译失败,如果把泛型都去掉,就可以了

转型和警告

泛型没有消除对转型的需要。

ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));
List<Widget> shapes = (List<Widget>)in.readObject(); // 警告
List<Widget> lw = List<Widget>.class.cast(in.readObject()); // 编译失败
List<Widget> lw = (List<Widget>)List.class.cast(in.readObject()); // 警告
List<Widget> lw = List.class.cast(in.readObject()); // 通过泛型类来转型

重载

由于擦除的原因,重载方法将产生相同的类型签名。

void f(List<T> v);
void f(List<W> v); // 编译失败

基类劫持了接口

public class ComparablePet implements Comparable<ComparablePet> {
  public int compareTo(ComparablePet arg) { return 0; }
} ///:~
class Cat extends ComparablePet implements Comparable<Cat>{
  // 编译失败
  public int compareTo(Cat arg) { return 0; }
} ///:~

自限定的类型

class SelfBounded<T extends Selfbounded<T>>{}

古怪的循环泛型(CRG):基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用导出类型。

public class BasicHolder<T> {
  T element;
  void set(T arg) { element = arg; }
  T get() { return element; }
  void f() {
    System.out.println(element.getClass().getSimpleName());
  }
} ///:~
class Subtype extends BasicHolder<Subtype> {}

public class CRGWithBasicHolder {
  public static void main(String[] args) {
    Subtype st1 = new Subtype(), st2 = new Subtype();
    st1.set(st2);
    Subtype st3 = st1.get();
    st1.f();
  }
} /* Output:
Subtype
*///:~

自限定

可以保证类型参数必须与正在被定义的类相同。

class SelfBounded<T extends SelfBounded<T>> {
  T element;
  SelfBounded<T> set(T arg) {
    element = arg;
    return this;
  }
  T get() { return element; }
}
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // Also OK
class C extends SelfBounded<C> {
  C setAndGet(C arg) { set(arg); return get(); }
}
class D {}
// Can't do this:
// class E extends SelfBounded<D> {}
// Compile error: Type parameter D is not within its bound

// Alas, you can do this, so you can't force the idiom:
class F extends SelfBounded {}

自限定限制只能作用于继承关系。

动态类型安全

java.util.Collections 中提供来一组便利工具,可以解决类型检查的问题。它们是:静态方法 checkedCollection()、checkedList()、checkedMap()、checkedSet()、checkedSortedMap() 和 checkedSortedSet()。这些方法每一个都会将你希望动态检查的容器当做第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。如果向Java SE5 之前的代码传递泛型容器,可能会导致类似“将猫插入狗队列”的问题,使用这些方法可以确保不出现这种问题。

异常

由于擦除的原因,将泛型应用于异常是非常受限的。catch 语句不能捕获泛型类型的异常,泛型类也不能直接或间接继承自 Throwable。但是,类型参数可能会在一个方法的 throws 子句中用到。

interface Processor<T,E extends Exception>{
    void process(List<T> resultCollector) throws E;
}

剩余部分看着有点懵...



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Java编程思想》笔记15-泛型

文章字数:2.9k

本文作者:Bin

发布时间:2018-05-25, 21:24:26

最后更新:2019-08-06, 00:46:42

原始链接:http://coolview.github.io/2018/05/25/Java%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3/%E3%80%8AJava%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E3%80%8B%E7%AC%94%E8%AE%B015-%E6%B3%9B%E5%9E%8B/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录