本文首发于个人网站:Java中的泛型(二)

泛型可以应用于同一个类,该类可以针对多种类型使用,例如构建一个RedisTemplateService组件,用于处理当前应用中所有对象的缓存操作。这篇文章主要介绍泛型应用于接口、方法和匿名内部类的一些知识点和使用案例,也包括《Java编程思想》中对应的练习题的解读。

泛型接口

泛型应用于接口,是工厂方法设计模式的一种应用。我使用《Java编程思想》中的例子进行了练习。

下面这个例子中,CoffeeGenerator用于生成随机的Coffee对象。

  1. package org.java.learn.generics.coffee;
  2. import org.apache.commons.lang3.RandomUtils;
  3. import org.java.learn.util.Generator;
  4. import java.util.Iterator;
  5. /**
  6. * 实现Iterable接口,表示当前类可以用在循环语句中
  7. *
  8. * 作用: User: duqi Date: 2017/11/30 Time: 22:58
  9. */
  10. public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
  11. private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
  12. private int size = 0;
  13. public CoffeeGenerator() {
  14. }
  15. /**
  16. * 末端哨兵,在case2中for-each语句中,告诉程序什么时候停止
  17. * @param size
  18. */
  19. public CoffeeGenerator(int size) {
  20. this.size = size;
  21. }
  22. @Override
  23. public Coffee next() {
  24. try {
  25. return (Coffee) types[RandomUtils.nextInt(0, types.length-1)].newInstance();
  26. } catch (Exception e) {
  27. throw new RuntimeException();
  28. }
  29. }
  30. class CoffeeIterator implements Iterator<Coffee> {
  31. /**
  32. * 内部类可以直接访问外部类的属性
  33. */
  34. int count = size;
  35. @Override
  36. public boolean hasNext() {
  37. return count > 0;
  38. }
  39. @Override
  40. public Coffee next() {
  41. count--;
  42. return CoffeeGenerator.this.next();
  43. }
  44. @Override
  45. public void remove() {
  46. throw new UnsupportedOperationException();
  47. }
  48. }
  49. @Override
  50. public Iterator<Coffee> iterator() {
  51. return new CoffeeIterator();
  52. }
  53. public static void main(String[] args) {
  54. //case1:测试CoffeeGenerator的next()方法;
  55. CoffeeGenerator gen = new CoffeeGenerator();
  56. for (int i = 0; i < 5; i++) {
  57. System.out.println(gen.next());
  58. }
  59. //case2: 测试在for-each语句中生成对象
  60. for (Coffee coffee: new CoffeeGenerator(5)) {
  61. System.out.println(coffee);
  62. }
  63. }
  64. }

再看一个例子,使用Generator接口生成Fibonacci数列。

  1. package org.java.learn.generics.coffee;
  2. import org.java.learn.util.Generator;
  3. /**
  4. * 作用: User: duqi Date: 2017/12/2 Time: 13:59
  5. */
  6. public class Fibonacci implements Generator<Integer> {
  7. private int count = 0; //全部是int基本类型,但是类型参数是Integer
  8. @Override
  9. public Integer next() {
  10. return fib(count++);
  11. }
  12. private int fib(int n) {
  13. if (n < 2) {
  14. return 1;
  15. }
  16. return fib(n - 2) + fib(n - 1);
  17. }
  18. public static void main(String[] args) {
  19. Fibonacci fibonacci = new Fibonacci();
  20. for (int i = 0; i < 18; i++) {
  21. System.out.print(fibonacci.next() + " ");
  22. }
  23. }
  24. }

如果希望将这个Fibonacci生成器用于循环语句,书中的例子用的是继承Fibonacci,写一个IterableFibonacci类,该类实现了Iterable接口。在练习7中,作者提示可以使用“组合代替继承”实现同样的功能,我尝试自己做了下,这是我的实现:

  1. package org.java.learn.generics;
  2. import java.util.Iterator;
  3. /**
  4. * 组合代替继承,实现适配器模式
  5. *
  6. * IterableFibonacci2适配Fibonacci为可被循环语句使用的生成器
  7. *
  8. * 作用: User: duqi Date: 2017/12/2 Time: 14:08
  9. */
  10. public class IterableFibonacci2 implements Iterable<Integer> {
  11. //末端哨兵
  12. private int n;
  13. private Fibonacci fibonacci; //组合代替继承
  14. public IterableFibonacci2(int n, Fibonacci fibonacci) {
  15. this.n = n;
  16. this.fibonacci = fibonacci;
  17. }
  18. @Override
  19. public Iterator<Integer> iterator() {
  20. return new Iterator<Integer>() {
  21. @Override
  22. public boolean hasNext() {
  23. return n > 0;
  24. }
  25. @Override
  26. public Integer next() {
  27. n--;
  28. return fibonacci.next();
  29. }
  30. @Override
  31. public void remove() {
  32. throw new UnsupportedOperationException();
  33. }
  34. };
  35. }
  36. public static void main(String[] args) {
  37. for (int i: new IterableFibonacci2(18, new Fibonacci())) {
  38. System.out.print(i + " ");
  39. }
  40. }
  41. }
  • 上面三个例子中,提到了两种设计模式:工厂方法设计模式适配器模式
  • 在泛型中,基本类型无法作为类型参数,但是Java提供了自动打包和拆包的功能;

泛型方法

知识点总结

  • 如果使用泛型方法可以取代将整个类(或接口)泛型化,那么就应该只使用泛型方法;
  • static方法要使用泛型能力,就必须成为泛型方法;
  • 类型推断:这是编译器的特性。在使用泛型类的时候,必须在创建对象的时候指定类型参数的值,但是在使用泛型方法时候,不必指明参数类型。

    • 类型推断只对赋值操作有效
  • 泛型方法与可变参数可以一起使用

例子1:使用Generator的泛型方法

  1. package org.java.learn.generics;
  2. import org.java.learn.generics.coffee.Coffee;
  3. import org.java.learn.generics.coffee.CoffeeGenerator;
  4. import org.java.learn.util.Generator;
  5. import java.util.ArrayList;
  6. import java.util.Collection;
  7. /**
  8. * 作用: User: duqi Date: 2017/12/2 Time: 14:58
  9. */
  10. public class Generators {
  11. /**
  12. * 泛型方法的定义格式——将泛型参数列表放在方法的返回值左面
  13. */
  14. public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
  15. for (int i = 0; i < n; i++) {
  16. coll.add(gen.next());
  17. }
  18. return coll;
  19. }
  20. public static void main(String[] args) {
  21. Collection<Coffee> coffees = fill(new ArrayList<>(), new CoffeeGenerator(), 4);
  22. for (Coffee coffee : coffees) {
  23. System.out.println(coffee);
  24. }
  25. Collection<Integer> numers = fill(new ArrayList<>(), new Fibonacci(), 12);
  26. for (int i : numers) {
  27. System.out.print(i + " ");
  28. }
  29. }
  30. }

例子2:一个通用的Generator

下面这个例子,是一个通用的生成器,只需要传入指定的类型,就可以生成对应类型的对象。

  1. package org.java.learn.util;
  2. /**
  3. * 作用: User: duqi Date: 2017/12/2 Time: 15:04
  4. */
  5. public class BasicGenerator<T> implements Generator<T> {
  6. private Class<T> type;
  7. public BasicGenerator(Class<T> type) {
  8. this.type = type;
  9. }
  10. @Override
  11. public T next() {
  12. try {
  13. return type.newInstance();
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. throw new RuntimeException(e);
  17. }
  18. }
  19. public static <T> Generator<T> create(Class<T> type) {
  20. return new BasicGenerator<>(type);
  21. }
  22. }

上面这段代码可以创建什么类的对象呢?(1)public的类;(2)含有默认构造器的类;这里给出一个例子:

  1. package org.java.learn.generics;
  2. /**
  3. * 作用: User: duqi Date: 2017/12/2 Time: 15:10
  4. */
  5. public class CountedObject {
  6. private static long counter = 0;
  7. private final long id = counter++;
  8. public long getId() {
  9. return id;
  10. }
  11. @Override
  12. public String toString() {
  13. return "CounteredObject " + id;
  14. }
  15. }

然后再给出一个使用上述构造器的例子,书中的例子是使用BasicGenerator的create()方法,我这里还实现了练习14中提到的方法,参见:

  1. package org.java.learn.generics;
  2. import org.java.learn.util.BasicGenerator;
  3. import org.java.learn.util.Generator;
  4. /**
  5. * 作用: User: duqi Date: 2017/12/2 Time: 15:11
  6. */
  7. public class BasicGeneratorDemo {
  8. public static void main(String[] args) {
  9. Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);
  10. for (int i = 0; i < 5; i++) {
  11. System.out.println(gen.next());
  12. }
  13. //练习14:不使用create方法,使用显式的构造器
  14. Generator<CountedObject> generator = new BasicGenerator<>(CountedObject.class);
  15. for (int i = 0; i < 5; i++) {
  16. System.out.println(generator.next());
  17. }
  18. }
  19. }

例子3:简化元组的使用

之前的一篇文章里,已经实现过TwoTuple、ThreeTuple等工具类,但是使用的时候还不太方便,这里利用泛型方法实现一个工具类,可以简化元组的使用。

  1. package org.java.learn.util.tuple;
  2. /**
  3. * 作用: User: duqi Date: 2017/12/2 Time: 15:21
  4. */
  5. public class Tuple {
  6. public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
  7. return new TwoTuple<>(a, b);
  8. }
  9. public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c) {
  10. return new ThreeTuple<>(a, b, c);
  11. }
  12. public static <A, B, C, D> FourTuple<A, B, C, D> tuple(A a, B b, C c, D d) {
  13. return new FourTuple<>(a, b, c, d);
  14. }
  15. }

这个工具类的使用例子如下:

  1. package org.java.learn.util.tuple;
  2. import org.java.learn.generics.coffee.Breve;
  3. import org.java.learn.generics.coffee.Cappuccino;
  4. import static org.java.learn.util.tuple.Tuple.*;
  5. /**
  6. * 作用: User: duqi Date: 2017/12/2 Time: 15:24
  7. */
  8. public class TupleTest {
  9. static TwoTuple<String, Integer> f() {
  10. return tuple("hi", 47);
  11. }
  12. static TwoTuple f2() {
  13. return tuple("hi", 47);
  14. }
  15. static ThreeTuple<Breve, String, Integer> g() {
  16. return tuple(new Breve(), "hi", 44);
  17. }
  18. static FourTuple<Cappuccino, Breve, String, Integer> h() {
  19. return tuple(new Cappuccino(), new Breve(), "hi", 447);
  20. }
  21. public static void main(String[] args) {
  22. TwoTuple<String, Integer> ttsi = f();
  23. System.out.println(ttsi);
  24. /**
  25. * 这里没有发出告警,是因为我们将f2()的返回值直接返回,并没有再尝试转为参数化对象;
  26. */
  27. System.out.println(f2());
  28. System.out.println(g());
  29. System.out.println(h());
  30. /**
  31. * 练习15:这里尝试将f2的返回值转为一个参数化对象,就收到了报警
  32. */
  33. TwoTuple<String, Integer> ttsi2 = f2();
  34. }
  35. }

例子4:一个Set实用工具

书中提供了一个Sets工具类,用于实现常用的集合操作,代码如下:

  1. package org.java.learn.util;
  2. import java.util.HashSet;
  3. import java.util.Set;
  4. /**
  5. * 作用: User: duqi Date: 2017/12/2 Time: 15:40
  6. */
  7. public class Sets {
  8. /**
  9. * A和B的并集
  10. * @param a
  11. * @param b
  12. * @param <T>
  13. * @return
  14. */
  15. public static <T> Set<T> union(Set<T> a, Set<T> b) {
  16. Set<T> result = new HashSet<>(a);
  17. result.addAll(b);
  18. return result;
  19. }
  20. /**
  21. * A和B的交集
  22. * @param a
  23. * @param b
  24. * @param <T>
  25. * @return
  26. */
  27. public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
  28. Set<T> result = new HashSet<>(a);
  29. result.retainAll(b);
  30. return result;
  31. }
  32. /**
  33. * A和B的差集,将A中移除B中的元素
  34. * @param superset
  35. * @param subset
  36. * @param <T>
  37. * @return
  38. */
  39. public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
  40. Set<T> result = new HashSet<>(superset);
  41. result.removeAll(subset);
  42. return result;
  43. }
  44. /**
  45. * A和B中所有的元素,减去A和B的交集,剩下的元素
  46. * @param a
  47. * @param b
  48. * @param <T>
  49. * @return
  50. */
  51. public static <T> Set<T> complement(Set<T> a, Set<T> b) {
  52. return difference(union(a, b), intersection(a, b));
  53. }
  54. }

# 总结

  1. 本节涉及的知识点:泛型接口、泛型方法
  2. 本节练习用的代码:LearnJava

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
Java泛型基础(二):泛型接口 - 图1