第十五章、泛型
泛型(generics)的概念是Java SE5的重大变化之一。泛型实现了参数化类型(parameterized types)的概念,使代码可以应用于多种类型。“泛型”这个术语的意思是:“适用于许多许多的类型”。
1.与c++的比较
c++的模板、Java泛型的边界
2.简单泛型(泛型类)
泛型的产生背景 :创造容器类是促成泛型出现的一个重要原因。
泛型的核心概念 :泛型的主要目的之一就是用来指定容器要持有什么类型的对象,再由编译器来保证类型的正确性。
与其使用Object我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型。要达到这个目的,要使用类型参数<T>:
public class Holder<T> {
private T a;
public Holder(T a) {this.a = a;}
public void set(T a) {this.a = a;}
public T get() {return a;}
}
2.1 一个元祖类库
元祖 :它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。元祖也被称为数据传送对象或信使。元祖可以具有任意长度,其中的元素可以是任意不同的类型。还可以利用继承机制实现长度更长的元祖。
为了使用元祖,你只需定义一个长度适合的元祖,将其作为方法的返回值,然后在return语句中创建该元祖,并返回即可。
2.2 一个堆栈类
链表节点-泛型-嵌套类实现:
private static class Node<U> {
U item;
Node<U> next;
Node() {}
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
public boolean end() {return item==null && next ==null;}
}
2.3 RandomList
3.泛型接口
泛型也可以应用于接口。例如 生成器(generator),这是一种专门负责创建对象的类。实际上,这是 工厂方法设计模式 的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。
一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。
public interface Generator<T> {
T next();
}
4.泛型方法
概念:泛型方法:即含参数化类型(parameterized types)的参数或者返回值方法。
举例:
方法参数为泛型:public <T> void func(T x){};
方法返回值为泛型:public <T> List<T> list(){};
场景:泛型方法所在类,即可以是泛型类,也可以不是泛型类。
原则:泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。
4.1 杠杆利用类型参数推断
类型参数推断:使用泛型类时,创建对象时必须指定类型参数的值<ClassDemo>,但使用泛型方法时,通常不必指明参数类型,编译器会自动找出具体的类型。
类型参数推断的适用场景:类型参数推断只对赋值操作(=)有效,其他场景(??比如作为方法参数传递时)无效。
显式的类型说明:调用泛型方法时,可以显式的指明类型(应用极少)。
public class Demo{
public static <K,V> Map<K,V> map(){
return new HashMap<K,V>();
}
}
func(Demo.<Person,List<Pet>>map()); //显式的类型说明--用法!
4.2 可变参数与泛型方法
泛型方法与可变参数列表能够很好地共存
eg: public static <T> List<T> makeList(T... args){}
4.3 用于Generator的泛型方法
public interface Generator<T> {
T next();
}
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;
}
}
4.4 一个通用的Generator
public class BasicGenerator<T> implements Generator<T> {
private Class<T> type; //Class对象
public BasicGenerator(Class<T> type) {
this.type = type;
}
@override
public T next() {
try {
return type.newInstance();//通过Class对象生成实例Instance
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type) {
return new BasicGenerator<T>(type);
}
}
4.5 简化元组的使用
4.6 一个Set实用工具
EnumSet:Java SE1.5新增容器,可以直接从Enum创建Set。
5.匿名内部类
泛型还可以应用于内部类以及匿名内部类。
public interface Generator<T> {
T next();
}
public static Generator<Customer> generator(){
return new Generator<Customer>{ //泛型类创建对象时,必须指明类型
public Customer next(){ return new Customer();} //泛型方法调用时,自动类型推断
}
}
6.构建复杂模型
泛型的一个重要好处是能够简单(参数化类型的容器)而安全(类型安全,编译器自动检测)地创建复杂的模型。
7.擦除的神秘之处
根据JDK文档的描述,Class.getTypeParameters() 将返回一个 TypeVarible 对象数组,表示有泛型声明的类型参数...这只是表示用作参数占位符的标识符,这并非有用的信息。所以在泛型代码内部,无法获得任何有关泛型参数类型的信息。
泛型-类型参数的擦除: Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此,List<String> 和 List<Integer> 在运行时实际上是相同的类型。这两种类型都被擦除成它们的原生类型,即 List。
7.2 迁移兼容性。
泛型类型只有在静态类型检查期间(编译期?)才出现。在此之后(运行期?),程序中的所有泛型类型都被擦除,替换为他们的非泛型上界(第一个边界)。普通类型被擦除成 Object。
eg: 类型参数的擦除:
<T extends HasF> 会被类型擦除为 HasF
<T super HasE> 会被类型擦除为 HasE??
擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用,反之亦然,这经常被称为 迁移兼容性。
7.3 擦除的问题
擦除的理由:在不破坏现有java类库的情况下,将泛型融入到Java语言中。
擦除的代价:是显著的。不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和 new表达式。因为所有关于参数的类型信息都丢失了。
7.4 边界处的动作
对于泛型中创建数组,推荐使用:Array.newInstance() (WHY?? )
public class ArrayMaker<T>{
private Class<T> kind; //会被类型擦除为Class对象
public ArraryMake(Class<T> kind){this.kind = kind;}
public T[] create(int size){
return (T()) Array.newInstance(kind,size); //推荐使用此方式,借助Kind Class对象创建数组
}
}
ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class);
String[] strArray = stringMaker.create(10);
8.擦除的补偿
擦除丢失了在泛型代码中执行某些操作的能力。任何在运行需要周到确切类型信息的操作都将无法工作。
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
if(arg instanceof T) { //ERROR,可改为Class对象isInstance()
T var = new T(); //ERROR,可改为Class对象newInstance()
T[] array = new T[SIZE]; //ERROR,可改为使用new ArrayList<T>()
T[] array = (T)new Object[SIZE]; //unchecked warning
}
}
}
擦除补偿:使用 instanceof 的尝试是失败的,因为其类型信息已经被擦除了。如果引入类型标签,就可以转用动态的 isInstance()() 。
public class ClassObject<T> {
Class<T> kind;
public boolean f(Object arg) {
return kind.isInstance(arg); //擦除补偿的用法举例
}
}
9.边界
泛型边界:使得你 可以在用于泛型的参数类型上设置限制条件。
泛型复用extends关键字:不同于常规的extends关键字语法&含义。举例:<T extends Dimension&HasColor>
通配符?:定义:<? extends Fruit> ,可使用:<Orange>
超类型通配符super关键字:举例:定义:<T super Orange>,可使用<Fruit>
无界通配符<?>:意味任何对象:List<?>
10. 泛型问题
任何基本数据类型 不能作为泛型类型参数(->可改为使用包装类)
一个类不能实现同时实现泛型接口的两种变体(泛型-类型擦除后,擦除后的方法不能重载)。
转型(upcasting&downcasting)+ instanceof() :均不可用!
方法重载:方法参数(泛型)会被擦除,不可用!
11. 自限定类型
自限定类型:确保类型参数必须与正在被定义的类相同。
参数协变:自限定类型的价值,在于产生协变参数类型(方法参数类型会随着子类变化而变化)
class SelfBounded<T extends SelfBounded<T>>{
T element;
SelfBounded<T> set(T tag){
element = tag;
return this;
}
T getElement(){
return element;
}
}
class A extends SelfBounded<A>{} //自限定:类型参数&定义类必须一致
A a = new A();
a.set(new A()); //参数协变:方法参数&定义的子类 可以保持动态一致。
12. 动态类型安全
泛型容器:受检查的容器在试图插入类型不正常的对象时,抛出ClassCastException。
13. 异常
14. 混型
AOP:面向方面(切面)的编程,Aspect Oriented Programming:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容.
混型:混合多个类的能力,将特性和行为一致性的应用多个类上。
装饰者模式:GoF23之一:使用分层对象来动态透明的向单个对象添加责任。
15. 将函数对象用作策略
策略模式:GoF23之一:将变化的事物 完全隔离到一个函数对象中。
函数对象:就是某种程度上,行为像函数的对象。
函数对象的价值:与普通方法不同,他们可以被传递出去,还可以拥有在多个调用之间持久化的状态。
interface Combiner<T>{
T combine(T x, T y); //策略模式:
}
class StrCombiner<String>{ //函数对象:
String combine(String x, String y){ return x+ y;}
}
网友评论