泛型(Generics)

重温了一下泛型,心得记录如下。

1)什么是泛型?
泛型(Generics)是相对于类型(Type)而言的,泛型是对类型的抽象。泛型最常见的应用是在集合或容器中,即在泛化的数据结构或算法中使用。泛型的命名约定一般是单个大写字母,并用一对尖括号<>括在其中。泛型的作用之一,是可以在编译时而不是运行时检查类型转换的错误,即消除类型转换,使得代码更清晰,更健壮。

效果之一:在空间上,通过泛型引入的类型检查机制可以精确地定位错误的位置,而无泛型时类型转换的错误可能远离代码中断运行的现场。
Debugging may be difficult, as the point in the code where the exception is thrown may be far removed from the point in the code where the error is located.
效果之二:在时间上,通过泛型引入的类型检查机制可以提前发现错误,并且其类型擦除(Type Erasure)机制可以自动地进行类型转换。
It's always better to catch bugs when compiling than when running.


2)泛型在类型与运算中的运用
泛型可用在类型(generic classes and generic interfaces),也可用在运算上(generic methods and generic constructors)。这里头涉及到一些概念:formal type parameter 形式化类型参数,actual type argument 实际类型参数,type variable类型变量, type value类型值,parameterized type 参数化类型。generic type declaration 泛型类型声明,type inference类型推断等。


public class Box<T> {

private T t;

public void add(T t) {
this.t = t;
}

public T get() {
return t;
}

public <U> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}

public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text");
}
}



3)泛型的边界
单个大写字母标识的具名泛型可以有上限,问号标识的未名(未知)的泛型可以有上限或下限。
Bounded type parameters limit the kinds of types that can be passed into a type parameter; they can specify an upper bound only. Wildcards represent unknown types, and they can specify an upper or lower bound.

关键字extends指定泛型的上限(upper bound),表示其类(class)扩展的限制(必须扩展该类)或接口(interface)实现的限制(必须实现该接口)的含义。如果要求该泛型同时满足扩展某类以及实现某接口,则需要&运算符号。<T extends SomeClass & SomeInterface>
extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

关键super指定泛型的下限(lower bound),表示其类(class)或接口(interface)必须为某类所继承(inheritance)或实现(implementation),即是该类的父类或父接口。如果要求该泛型同时满足为某类所或接口所扩展实现,则需要&运算符号。<T super SomeClass&SomeInterface>

一般的,当对输入参数类型有限制时,可以使用下限通配符(? super T);对返回参数类型有限制时,可以使用上限通配符(? extends T)。
In general, if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcards (? super T). Conversely, if the API only returns T, you'll give your clients more flexibility by using upper bounded wildcards (? extends T).



public class Box<T> {

private T t;

public void add(T t) {
this.t = t;
}

public T get() {
return t;
}

public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}

public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}


举例
Cage<? extends Animal> 上限通配符,必须是Animal的子类。
Cage<? super Animal> 下限通配符,必须是Animal的父类。
Cage<T extends Animal> 上限泛型,必须是Animal的子类
Cage<T super Animal> 没有这种用法。
Cage<?> 无限制,通配符。

4)泛型的特化
所谓泛型的特化,就是给形式化类型参数指定实际类型参数。这与类的实例化有相似之处,抽象层次不同而已。泛型之类型,正如类型之于值。泛型特化(subtyping),会产生类型擦除(type erasure)效果。类型擦除机制在编译时擦除掉所有的泛型信息,从而使得程序运行时看不见这些信息,不会影响到字节码的生成。

When a generic type is instantiated, the compiler translates those types by a technique called type erasure — a process where the compiler removes all information related to type parameters and type arguments within a class or method. Type erasure enables Java applications that use generics to maintain binary compatibility with Java libraries and applications that were created before generics.


5)泛型中的最难理解的地方
一般地,如果Foo是Bar的子类型(子类或子接口),G是一种通用的类型声明,并不意味着G<Foo> 是G<Bar>的子类型。这也许是学习泛型最难的一件事,因为其与我们根深蒂固的直觉相悖。
In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some
generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>. This is probably the hardest thing you need to learn about generics, because it goes against our deeply held intuitions.

这个看似违背直觉,其实还是存在合乎逻辑的地方。

举两个例子。
A) 水果与篮子
放水果的篮子,水果。
放苹果的篮子,苹果。

苹果与水果是“is-a"的关系,而“放苹果的篮子”与“放水果的篮子”的关系却未必是"is-a"的关系,因为篮子与篮子的关系不一定是“is-a"的关系,只是篮子里头放的东西,有"is-a"的关系。

B) 动物与笼子
关动物的笼子,动物。Cage<Animal>
关狮子的笼子,狮子。Cage<Lion>
关蝴蝶的笼子,蝴蝶。Cage<Butterfly>

显然,关狮子的笼子不能用来关蝴蝶,关蝴蝶的笼子不能用来关狮子。因为用关狮子的笼子,蝴蝶会穿过缝隙飞走;用关蝴蝶的笼子,狮子会破笼而出逃走。

//Cage<Animal> 可以关“[b]所有动物[/b]”的笼子,
// 不能被赋值为关狮子的笼子,否则这个笼子关不住蝴蝶。
Cage<Animal> animalCage = new Cage<Lion>();

// Cage<? extends Animal> 可以关“[b]某种动物[/b]”的笼子
// 可以被赋值为关狮子的笼子,但不能用来关狮子。
// 因为其是未知和不确定的,其可用于查询。
Cage<? extends Animal> someCage = new Cage<lion>();



import java.util.ArrayList;
import java.util.List;


class Animal {}
class Lion extends Animal {}
class Butterfly extends Animal {}

class Cage<E> {
private List<E> cage = new ArrayList<E>();
public void add(E e) {
cage.add(e);
}
public List<E> getCage() {
return this.cage;
}
}

public class Test {
public static void main(String[] args) {
// 关狮子的笼子不能用来关蝴蝶
Cage<Lion> lionCage = new Cage<Lion>();
lionCage.add(new Lion()); // ok
// lionCage.add(new Butterfly()); // compile error

//Cage<Animal> 可以关“所有动物”的笼子,
// 不能被赋值为关狮子的笼子,否则这个笼子关不住蝴蝶。
// Cage<Animal> animalCage = new Cage<Lion>(); // compile error
Cage<Animal> animalCage = new Cage<Animal>();
animalCage.add(new Lion()); // ok

// Cage<? extends Animal> 可以关“某种动物”的笼子
// 可以被赋值为关狮子的笼子,但不能用来关狮子。
// 因为其是未知和不确定的,其可用于查询。
Cage<? extends Animal> cage = lionCage; // ok
// cage.add(new Lion()); // compile error
for (Animal animal : cage.getCage()) {
System.out.println(animal); //ok
}
}
}


6)泛型与反射
泛型,是将“类型”泛化为“泛型”,从上往下赋予“类型”动态性,编译时擦除类型。(Generics, Type Erasure)
反射,是将“实例”反射为“类型”,从下往上赋予“类型”动态性,运行时确定类型。(Reflection, Type Determination)

进一步说,编程语言可向上抽象,通过“泛型”获得“动态性”;机器(语言)可向上抽象,即使编程语言可通过“反射”获得“动态性”。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值