基本定义
- 泛型实现了参数化类型的概念,是代码可以应用于多种类型。“泛型”这个术语的意思是:“应用于许多许多的类型”
- 在你创建参数化类型的一个实例时,编译器会为你负责转型操作,并且保证类型的正确性,这应该是一个进步
简单的泛型
-
当创建
Holder3
对象时,必须指明想要持有什么类型的对象,将其置于尖括号内。 -
在存入和取出指定对象的时候,就会自动选择该类型。
public class Holder3<T> { private T a; public Holder3(T a) { this.a = a; } public T getA() { return a; } public void setA(T a) { this.a = a; } public static void main(String[] args) { Holder3<Fruit> fruitHolder3 = new Holder3<Fruit>(new Fruit()); Fruit a = fruitHolder3.getA(); // 无需强转,直接使用 } } class Fruit { String name; }
-
自定义一个元组类库,类似于python中的元组概念。无法修改,但可以一次性返回多个值。
public class TwoTuple<A, B> { public final A first; public final B second; public TwoTuple(A first, B second) { this.first = first; this.second = second; } @Override public String toString() { return "(" + first + "," + second + ")"; } public static void main(String[] args) { TwoTuple<Fruit, Fruit> fruitFruitTwoTuple = new TwoTuple<>(new Fruit(), new Fruit()); // 使用 } }
利用
final
声明对象,因此无法用户无法修改该对象,因此直接使用public
使用即可public class ThreeTuple<A, B, C> extends TwoTuple<A, B> { public final C third; public ThreeTuple(A first, B second, C third) { super(first, second); this.third = third; } @Override public String toString() { return "(" + first + "," + second + "," + third + ")"; } public static void main(String[] args) { ThreeTuple<Integer, Integer, Integer> tuple = new ThreeTuple<>(1, 2, 3); System.out.println(tuple); } }
-
一个堆栈类,自己实现
stack
public class LinkedStack<T> { // 内部类,节点 private class Node<U> { U item; Node<U> next; // 下一个节点 // 构造器,设置值及下一个节点 public Node(U item, Node<U> next) { this.item = item; this.next = next; } public Node() { this.item = null; this.next = null; } //判断是否为空,即最后一个节点 boolean end() { return item == null && next == null; } } private Node<T> top = new Node<>(); // top指向末端哨兵。后面top一直往后移动,指向栈顶。该节点就是一直存在于栈底,叫做末端哨兵 public void push(T item) { top = new Node<T>(item, top); // 新节点指向栈顶。top移动,指向最新节点,即栈顶 } public T pop() { T item = top.item; // 获取当前栈顶元素 if (!top.end()) // 如果不是最后一个,那么还有会next,将top移动到next top = top.next; return item; // 返回当前栈顶元素 } public static void main(String[] args) { LinkedStack<String> stack = new LinkedStack<>(); for (String s : "hello world ni hao shi jie".split(" ")) { stack.push(s); // 遍历push } String s; // 遍历pop,直到末端哨兵 while ((s = stack.pop()) != null) { System.out.println(s); } } }
使用末端哨兵来判断堆栈何时为空。
每次
push()
,就会创建一个Node<T>
对象,并将其链接到前一个Node对象。每次
pop()
,总会返回top.item()
,然后丢弃当前top
所指的Node<T>
,并将top
转移到下一个Node<T>
,除非已经碰到了末端哨兵。
泛型接口
-
泛型也可以应用于接口
-
生成器(generator),专门负责创建对象的类,工厂方法设计模式的一种应用
public interface Generator<T> { // 生成一个T T next(); }
public abstract class Coffee { private static long counter = 0; private final long id = counter++; // 每杯咖啡都有一个独特的id @Override public String toString() { return getClass().getSimpleName() + " " + id; } }
// Coffee的实现类 public class Cappuccino extends Coffee { } // .... 更多实现类
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> { private final Class<?>[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class}; private static Random rand = new Random(47); private int size = 0; public CoffeeGenerator() { } public CoffeeGenerator(int size) { this.size = size; } /** * 随机生成一个咖啡实例 * @return 咖啡 */ @Override public Coffee next() { try { return (Coffee)types[rand.nextInt(types.length)].getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } // Coffee迭代器 class CoffeeIterator implements Iterator<Coffee> { int count = size; // 数量为size个数 @Override public boolean hasNext() { return count > 0; } @Override public Coffee next() { count--; return CoffeeGenerator.this.next(); // 该实例化对象的next()方法 } } @Override public Iterator<Coffee> iterator() { return new CoffeeIterator(); } public static void main(String[] args) { CoffeeGenerator gen = new CoffeeGenerator(5); System.out.println(gen.next()); System.out.println(gen.next()); System.out.println(gen.next()); System.out.println(gen.next()); System.out.println(gen.next()); System.out.println(gen.next()); // 迭代器 Iterator<Coffee> iterator = gen.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
-
斐波拉契数列生成器
public class Fabonacci implements Generator<Integer> { private int count = 0; private Map<Integer, Integer> record = new HashMap<>(); // 为了防止重复工作,将已经计算过的结果保存在HashMap中,后面递归的时候直接调用就好了 public Fabonacci() { record.put(0, 1); record.put(1, 1); } @Override public Integer next() { return fib(count++); } private int fib(int n) { if (n < 2) return record.get(n); if (!record.containsKey(n)) record.put(n, fib(n - 1) + fib(n - 2)); // 若尚未存在该数字的结果,则通过计算获得,并保存在Map中 return record.get(n); } public static void main(String[] args) { Fabonacci fabonacci = new Fabonacci(); for (int i = 0; i < 100; i++) { System.out.println(fabonacci.next()); } } }
但是,目前没有在代码中添加迭代器,为了添加迭代器,又不修改代码本身,可以通过适配器设计模式(adapter)实现。具体来说,是类适配器模式
public class IterableFabonacci extends Fabonacci implements Iterable<Integer> { private int maxSize; // 边界值 public IterableFabonacci(int maxSize) { this.maxSize = maxSize; } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { @Override public boolean hasNext() { return maxSize > 0; } @Override public Integer next() { maxSize--; return IterableFabonacci.this.next(); // 持续返回下一个值 } }; } public static void main(String[] args) { Iterator<Integer> iterator = new IterableFabonacci(10).iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
泛型方法
-
泛型方法使得该方法能够独立于类而产生变化。
-
无论何时,只要你能做到,就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。
-
对于一个
static
的方法而言,无法访问泛型类的类型参数,所以,如果static
方法需要使用泛型能力,就必须使其成为泛型方法。(也就是说,泛型类的泛型参数是给对象用的,不实例化没办法泛型参数。除非static
方法自己就是泛型方法)public class GenericMethods { /** * 功能,获取参数的类型的名称 * @param x * @param <T> */ public <T> void f(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] args) { GenericMethods gm = new GenericMethods(); //无需指定类型参数 gm.f(""); // java.lang.String gm.f(1); // java.lang.Integer gm.f(1.0); // java.lang.Double gm.f(1.0f); // java.lang.Float gm.f('c'); // java.lang.Character gm.f(gm); // chapter15.generator.GenericMethods } }
-
当使用泛型类时,必须在创建对象的时候指定类型参数的值。
-
在使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这成为类型参数推断(type argument inference)。因此,我们可以像调用普通方法一样调用
f()
,就像f()
被无限次重载过。 -
如果调用
f()
时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。 -
杠杆利用类型参数推断
public class New { public static <K, T> Map<K, T> map() { return new HashMap<>(); } public static <T> List<T> list() { return new ArrayList<>(); } public static void main(String[] args) { Map<Integer, Integer> map = New.map(); } }
-
可变参数与泛型方法
public class GenericVarargs { 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<Integer> list = makeList(1, 2, 3, 4, 5, 6); System.out.println(list); List<? extends Serializable> a = makeList(1, "a"); System.out.println(a); List<? extends Number> numbers = makeList(1, 1.0); System.out.println(numbers); } }
它会自动找到输入内容的最小的公共基类/接口,作为接受的类型参数
-
利用生成器,填充一个
Collection
public class Generators { /** * 根据生成器,给指定的集合填充n个值 * @param coll * @param gen * @param n * @param <T> * @return */ 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<Coffee> coffees = fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 10); for (Coffee coffee : coffees) { System.out.println(coffee); } } }
-
一个通用的
Generator
。很方便就可以创建出指定数目的beanpublic class BasicGenerator<T> implements Generator<T> { private Class<T> type; public BasicGenerator(Class<T> type) { this.type = type; } @Override public T next() { try { return type.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } public static <T> Generator<T> create(Class<T> type) { return new BasicGenerator<T>(type); } public static void main(String[] args) { Generator<Mocha> mochaGenerator = BasicGenerator.create(Mocha.class); for (int i = 0; i < 10; i++) { System.out.println(mochaGenerator.next()); } } }
-
简化元组的使用
public class Tuple { public static <A, B> TwoTuple<A, B> tuple(A a, B b) { return new TwoTuple<>(a, b); } public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c) { return new ThreeTuple<>(a, b, c); } public static void main(String[] args) { ThreeTuple<Integer, Integer, Integer> tuple = tuple(1, 2, 3); System.out.println(tuple); } }
将
Tuple
包装成一个重载的泛化方法tuple
,根据不同的输入参数,构造不同的Tuple
匿名内部类
- 匿名内部类
public class Customer { private static long counter = 1; private final long id = counter++; public Customer() { } @Override public String toString() { return "Customer{" + "id=" + id + '}'; } public static Generator<Customer> generator() { // 匿名内部类 return new Generator<Customer>() { @Override public Customer next() { return new Customer(); } }; } }
擦除的神秘之处
-
在泛型代码内部,无法获得任何有关泛型参数类型的信息
-
Java泛型是使用擦除来实现的,这意味着当你使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此
List<String>
和List<Integer>
在运行时事实上是相同的类型。Class cls = new ArrayList<Integer>().getClass(); Class cls2 = new ArrayList<String>().getClass(); System.out.println(cls == cls2); // true
-
有了擦除,Java编译器无法知道当前类型参数,也无法知道该泛型是否具有某个属性或方法,因此以下操作会造成编译错误
class Manipulator<T> { private T obj; public Manipulator(T x) {obj = x;} public void manipulate() {obj.f();} // 会造成编译错误,即使另一个类的确含有该方法 }
为了调用
f()
,我们必须协助泛型类,给定泛型类的边界,以此告诫编译器只能接受遵循这个边界的类型。这里重用了extends
关键字class Manipulator2<T extends HasF> { // 给定边界,且该边界含有下面使用的属性或方法 private T obj; public Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } // 编译通过 }
-
即使kind被存储为Class,擦除也意味着它实际将被存储为Class,没有任何参数,因此当你使用它时,例如在创建数组时,Array.newInstance()实际上并未拥有kind所蕴含的类型信息,因此这不会产生具体的结果,所以必须转型
-
创建一个泛型数组
public class ArrayMaker<T> { private Class<T> kind; public ArrayMaker(Class<T> kind) { this.kind = kind; } @SuppressWarnings("unchecked") T[] create(int size) { return (T[]) Array.newInstance(kind, size); // 创建泛型数组的方式 } public static void main(String[] args) { ArrayMaker<String> maker = new ArrayMaker<>(String.class); String[] strings = maker.create(10); System.out.println(Arrays.toString(strings)); strings[0] = "nihao"; System.out.println(Arrays.toString(strings)); } }
擦除的补偿
-
任何在运行时需要知道确切类型信息的操作都将无法工作
public class Erased<T> { private final static int SIZE = 100; public void f(Object arg) { if (arg instanceof T) { } // ERROR T t = new T(); // ERROR T[] array = new T[SIZE]; // ERROR T[] ts = (T[]) new Object[SIZE]; // warning } }
-
使用
instanceof
的尝试最终失败了,因为其类型信息已经被擦除了。如果引入类型标签,就可以转而使用动态的isInstance()
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<List> listClassTypeCapture = new ClassTypeCapture<List>(List.class); System.out.println(listClassTypeCapture.f(new ArrayList<>())); } }
-
创建泛型实例
new T(); // 会发生错误
Java中的解决方案是,传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以用
newInstance()
来创建这个类型的新对象。public class InstantiateGenericType { public static void main(String[] args) { try { ClassAsFactory<Employee> factory = new ClassAsFactory<>(Employee.class); } catch (RuntimeException e) { e.printStackTrace(); } } } // bean class Employee { } // 工厂 class ClassAsFactory<T> { T x; public ClassAsFactory(Class<T> kind) { try { x = kind.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } }
-
泛型数组