Core Java Volume I 读书笔记---第十二章 泛型程序设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaoyaohuqijun/article/details/78079465

第十二章: 泛型程序设计

 

12.1 为什么要使用泛型程序

 

泛型能避免重复代码, 没有泛型之前同样的功能比较比较大小,  整型需要一个实现, 字符串需要一种实现。

泛型的核心是将类型参数化

泛型节省的是程序员写的代码, 最终实际运行时实际没有那么节省,  生成重复代码的过程都由编译器干了,  无论是C++ 还是Java的泛型系统都是这样。

 

 

12.2  定义简单泛型类

 

一个泛型类(genericclass )就是有一个或者多个类型变量的类

Eg.

public class Pair<T>
{
   
privateT first;
    private
T second;

    public
Pair() {first = null; second = null; }

   
publicPair(T first,T second) {this.first = first; this.second = second; }
   
   
publicT getFirst() { return first; }
   
publicT getSecond() { return second;}

   
publicvoid setFirst(T first) { this.first = first; }
   
publicvoid setSecond(T second) { this.second = second;}
}

 

 

泛型类可以看做普通类的工厂。

 

C++ 中泛型类可以看做类的模板或者类的类, C++ 和Java的泛型使用起来差不多,  实现却完全不一样。

 

 

12.3 泛型方法

 

泛型方法可以定义在普通类中,也可以定义在泛型类中。

类型变量放在修饰符后面,返回类型的前面

 

 

12.4 类型变量的限定

类或者方法可以对类型变量加以约束:

<T extendsBoudingType>

表示T应该是绑定类型的子类型,  T和绑定类型可以使类和接口。

<T extendsComparable & Serializable>

可以有多个限定,使用”&”分隔;

12.5 泛型代码和虚拟机

 

Java 中泛型由编译器实现, 虚拟机感知到的是普通类型。

过程:

1.      类型擦除 (擦除类型参数,剩下原始类型)

2.      类型转换 (调用泛型方法或者存取泛型域时,原始类型转换为 类型参数类型)

 

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type). 原始类型的名字就是删去类型参数后的泛型类型名。

原始类型用第一个限定的类型变量来替换, 如果没有限定就用Object 替换。

 

前面定义的Pair<T>的原始类型为:

class Pair
{
   
privateObject first;
    private
Object second;

    public
Pair() {first = null; second = null; }

   
publicPair(Objectfirst,Objectsecond) {this.first = first; this.second = second; }

   
publicObject getFirst() { return first; }
   
publicObject getSecond() { return second;}

   
publicvoid setFirst(Objectfirst) { this.first = first; }
   
publicvoid setSecond(Objectsecond) { this.second = second;}
}

 

 

总之,需要记住有关Java泛型转换的事实:

1.      虚拟机中没有泛型,只有普通的类和方法

2.      所有的类型参数都被它们的限定类型替换

3.      桥方法被合成来保持多态

4.      为保持类型安全,必要时插入强制类型转换

 

 

 

12.6 约束与局限性

        

1.      不能使用基本类型实际话类型参数

类型擦除后,Object 不能存储基本类型的值

2.      运行时类型查询只使用于原始类型

类型擦除后,虚拟机中存在的是原始类型, 所以类型查询只产生原始类型

3.      不能创建参数化类型的数组

Pair<String>[] table  = new Pair<String>[100];

类型擦除后, table 的实际类型是Pair[], 元素类型为Pair,  new Pair<String>[100] 对象的元素类型为Pair<String>

4.      不能实例化类型变量

不是使用new T(…), new T[…]或者T.class这样的表达式

5.      泛型类的静态上下文中类型变量无效

泛型类中禁止使用带有类型变量的静态域或者方法

类变量只有一个,  所以用类型变量声明的静态域经过类型擦除后实际为Object a, 虚拟机中存在的只有一个Object类型的对象。  当T 实际类型为String 时, a 要是个String ,   当T 实际类型为Random 时, a 需要为Random 类型。  a 需要能千变万化,  但做不到, 因为a是静态的,只有一个。

6.      不能抛出也不能捕获泛型类对象,  甚至泛型类或者Throwable 都是非法的

 

 

 

12.7 泛型类的继承规则

 

Pair<T>  是一个泛型类,   S 和U是两个普通类。

无路S与U有什么联系, 通常Pair<S>与Pair<U>没有什么联系。

 

 

12.8  通配符类型

 

由于Java泛型实现的机制(类型擦除, 类型转换)导致泛型系统有很多约束和局限,  不那么令人愉快,所以有了通配符类型.

通配符类型配合泛型使用,   类型变量的值可以为通配符类型

 

Pair<?  extends Employee>  表示任何泛型Pair 类型, 参数为Employee 的子类

Pair<?  super Manager >   参数为Manger的父类

 

带有超类型限定的通配符可以向泛型对象写入,  带有子类型限定的通配符可以从泛型对象读取:

Pair<? extends Employee>  p  其方法为:

? extends Employee getFirst()

void setFirst(<? extends Employee)

编译器将拒绝setFirst,  因为不知道参数的具体类型, 只知道是Employee的某个子类;

getFirst 方法不存在这个问题:  将get 的放回值赋给一个Employee 的引用完全合法(多态:父类的变量可以引用父类或者其子类的对象

 

反过来使用超类型限定的通配符:

? super Manager getFirst()

void setFirst(<? super Manager)

编译器将拒绝getFirst,因为返回类型的不到保证

SetFirst 可以用任意的Manager对象调用它(继承的替换原则:任何父类对象出现的地方,都可以用子类对象替换

 

这里有点烧脑, 记住多态的含义:  父类的变量可以引用父类或者其子类的对象

所以读取对象时需要知道父类

 

超类型限定的通配符其子类型是确定的,  子类型限定的通配符其父类型是确定的。

 

无限定通配符:

Pair<?>

?getFirst()

void serFirst(?)

其set方法不能调用,  get 方法的返回值只能赋值一个Object

12.9 反射和泛型

 

   Class 类实际是泛型的,String.class 实际上是一个Class<String>  类的对象(唯一的对象)

 

  反射可以通过Type 接口获得泛型相关的信息

 

Type 接口包含以下子类型:

1.      Class 类, 描述具体的类型

2.      TypeVariable 接口,描述类型变量(如 T extends Comparable<? superT>)

3.      WildcardType 接口,描述通配符(如 ? super T)

4.      ParameterizedType 接口,描述泛型类或接口类型(如Comparable<? super T>

5.      GenericArrayType 接口,描述泛型数组(如 T[])

 

关于反射与泛型, 可以参考这里的代码:

https://github.com/geekhuqijun/JavaCodePieces/blob/master/GenericReflectionTest.java

 

 

总结

 

Java 中的反射机制是通过类型擦除后进行类型转换实现的,  这基于Java 中所有的类都从Object 而来这一事实。  实际上没有泛型机制, Java  也可以通过Object 写出非常通用的代码,在Java 引用泛型机制前就是这样做的。  Java 的泛型机制不过是在前面的基础上改进而已。

C++ 的泛型机制完全不一样,  编译器会根据模板实际生成各种泛型类的具体代码。例如定义了<TypenameT> Pair   ,  如果代码中有使用Pair<float>  和Pair<char> ,  C++ 编译器会根据模板生成 Pair<float>  和Pair<char>  两种类型。

两种机制各有优劣吧:

C++ 的泛型机制会有所谓的“模板代码膨胀”问题:如果模板实际有10中不同的类型使用, 那编译器会生成10中不同的类定义。  不过C++ 的泛型类使用起来体验更好,和使用普通类一样使用就可以了。

Java 的泛型机制虽然不会有“代码膨胀”的问题, 但Java编译器生成的代码也不少,  比较一条使用泛型的语句JAVA 编译器要转换成使用原型,类型转换两条语句。  此外类型擦除会导致很多问题,参考“12.6 约束与局限性“,使用起来不是那么直接。

没有更多推荐了,返回首页