Java编程思想: 泛型

简单泛型

Java SE5以前是使用Object来实现其泛型的:java

public class Test {
  private Object o;
  public void set(Object o) {
    this.o = o;
  }
  public Object get() {
    return this.o;
  }
  public static void main(String[] args) {
    Test t = new Test();
    t.set("hello world");
    System.out.println(t.get());
    t.set(12.34);
    System.out.println(t.get());
  }
}

但这种实现方式存在两个的问题:ios

1. 因为存储的是Object类型, 则实际调用时候须要强制转换回原类型. 如执行t.get()操做时候, 实际上将对象o强制转换回String/Double类型.数组

2. 对于set方法, 咱们能够写入任何对象. 但若是这个方法的本来含义只但愿存储类A及其子类的话, 则代码层面就没法处理这种状况.dom

后面引入了新的泛型写法:ide

public class Test<T> {
  private T o;
  public void set(T o) {
    this.o = o;
  }
  public T get() {
    return this.o;
  }
  public static void main(String[] args) {
    Test<String> t = new Test<>();
    t.set("hello world");
    System.out.println(t.get());
  }
}

这里, 因为<String>的存在, 完美解决了上述的两个问题.函数

一个元祖类库工具

存在以下的需求: 一个方法返回多个对象. 针对return只返回一个对象的状况, 咱们能够新建一个泛型类, 存储多个对象. 而后返回这个类对象.ui

public class TwoTuple {
  public final A first;
  public final B second;
  public TwoTuple(A a, B b) {
    first = a;
    second = b;
  }
  public String toString() {
    return "(" + first + "," + second + ")";
  }
}

一个堆栈类this

public class LinkedStack<T> {
  private static class Node<U> {
    U item;
    Node<U> next;
    Node() { item = null; next = null; }
    Node(U item, Node<U> next) {
      this.item = item;
      this.next = next;
    }
    boolean end() { return item == null && next == null; }
  }
  private Node<T> top = new Node<T>();
  public void push(T item) {
    top = new Node<T>(item, top);
  }
  public T pop() {
    T result = top.item;
    if (!top.end()) {
      top = top.next;
    }
    return result;
  }

  public static void main(String[] args) {
    LinkedStack<String> lss = new LinkedStack<>();
    for (String s: "hello world Java".split(" ")) {
      lss.push(s);
    }
    String s;
    while ((s = lss.pop()) != null) {
      System.out.println(s);
    }
  }
}

RandomList编码

假设咱们须要一个持有特定对象的列表, 每次调用其上的select()方法时, 就随机选取一个元素.

import java.util.*;

public class RandomList<T> {
  private ArrayList<T> storage = new ArrayList<>();
  private Random rand = new Random(47);
  public void add(T item) { storage.add(item); }
  public T select() {
    return storage.get(rand.nextInt(storage.size()));
  }
  public static void main(String[] args) {
    RandomList<String> rs = new RandomList<>();
    for (String s: " 1 2 3 4 5 6 7 8 9".split(" ")) {
      rs.add(s);
    }
    for (int i = 0; i < 11; i++) {
      System.out.print(rs.select() + " ");
    }
  }
}

 

泛型接口

假设咱们要编写一个生成器接口, 那么须要作以下的准备:

1. 继承接口Iterable, 它的主要做用是告诉for循环, 下例中的Generator具备循环的能力. 须要实现的接口为iterator(), 当在程序中执行如for (Integer i: new Generator(5))的时候, 其实是对iterator()方法所返回的对象进行循环.

2. 而iterator()方法返回的对象必须具备可迭代能力, 因此它须要继承Iterator接口. Iterator接口须要实现的三个方法: next()/hasNext()/remove()方法.

import java.util.*;

public class Generator implements Iterable<Integer> {
  private int[] list = {1, 2, 3, 4, 5, 6};
  private int size;
  private Random rand = new Random(47);
  Generator() {}
  Generator(int size) {
    this.size = size;
  }
  public Integer next() {
    try {
      size--;
      return list[rand.nextInt(list.length)];
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
  }

  class GeneratorIterator implements Iterator<Integer> {
    int count = size;
    public boolean hasNext() {
      return count > 0;
    }
    public Integer next() {
      count--;
      return Generator.this.next();
    }
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }


  public Iterator<Integer> iterator() {
    return new GeneratorIterator();
  }

  public static void main(String[] args) {
    Generator gen = new Generator();

    for (int i = 0; i < 5; i++) {
      System.out.print(gen.next() + " ");
    }
    System.out.println();
    for (Integer i: new Generator(5)) {
      System.out.print(i + " ");
    }
  }
}

而下例是一个Fibonacci函数的可迭代实现:

import java.util.*;

public class IterableFibonacci implements Iterable<Integer>{
  private int count = 0;
  private int size;

  public IterableFibonacci() {}
  public IterableFibonacci(int size) {
    this.size = size;
  }
  public Integer next() {
    return fib(count++);
  }
  private int fib(int n) {
    if (n < 2) return 1;
    return fib(n - 1) + fib(n - 2);
  }
  public Iterator<Integer> iterator() {
    return new Iterator<Integer>() {
      public boolean hasNext() {
        return size > 0;
      }

      public Integer next() {
        size--;
        return IterableFibonacci.this.next();
      }

      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }

  public static void main(String[] args) {
    IterableFibonacci f = new IterableFibonacci();
    for (int i = 0; i < 18; i++) {
      System.out.print(f.next() + " ");
    }
    System.out.println();
    for (int i: new IterableFibonacci(18)) {
      System.out.print(i + " ");
    }
    System.out.println();
  }
}

 

泛型方法

对于类的方法来讲, 若是能泛型, 则尽可能泛型化. 

public class GenericMethods {
  public <T> void f(T x) {
    System.out.println(x.getClass().getName());
  }
  public static void main(String[] args) {
    GenericMethods gm = new GenericMethods();
    gm.f("");
    gm.f(1);
    gm.f(1.234);
  }
}

赋值可推断其泛型类型

当存储赋值语句时, 右边的表达式可经过左边的表达式推断出其泛型类型. 因此通常代码:

ArrayList<String> lst = new ArrayList<String>();

可简化为:

ArrayList<String> lst = new ArrayList<>();

显式的类型说明

要显式的指明类型, 必须在点操做符与方法名之间插入尖括号, 而后把类型置于尖括号内. 若是是在定义该方法的类的内部, 必须在点操做符以前使用this关键字, 若是是使用static的方法, 必须在点操做符以前加上类名.

public class Test {
  private String x = "hello";
  public <T> void f(T x) {
    System.out.println(x.getClass().getName());
  }
  public void show() {
    this.<String>f(x);
  }
  public static <T> void func(T x) {
    System.out.println(x.getClass().getName());
  }
  public static void main(String[] args) {
    Test t = new Test();
    t.<String>f("hello world");
    t.show();
    Test.<String>func("hello");
  }
}

实际上都不须要进行显式的说明, 编译器会自动根据具体的数据类型推断出T的具体类型.

可变参数与泛型方法

可变参数能够应用于泛型方法之上:

import java.util.*;

public class Test {
  public static <T> List<T> makelist(T... args) {
    List<T> result = new ArrayList<>();
    for (T item: args) {
      result.add(item);
    }
    return result;
  }
  public static void main(String[] args) {

    List<String> lst = makelist("ABCDEFGHI".split(""));
    System.out.println(lst);
  }
}

用于Generator的泛型方法

import java.util.*;

interface Generator<T> {
  T next();
}

class IteratorGenerator implements Generator<Integer>, Iterable<Integer> {
  private int count = 0;
  private int size;
  IteratorGenerator() {}
  IteratorGenerator(int s) {
    size = s;
  }
  private int fib(int n) {
    if (n < 2) return 1;
    return fib(n - 1) + fib(n - 2);
  }
  public Integer next() {
    return fib(count++);
  }
  class GeneratorIter implements Iterator<Integer> {
    private int count = size;
    public boolean hasNext() {
      return size > 0;
    }
    public Integer next() {
      size--;
      return IteratorGenerator.this.next();
    }
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }

  public Iterator<Integer> iterator() {
    return new GeneratorIter();
  }

  public static void main(String[] args) {
    IteratorGenerator gen = new IteratorGenerator();
    for (int i = 0; i < 18; i++) {
      System.out.print(gen.next() + " ");
    }
    System.out.println();
    for (Integer i: new IteratorGenerator(18)) {
      System.out.print(i + " ");
    }
  }
}
public class Generators {
  public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
    for (int i = 0; i < n; i++) {
      coll.add(gen.next());
    }
    return coll;
  }

  public static void main(String[] args) {
    Collection<Integer> f = fill(new ArrayList<Integer>(), new IteratorGenerator(), 12);
    for (int i: f) {
      System.out.print(i + " ");
    }
  }
}

一个通用的Generator

咱们能够为任何一个类构造一个Generator:

import java.util.*;

interface Generator<T> {
  T next();
}

class IteratorGenerator<T> implements Generator<T> {
  private Class<T> type;
  public IteratorGenerator(Class<T> type) {
    this.type = type;
  }
  public T next() {
    try {
      return type.newInstance();
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static <T> Generator<T> create(Class<T> type) {
    return new IteratorGenerator<T>(type);
  }
}

class CountedObject {
  private static long counter = 0;
  private final long id = counter++;
  public long id() { return id; }
  public String toString() {
    return "CountedObject " + id;
  }
}
public class Generators {
  public static void main(String[] args) {
    Generator<CountedObject> gen = IteratorGenerator.create(CountedObject.class);
    for (int i = 0; i < 5; i++) {
      System.out.println(gen.next());
    }
  }
}

一个Set实用工具

import java.util.*;

public class Sets {
  public static <T> Set<T> union(Set<T> a, Set<T> b) {
    Set<T> result = new HashSet<T>(a);
    result.addAll(b);
    return result;
  }
  public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
    Set<T> result = new HashSet<T>(a);
    result.retainAll(b);
    return result;
  }
  public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
    Set<T> result = new HashSet<T>(superset);
    result.removeAll(subset);
    return result;
  }
  public static <T> Set<T> complement(Set<T> a, Set<T> b) {
    return difference(union(a, b), intersection(a, b));
  }

  public static void main(String[] args) {
    Set<Integer> s1 = new HashSet<>(Arrays.asList(1, 2, 3, 4));
    Set<Integer> s2 = new HashSet<>(Arrays.asList(3, 4, 5, 6));
    System.out.println(Sets.union(s1, s2));
    System.out.println(Sets.intersection(s1, s2));
    System.out.println(Sets.difference(s1, s2));
    System.out.println(Sets.complement(s1, s2));
  }
}

 

匿名内部类

咱们可使用匿名内部类生成next方法, 而后在fill函数中调用便可.

import java.util.*;

interface Generator<T> {
  T next();
}

class Customer {
  private static long counter = 1;
  private final long id = counter++;
  private Customer() {}
  public String toString() { return "Customer " + id; }
  public static Generator<Customer> generator() {
    return new Generator<Customer>() {
      @Override
      public Customer next() {
        return new Customer();
      }
    };
  }
}

class Generators {
  public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
    for (int i = 0; i < n; i++) {
      coll.add(gen.next());
    }
    return coll;
  }
}
public class BankTeller {
  public static void main(String[] args) {
    Queue<Customer> line = new LinkedList<>();
    Generators.fill(line, Customer.generator(), 15);
    for (Customer c: line) {
      System.out.println(c);
    }
  }
}

 

类型擦除

何为擦除

在使用泛型编码时, 任何关于泛型的类型信息都会被擦除成最原始的类型: Object.

验证1: 不一样类型的List, 其class永远为List.class

import java.util.*;

public class Test {
  public static void main(String[] args) {
    List<String> l1 = new ArrayList<>();
    List<Integer> l2 = new ArrayList<>();
    // true
    System.out.print(l1.getClass() == l2.getClass());
    try {
      // true
      System.out.print(l2.getClass() == Class.forName("java.util.ArrayList"));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

验证2: 其泛型参数, 如String, Integer, 会最终用标识符如T, E等表示:

import java.util.*;

public class Test {
  public static void main(String[] args) {
    List<String> l1 = new ArrayList<>();
    // [E] 
    System.out.print(Arrays.toString(l1.getClass().getTypeParameters()));
  }
}

C++实现模板的方式和Java实现模板方式的比较

C++的代码:

#include <iostream>
using namespace std;

template<class T> class Manipulator {
    T obj;
public:
    Manipulator(T x) { obj = x; }
    void manipulate() { obj.f(); }
};

class HasF {
public:
    void f() {
        cout << "HasF::f()" << endl;
    }
};

int main() {
    HasF hf;
    Manipulator<HasF> manipulator(hf);
    manipulator.manipulate();
}

Java代码:

class Manipulator<T> {
  T obj;
  Manipulator(T x) {
    obj = x;
  }
  public void manipulator() {
    obj.f();
  }
}

class HasF {
  public void f() {
    System.out.println("HasF::f()");
  }
}

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

这里因为obj已经被擦除成Object类型, 因此会致使编译错误.

咱们只要让obj最多被擦除成HasF类型便可, 可以使用语法: <T extends HasF>, 这说明T能够为HasF或其子类, 则T最多被擦除为基类HasF类型, 从而致使obj.f()有意义.

class Manipulator<T extends HasF>

擦除的来源

泛型并非Java1.0的语言特性, 因此它没法达到上例C++中的"具体化"效果.

假设泛型做为其语言特性, 实现了具体化, 则咱们能够在类型参数上执行基于类型的语言操做和反射操做(如转型, new表达式, instanceof操做).

public class Erased<T> {
  private final int SIZE = 100;
  public static void f(Object arg) {
    // error
    if (arg instanceof T) {}
    // error
    T var = new T();
    // error
    T[] arr = new T[SIZE];
  }
}

擦除的核心的动机是: 使得泛化的客户端能够用非泛化的类库来使用. 由于Java SE5以前是没有泛化的, 而若是但愿SE5以后的返回代码兼容以前的代码, 则须要将泛化的代码擦除为Object类型.

擦除例子1:

import java.lang.reflect.Array;
import java.util.Arrays;

public class ArrayMarker<T> {
  private Class<T> kind;
  public ArrayMarker(Class<T> kind) { this.kind = kind; }
  @SuppressWarnings("unchecked")
  T[] create(int size) {
    return (T[]) Array.newInstance(kind, size);
  }
  public static void main(String[] args) {
    ArrayMarker<String> stringMarker = new ArrayMarker<>(String.class);
    String[] stringArray = stringMarker.create(9);
    // [null, null, null, null, null, null, null, null, null]
    System.out.println(Arrays.toString(stringArray));
  }
}

即便kind被存储为Class<T>, 擦除也意味着它实际上被存储为Class, 没有任何参数.

这里主要问题在于: 咱们须要操做T类型来建立数组, 而擦除正好影响到了T的具体类型.

擦除例子2:

import java.util.*;

public class FilledListMarker<T> {
  List<T> create(T t, int n) {
    List<T> result = new ArrayList<>();
    for (int i = 0; i < n; i++) {
      result.add(t);
    }
    return result;
  }

  public static void main(String[] args) {
    FilledListMarker<String> stringMarker = new FilledListMarker<>();
    List<String> list = stringMarker.create("hello", 4);
    // [hello, hello, hello, hello]
    System.out.println(list);
  }
}

这里擦除T根本不会影响到代码的任何一个细节. 内部的操做是基于List的.

擦除补偿

因为擦除致使了类型为Object, 则它不能执行相似new, instanceof等操做. 这时候须要补偿其擦除形成的影响.

1. 使用isInstance来代替isinstanceof:

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.class);
    System.out.println(ctt1.f(new Building()));
    System.out.println(ctt1.f(new House()));
    ClassTypeCapture<Building> ctt2 = new ClassTypeCapture(House.class);
    System.out.println(ctt2.f(new Building()));
    System.out.println(ctt2.f(new House()));
  }
}

2. 使用工厂模式, 经过传递Class对象调用newInstance来解决new T()无效的状况:

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 InstantianteGenericType {
  public static void main(String[] args) {
    ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class);
    System.out.println("ClassAsFactory<Employee> succeeded");
    try {
      ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class);
    } catch (Exception e) {
      System.out.println("ClassAsFactory<Integer> failed");
    }
  }
}

newInstance()会调用类的默认构造函数, 而Integer类没有默认构造函数.

3. 使用ArrayList来代替泛型数组:

public class ListOfGenerics<T> {
  private List<T> arr = new ArrayList<>();
  public void add(T item) { array.add(item); }
  public T get(int index) { return array.get(index); }
}

数组之因此不能够泛型在于: 数组的自己特性要求它们必须知道元素的类型和元素的个数(这样数组能够进行分配肯定的内存空间大小). 可是擦除的存在致使运行时候并不知道其元素的类型, 而数组并无动态建立的能力(由于ArrayList有, 因此才用ArrayList来解决泛型数组所遇到的难题).

 

通配符

相关文章
相关标签/搜索