一般的类型和方法, 只能使用具体的类型: 要么是基本类型, 要么是自定义的类. 如果要编写可以应用于多种类型的代码, 这种刻板的
限制对代码的束缚就会很大
在面向对象编程中, 多态算是一种泛化机制, 你可以在方法的参数里使用基类的引用, 那么这个方法就可以使用这个基类的任何派生
类做参数, 如果是一个接口做参数, 限制就更小, 甚至可以写一个还不存在的类, 客户端程序员可以自己实现一个接口的实现类来满足
类或方法
但接口的限制还是太大了, 一旦指明了接口, 就必须用某种特定的接口或其实现类, 我们想要的是, 某种不具体的类型, 而不是某个具
体的类或接口
泛型实现了参数化类型的概念
泛型这个术语的意思是, 适用于许多许多的类型
- 简单泛型
把成员设置成Object类型的,初始化,赋值,取值的时候可以用不同的类型来定义这个成员
public class A {
private Object a;
A(Object a) { this.a = a; }
public void set(Object a) { this.a = a; }
public Object get() { return a; }
}
使用类型参数T, 声明类的时候写在类名后面<T>
public class B<T> {
private T a;
A(T a) { this.a = a; }
public void set(T a) { this.a = a; }
public T get() { return a; }
}
这就是Java泛型的核心概念: 告诉编译器想使用什么样的类型, 然后编译器来处理一切的细节
一般我们可以把泛型看成和其他类型差不多, 只不过多了个类型参数
元组是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中的元素, 但不允许存入新元素
元组可以取任意长度, 每个对象可以是不同的类型, 声明对象时, 我们希望确定每一个元组中元素的具体类型
class X<A,B>
class Y<A,B,C> extends X<A,B>
static X<String,Integer> f() { return new X<String,Integer>("str",12); }
泛型实现链表型栈
class Stack<T> {
private class Node<U> {
U item;
Node<U> next;
Node() { this.item = null; this.next = null; }
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
public 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;
}
}
每个Stack对象里有一个节点Node,里面存了一个泛型的数值和一个表示下一个节点的对象
- 泛型接口
生成器是一种创建对象的类, 属于工厂设计模式, 但它不需要参数
generator
public interface Generator<T> { T next(); }
public class CoffeeGenerator implements Generator<Coffee> {
public Coffee next() {
return (Coffee)types[rand.nextInt(types.length)].newInstatnce();
}
}
- 泛型方法
public <T> f(T x) {}
可以在类中包含参数化方法, 泛型方法, 与其所在的类是否是泛型没有关系
无论何时, 只要你能做到, 你就应该尽量使用泛型方法, 如果只用泛型方法就可以, 没必要在类上加泛型
泛型类在自己创建对象时, 必须指定一个具体的类型, 泛型方法则不需要,
编译器会根据你传递进来的参数为你找出具体的类型, 这叫做类型参数推断
class New {
public static <U,V> Map<U,V> map() { return new HashMap<U,V>(); }
}
Map<String,List<String>> map = New.map();
类型参数推断可以用于简化这种容器类型的创建
但是它只能用于赋值操作, 如果把它当做方法中的参数, 是不能自动识别成方法中所需要的参数的
在泛型方法中, 可以显式地指明类型
public static void f(Map<String,List<String> map) {}
f(New.<String,List<String>.map());
一个通用的生成器, 可以通过泛型方法实现, 传入一个类型的class对象, 从而创建这个类型的对象
public class BasicGenerator<T> implements Generator<T> {
private Class<T> type;
public BasicGenerator(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 BasicGenerator<T>(type);
}
}
public void test() {
Generator<X> gen = BasicGenerator.create(X);
for(int i = 0;i < 5;i++)
print(gen.next());
}
}
匿名内部类
泛型还可以运用于内部类以及匿名内部类
class Customer {
private Customer() {}
public static Generator<Customer> generator() {
return new Generator<Customer>() {
public Customer next() { return new Customer(); }
}
}
}
- 擦除
在泛型代码内部, 无法获得任何有关泛型参数类型的信息
Java泛型是用擦除来实现的, 在使用泛型时, 任何的类型信息都被擦除了
因此List<String>和List<Integer>在运行时实际上是相同的类型
Class.getTypeParameters()可以返回一个TypeVariable对象数组, 表示泛型声明所声明的类型参数
但它只返回做参数占位符的标识符, 并不返回你实例化时传进去实际的类型参数orz
class Exam { public void f(); }
class Gen<T> {
private T obj;
Gen(T obj) { this.obj = obj; }
public void g() { obj.f(); }
//这里编译不通过, obj不知道它的类型的边界, 所以不能确定它可以调用f()方法
}
class Gen<? extends Exam> {
private T obj;
Gen(T obj) { this.obj = obj; }
public void g() { obj.f(); }
//这样就能通过了, <? extends Exam>把泛型的类型参数擦除到了它的第一个边界, 即Exam, 就可以调用Exam中的方法了
}
擦除减少了泛型的泛化性, 泛型类型被当作第二类类型处理, 即不能在某些重要的上下文环境中使用, 只有在静态类型检查期间才
出现
泛型不能用于显式地引用运行时类型的操作, 比如new, instanceof, 转型, 实际上
你只是看上去拥有有关参数的类型信息而已
class Fool<T> {
private T obj;
public T f() { return obj; }
}
这时的T对你来说, 就是一个Object, 没有更多了
泛型可以创建毫无意义的事物
private Class<T> kind;
T[] create(int size) {
return (T[])Array.newInstance(kind, size);
}
使用Array.newInstance()创建泛型数组是好的
- 擦除的补偿
1.使用类型标签创建对象
class ClassAsFactory<T> {
T x;
public ClassAsFactory((Class<T> kind)) {
try {
x = kind.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
使用Class.newInstance()为类型创建对象
这种方式, 如果用在像Integer这样没有默认构造器的类上就会出错
2.使用显式的工厂
为每种想要创建对象的类型实现工厂接口
interface Factory<T> {
T creat();
}
class IntegerFactory implements Factory<Integer> {
public Integer create() { return new Integer(0); }
}
class Foo<T> {
private T x;
public <F extends Factory<T>> Foo(F factory) {
x = factory.create();
}
}
new Foo<Integer> = new Foo(new IntegerFactory());
用相同的或派生类的工厂类对象去执行相应的create, 从而正确地产生对象
3.模板方法
abstract class GenericWithCreator<T> {
final T element;
GenericWithCreator() { element = create(); }
abstract T create();
}
class X {}
class Creator extends GenericWithCreator<X> {
X create() { return new X(); }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
泛型数组
直接返回的泛型将丢失类型, 必须加上类型标记
class A<T> {
private T[] array;
public A(Class<T> type, int sz) {
array = (T[])Array.newInstance(type, sz);
//这里的T[]在运行时是被擦除的,只剩下生成的Object[], 所以这里必须指定type, 使其具有特定类型
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
public T[] rep() { return array; }
- 边界
边界使得你可以在用于泛型的参数类型上设置限制条件, 你可以按照自己的边界类型来调用方法
如果将这个参数设定为某个类型子集, 就可以用这些类型子集来调用该类型下的方法, 访问它的成员
class Dimension (
int x;
public void weight() {}
}
public Color { public void color() {} }
class GenericDimension<T extends Dimension & color> {
T item;
public GenericDimension(T item) { this.item = item; }
public int getX() { return item.x; }
public void weight() { item.weight(); }
public void color() { item.color(); }
}
可以extends多个类型, 每个类型下的方法都可以调用
- 通配符
public class Holder<T> {
private T value;
public Holder() {}
public Holder(T val) { value = val; }
public void set(T val) { value = val; }
public T get() { return value; }
public boolean equals(Object obj) {
return value.equals(obj);
}
public static void main(String[] args) throws Exception {
Holder<Apple> Apple = new Holder<Apple>(new Apple());
//由于知道T是APPLE 可以get一个Apple类型的引用, 也可以向上转型为Fruit类型的引用
Apple d = Apple.get();
Fruit f = Apple.get();
//Orange o = Apple.get();
//由于知道T是APPLE 可以传入一个Apple类型的引用, 但不能传入Fruit类型
Apple.set(d);
//Apple.set(f);
//Apple是一个Holder<Apple>类型的引用 可以向上转型为一个Holder<? extends Fruit>型引用
Holder<? extends Fruit> fruit = Apple;
//T是 ? extends Fruit, 可以返回一个Fruit类型的引用
Fruit p = fruit.get();
//Apple a = fruit.get();
d = (Apple)fruit.get();
try {
Orange c = (Orange)fruit.get();
} catch(Exception e) {
System.out.println(e);
}
//T是 ? extends Fruit, set方法要接受一个T, 但不知道T具体的类型, 因为可以是任何名字
//fruit.set(new Apple());
//fruit.set(new Fruit());
System.out.println(fruit.equals(d));
}
}
逆变
<? super MyClass>通配符是由某个特定类的任何基类来决定的
public class GenericReading {
static <T> T readExact(List<T> list) {
return list.get(0);
}
static List<Apple> apples = Arrays.asList(new Apple());
static List<Fruit> fruit = Arrays.asList(new Fruit());
static void f1() {
Apple a = readExact(apples);
Fruit f = readExact(fruit);
f = readExact(apples);
}
//静态的泛型方法readExact可以有效地适应每个方法调用, 它能精确地返回所需要类型的对象
static class Reader<T> {
T readExact(List<T> list) {
return list.get(0);
}
}
static void f2() {
Reader<Fruit> fruitReader = new Reader<Fruit>();
Fruit f = fruitReader.readExact(fruit);
//Fruit a = fruitReader.readExact(apples);
}
//一个泛型类中的方法readExact, 声明时为它指定了类型Fruit, 所以它只返回Fruit类型的对象, 但不允许传入一个List<Apples>来返回Fruit对象, 因为List<Fruit>和List<Apple>之间没有直接关系
static class CovarianReader<T> {
T readCovariant(List<? extends T> list) {
return list.get(0);
}
}
static void f3() {
CovarianReader<Fruit> fruitReader = new CovarianReader<Fruit>();
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
//这里相较于上一个方法, 泛型参数改成了List<? extends T>, 也就表示, 声明为Fruit类型时, 可以传入Fruit或者其派生类型的对象, 最终都会返回一个T, 也即Fruit类型的对象, 所以这里就可以用Fruit的派生类Apple类的List做参数了
public static void main(String[] args) throws Exception {
f1();f2();f3();
}
}
捕获转换
static <T> void f1(Holder<T> holder) {
T t = holder.get();
}
static void f2(Holder<?> holder) {
f1(holder); //捕获holder的具体类型
}
Holder<?> holder = new Holder<Double>();
必须给定一个确定的类型, 在f2()中不能返回T, 因为T对f2()来说是未知的
- 泛型的缺点
转型
class X<T> {
private Object[] array;
private int index = 0;
public T pop() { return (T)array[--index]; }
}
这个转型会有未检查转型的警告, 因为泛型被擦除为Object, 所以不知道这个转型是不是安全的
直接用泛型转型会有警告, 不转型又要求转型
List<Widget> list = (List<Widget)in.readObject();
readObject只能返回Object类型, 必须要转型, 可以直接这样转型又不能确定是安全的转型
List<Widget> list = List.class.cast(in.readObject());
用Class.class.cast()来代替显式的转型
重载
class X<W,V> {
public f(W w) {}
public f(V v) {}
}
重载f, 但是由于擦除, W和V都没了, 都是f, 看起来是一样的
- 自限定
古怪的循环泛型CRG
public class BasicHolder<T> {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
}
这是一个普通的泛型类
class Subtype extends BasicHolder<Subtype> {}
public f() {
Subtype st1 = new Subtype(), st2 = new Subtype();
st1.set(st2);
Subtype st3 = st1.get();
}
从这里可以看出, 所有基类的方法都接收导出类型做为参数, 并返回导出类型作为返回类型, 它是一种导出类的公共模板, 可以让导
出类对象创建自己这个类的对象, 有点像链表结构, 每个结点对象里面包含一个next表示下一个节点对象
自限定
class SelfBounded<T extends SelfBounded<T>> {}
自限定就是强制要你在继承关系中, 使用
class A extends SelfBounding {}
还可将自限定用于泛型方法
static <T extends SelfBounded<T>> T f(T arg) {
return arg.set(arg).get();
}
A a = f(new A());
当使用自限定时, 基类别中的方法就被限定了必须用子类做参数, 而不能传入基类对象做参数
使用普通的泛型时, 由于传入的泛型类型可以是基类, 那么基类中的方法就可以接收基类对象, 方法可以被重载
class SelfBounded<T extends SelfBounded> {
void set(T arg) {}
}
class A extends SelfBounded<A> {}
这里的泛型T被指定了只能用A的对象
class GenericSetter<T> {
void set(T arg) {}
}
class DerivedGS extends GenericSetter(Base) {
void set(Derived arg) {}
}
这里set被重载, 可以set(Derived)或者接受泛型set(Base)
- 异常
interface Processor<T,E extends Exception> {
void process(List<T> resultCollector) throws E;
}
class X<T,E extends Exception> {
public void process() throws E {}
}
class Failure extends Exception {}
class Sub<String, Failure> implements Processor<String, Failure> {
public void process(List<String> list) throws Failure {}
}
通过以Exception做边界的异常泛型, 加入异常类
- 混型
混合多个类的能力, 以产生一个可以表示混型中所有类型的类
C++中可以通过参数化类型来实现混型, 因为混型就是继承自其类型参数的类
Java中由于有擦除, 所以不能直接用泛型实现混型, 可以通过接口, 实现多个接口, 并对每一个混入的类型, 加入一个相应的域, 将
接口方法转发给恰当的对象, 使用代理来完成
- 潜在类型机制
泛型类型只要求实现某个方法子集, 而不是某个特定的类或接口, 它使你可以横跨继承结构, 调用不属于某个公共接口的方法, 即不
关心具体的类型, 只要符合方法定义就可以调用
在Java中实现潜在类型机制, 就必须使用一个类或者接口, 并且必须在边界表达式中指定它
interface Performs {
void speak();
void sit();
}
class Dog implements Performs {
public void speak() {}
public void sit() {}
}
class Robot implements Performs [
public void speak() {}
public void sit() {}
}
class Communicate {
public static <T extends Performs>
void perform(T performer) {
perform.speak();
perform.sit();
}
}
public static void main(String[] args) {
Dog d = new Dog();
Robot r = new Robot();
Communicate(d);
Communicate(r);
}
Dog和Robot被强制需要实现Performs类, 之后才可以使用这个接口里面这些通用的方法
可以使用映射实现潜在类型机制
class CommunicateReflectively {
public static void perform(Object spker) {
Class<?> spkr = speaker.getClass();
try {
try {
Method speak = spkr.getClass("speak");
speak.invoke(speaker);
} catch(NoSuchMethodException e) {}
try {
Method sit= spkr.getClass("sit");
speak.invoke(speaker);
} catch(NoSuchMethodException e) {}
} catch(Exception e) {}
试着创建一个apply方法, 它的作用是将任何一个方法作用于任何一个序列之中
class Apply {
public static <T, S extends Iterable<? extends T>
void apply(S seq, Method f, Object... args) {
try {
for(T t : seq)
f.invoke(t, args);
} catch(Exception e) {}
}
这个序列seq定义为实现了Iterable<? extends T>的类型, T是它的基类型, 故它可以通过foreach遍历, 也可以调用任意方法, 且不
用在意序列的类型(只要是实现了Iterable的), 是真正的泛化实现潜在类型机制
- 将函数对象用作策略
使用策咯设计模式, 把变化的事物完全隔离到一个函数对象中, 函数对象就是在某种程度上行为像函数的对象
函数对象的价值就在于, 它可以被传递出去, 并且拥有在多个调用之间持久化的状态
Interface Combiner<T> { T combine(T x, T y); }
public static <T> T
reduce(Iterable<T> seq, Combiner<T> combiner) {
Iterator it = seq.iterator();
if(it.hasNext()) {
T result = it.next();
while(it.hasNext())
result = combiner.combine(result, it.next());
return result;
}
return null;
}
static class IntegerAdder implements Combiner<Integer> {
public Integer combine(Integer x, Integer y) {
return x + y;
}
}
List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Integer result = reduce(li, new IntegerAdder());
print(result)
//output: 28
泛型的目的在于可表达性, 而不仅仅是为了创建类型安全的容器
因为泛型不是一开始就添加到Java中的, 所以某些容器无法达到它们应该具有的健壮性