本文首发于个人网站:Java中的泛型(二)
泛型可以应用于同一个类,该类可以针对多种类型使用,例如构建一个RedisTemplateService组件,用于处理当前应用中所有对象的缓存操作。这篇文章主要介绍泛型应用于接口、方法和匿名内部类的一些知识点和使用案例,也包括《Java编程思想》中对应的练习题的解读。
泛型接口
泛型应用于接口,是工厂方法设计模式的一种应用。我使用《Java编程思想》中的例子进行了练习。
下面这个例子中,CoffeeGenerator用于生成随机的Coffee对象。
package org.java.learn.generics.coffee;import org.apache.commons.lang3.RandomUtils;import org.java.learn.util.Generator;import java.util.Iterator;/*** 实现Iterable接口,表示当前类可以用在循环语句中** 作用: User: duqi Date: 2017/11/30 Time: 22:58*/public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {private Class[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};private int size = 0;public CoffeeGenerator() {}/*** 末端哨兵,在case2中for-each语句中,告诉程序什么时候停止* @param size*/public CoffeeGenerator(int size) {this.size = size;}@Overridepublic Coffee next() {try {return (Coffee) types[RandomUtils.nextInt(0, types.length-1)].newInstance();} catch (Exception e) {throw new RuntimeException();}}class CoffeeIterator implements Iterator<Coffee> {/*** 内部类可以直接访问外部类的属性*/int count = size;@Overridepublic boolean hasNext() {return count > 0;}@Overridepublic Coffee next() {count--;return CoffeeGenerator.this.next();}@Overridepublic void remove() {throw new UnsupportedOperationException();}}@Overridepublic Iterator<Coffee> iterator() {return new CoffeeIterator();}public static void main(String[] args) {//case1:测试CoffeeGenerator的next()方法;CoffeeGenerator gen = new CoffeeGenerator();for (int i = 0; i < 5; i++) {System.out.println(gen.next());}//case2: 测试在for-each语句中生成对象for (Coffee coffee: new CoffeeGenerator(5)) {System.out.println(coffee);}}}
再看一个例子,使用Generator接口生成Fibonacci数列。
package org.java.learn.generics.coffee;import org.java.learn.util.Generator;/*** 作用: User: duqi Date: 2017/12/2 Time: 13:59*/public class Fibonacci implements Generator<Integer> {private int count = 0; //全部是int基本类型,但是类型参数是Integer@Overridepublic Integer next() {return fib(count++);}private int fib(int n) {if (n < 2) {return 1;}return fib(n - 2) + fib(n - 1);}public static void main(String[] args) {Fibonacci fibonacci = new Fibonacci();for (int i = 0; i < 18; i++) {System.out.print(fibonacci.next() + " ");}}}
如果希望将这个Fibonacci生成器用于循环语句,书中的例子用的是继承Fibonacci,写一个IterableFibonacci类,该类实现了Iterable接口。在练习7中,作者提示可以使用“组合代替继承”实现同样的功能,我尝试自己做了下,这是我的实现:
package org.java.learn.generics;import java.util.Iterator;/*** 组合代替继承,实现适配器模式** IterableFibonacci2适配Fibonacci为可被循环语句使用的生成器** 作用: User: duqi Date: 2017/12/2 Time: 14:08*/public class IterableFibonacci2 implements Iterable<Integer> {//末端哨兵private int n;private Fibonacci fibonacci; //组合代替继承public IterableFibonacci2(int n, Fibonacci fibonacci) {this.n = n;this.fibonacci = fibonacci;}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {@Overridepublic boolean hasNext() {return n > 0;}@Overridepublic Integer next() {n--;return fibonacci.next();}@Overridepublic void remove() {throw new UnsupportedOperationException();}};}public static void main(String[] args) {for (int i: new IterableFibonacci2(18, new Fibonacci())) {System.out.print(i + " ");}}}
泛型方法
知识点总结
- 如果使用泛型方法可以取代将整个类(或接口)泛型化,那么就应该只使用泛型方法;
- static方法要使用泛型能力,就必须成为泛型方法;
类型推断:这是编译器的特性。在使用泛型类的时候,必须在创建对象的时候指定类型参数的值,但是在使用泛型方法时候,不必指明参数类型。
- 类型推断只对赋值操作有效
- 泛型方法与可变参数可以一起使用
例子1:使用Generator的泛型方法
package org.java.learn.generics;import org.java.learn.generics.coffee.Coffee;import org.java.learn.generics.coffee.CoffeeGenerator;import org.java.learn.util.Generator;import java.util.ArrayList;import java.util.Collection;/*** 作用: User: duqi Date: 2017/12/2 Time: 14:58*/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<Coffee> coffees = fill(new ArrayList<>(), new CoffeeGenerator(), 4);for (Coffee coffee : coffees) {System.out.println(coffee);}Collection<Integer> numers = fill(new ArrayList<>(), new Fibonacci(), 12);for (int i : numers) {System.out.print(i + " ");}}}
例子2:一个通用的Generator
下面这个例子,是一个通用的生成器,只需要传入指定的类型,就可以生成对应类型的对象。
package org.java.learn.util;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:04*/public class BasicGenerator<T> implements Generator<T> {private Class<T> type;public BasicGenerator(Class<T> type) {this.type = type;}@Overridepublic T next() {try {return type.newInstance();} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}public static <T> Generator<T> create(Class<T> type) {return new BasicGenerator<>(type);}}
上面这段代码可以创建什么类的对象呢?(1)public的类;(2)含有默认构造器的类;这里给出一个例子:
package org.java.learn.generics;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:10*/public class CountedObject {private static long counter = 0;private final long id = counter++;public long getId() {return id;}@Overridepublic String toString() {return "CounteredObject " + id;}}
然后再给出一个使用上述构造器的例子,书中的例子是使用BasicGenerator的create()方法,我这里还实现了练习14中提到的方法,参见:
package org.java.learn.generics;import org.java.learn.util.BasicGenerator;import org.java.learn.util.Generator;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:11*/public class BasicGeneratorDemo {public static void main(String[] args) {Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class);for (int i = 0; i < 5; i++) {System.out.println(gen.next());}//练习14:不使用create方法,使用显式的构造器Generator<CountedObject> generator = new BasicGenerator<>(CountedObject.class);for (int i = 0; i < 5; i++) {System.out.println(generator.next());}}}
例子3:简化元组的使用
之前的一篇文章里,已经实现过TwoTuple、ThreeTuple等工具类,但是使用的时候还不太方便,这里利用泛型方法实现一个工具类,可以简化元组的使用。
package org.java.learn.util.tuple;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:21*/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 <A, B, C, D> FourTuple<A, B, C, D> tuple(A a, B b, C c, D d) {return new FourTuple<>(a, b, c, d);}}
这个工具类的使用例子如下:
package org.java.learn.util.tuple;import org.java.learn.generics.coffee.Breve;import org.java.learn.generics.coffee.Cappuccino;import static org.java.learn.util.tuple.Tuple.*;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:24*/public class TupleTest {static TwoTuple<String, Integer> f() {return tuple("hi", 47);}static TwoTuple f2() {return tuple("hi", 47);}static ThreeTuple<Breve, String, Integer> g() {return tuple(new Breve(), "hi", 44);}static FourTuple<Cappuccino, Breve, String, Integer> h() {return tuple(new Cappuccino(), new Breve(), "hi", 447);}public static void main(String[] args) {TwoTuple<String, Integer> ttsi = f();System.out.println(ttsi);/*** 这里没有发出告警,是因为我们将f2()的返回值直接返回,并没有再尝试转为参数化对象;*/System.out.println(f2());System.out.println(g());System.out.println(h());/*** 练习15:这里尝试将f2的返回值转为一个参数化对象,就收到了报警*/TwoTuple<String, Integer> ttsi2 = f2();}}
例子4:一个Set实用工具
书中提供了一个Sets工具类,用于实现常用的集合操作,代码如下:
package org.java.learn.util;import java.util.HashSet;import java.util.Set;/*** 作用: User: duqi Date: 2017/12/2 Time: 15:40*/public class Sets {/*** A和B的并集* @param a* @param b* @param <T>* @return*/public static <T> Set<T> union(Set<T> a, Set<T> b) {Set<T> result = new HashSet<>(a);result.addAll(b);return result;}/*** A和B的交集* @param a* @param b* @param <T>* @return*/public static <T> Set<T> intersection(Set<T> a, Set<T> b) {Set<T> result = new HashSet<>(a);result.retainAll(b);return result;}/*** A和B的差集,将A中移除B中的元素* @param superset* @param subset* @param <T>* @return*/public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {Set<T> result = new HashSet<>(superset);result.removeAll(subset);return result;}/*** A和B中所有的元素,减去A和B的交集,剩下的元素* @param a* @param b* @param <T>* @return*/public static <T> Set<T> complement(Set<T> a, Set<T> b) {return difference(union(a, b), intersection(a, b));}}
# 总结
- 本节涉及的知识点:泛型接口、泛型方法
- 本节练习用的代码:LearnJava
本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。

