Java核心技术 卷1 第十二章总结

  这章的标题就是泛型程序设计,所以很显然就是讲Java的泛型机制的。我觉得这个机制是Java一个非常重要的机制,这里我不讲怎样很好的使用,只总结一下书中的内容,并且说一下我个人的理解。

1.使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。

2.泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。

3.在Java中增加泛型类之前,ArrayList是用一个Object引用数组来实现的。因此也能存储任何类型的对象。但是有两个不好的地         方:

  ① 当获取一个值的时候必须进行强制类型转换:

ArrayList files = new ArrayList();

String filename = (String) files.get(0);

 ② 没有错误检查,可以向数组列表中添加任何类的对象:

files.add(new File("..."));

      对于这个调用,编译和运行都不会出错。然而,如果将这个对象取出并通过强制类型转换赋给一个String引用就会产生一个异常。 

4.一个泛型类(generic class)就是具有一个或多个类型变量的类。

    下面给出一个例子:

public class Pair<T>
{
     private T first;
     private T second;
     
     public Pair() { first = null; second = null;}
     
     //注意下面这两个方法都不是泛型方法
     public T getFirst() { return first;}
     public void setFirst(T newValue ) { first = newValue;}
  
     ......

}

     泛型类还可以有多个类型变量。例如,public class Pair<T,U>{...}

5.Java与C++的泛型机制有着本质的不同!

6. 下面说下泛型方法的语法。

     一个例子:

class ArrayAlg
{
     public static <T> T getMiddle(T... a)
     {
          return a[a.length/2];
     }
}

  这个是在一个普通类中定义的泛型方法。语法特征就是方在修饰符后面和返回类型前面的类型变量。

  注意,泛型方法可以定义在普通类中,也可以定义在泛型类中。其实,泛型方法和泛型类没什么关系。不是说泛型类的方法都是泛型方法,其实往往都不是,就像上面的例子。

  至于调用的方法是:

String middle = ArrayAlg.<String>getMiddle("John","Q","Public");

//其实也可以这样,直接让编译器通过参数来推断出T的类型
String middle = ArrayAlg.getMiddle("John","Q","Public");

7. 有时候,我们需要对类型变量的范围进行限制。这时就可以使用关键字extends。

    具体的例子是:

public static <T extends Comparable> T min(T[] a) ...

   这时,能用来调用这个方法的类型必须是实现了Comparable接口的类型,其它的类型编译器会报错。

    至于为何不是使用implements关键字,因为T的实际绑定类型可以是类,也可以是接口,而extends更加接近子类型的概念。

8. 一个类型变量的限定可以有多个,用"&"符号来分隔。

    具体的例子是:

T extends Comparable & Serializable

   限定中,可以有多个接口超类型,但是至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。

9. 下面开始说明Java泛型的基本实现原理。(类型擦除——erased)

     虚拟机没有泛型类型对象——所有对象都属于普通类。

     无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。 擦除类型变量,并替换为限定类型。如果无限定则用Object代替,如果有多个限定则用第一个限定类型代替。

    就这点而言,Java泛型与C++模板有很大的区别。C++中每个模板的实例化产生不同的类型,这一现象称为“模板代码膨胀”。Java则不存在这个问题。因为,对于不同的类型参数生成的对象,其实都是同一个类型,有相同的Class对象。

   当程序调用泛型方法时,如果擦除返回类型,编译器会插入强制类型转换。

   例如:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

  类型擦除后,getFirst方法的返回类型实际上是Object类型。编译器会自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:

  •    对原始方法Pair.getFirst的调用 
  •    将返回的Object类型强制转换为Employee类型。

   看到这里就明白了,其实还是有强制类型转换的操作,只不过这个操作由编译器自动地来为我们生成,而不用我们手写了!

 10.下面说一下桥方法(bridge method)。

       这个桥方法主要用来解决泛型类被继承的多态性实现的问题。

       下面用一个例子说明:

class DateInterval extends Pair<Date>
{
     public void setSecond(Date second) { ... }
}

  上面这个类进行类型擦除后,实际上会变成:

class DateInterval extends Pair
{
    public void setSecond(Date second);//这个类自己声明的
    public void setSecond(Object second);//从Pair类继承过来的
}

  出现这种情况是因为Pair中的方法并不是我们想象中以Date类型为参数的,而是经过类型擦除后以Object类型为参数。所以这两个方法因为参数类型不同,所以只是构成重载而不是覆盖,所以多态性无从谈起。

   对于如下的代码:

DateInterval interval = new DateInterval(...);
Pair<Date> pair = interval;
pair.setSecond(aDate);

   如果编译器不做一些私下的修改的话,这个pair.setSecond()实际上调用的就是Pair类的方法,而不是DateInterval的相应方法,没有实现多态性。

   但是,编译器会做点修改来实现多态性的。方法就是在DateInterval类中生成一个桥方法:

public void setSecond(Object second) { setSecond((Date)second); }

   这样就真正覆盖了超类的setSecond方法,顺利实现多态性。

 11.这一点说一下使用Java泛型的约束与局限性。大多数的限制都是由类型擦除引起的。

  •     不能用基本类型实例化类型参数

            这个的原因就是类型擦除后,都会变成Object域,而Object并不能存储基本数据类型的值。

  •    运行时类型查询只适用于原始类型 

           虚拟机中的对象总有一个特定的非泛型类型。因此,所有泛型对象的类型查询只会返回原始类型。 

  •      不能创建参数化类型的数组 

            这个原因我也不太明白。但是可以声明类型为参数化数组的引用变量,只是不能new。还有可以new 通配符类型的数组。

  •     不能实例化类型变量 

            不能用像new T(...), new T[...]或T.class这样的表达式中的类型变量。因为类型擦除后,全部T换为Object,结果自然就变                味了。 

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

          例如:

public class Singleton<T>
{
     private static T singleInstance;//ERROR
     
     private static T getSingleInstance()//ERROR
     {
           if(singleInstance == null)  { ...}
           return singleInstance;
     }
}

          如果这个能运行,那么用不能类型生成的Singleton对象其实都是同一个引用。因为实际上它们就是同一个类,而静态域是属于整个类的。这就与我们想要的严重不符。所以从语法上规定这不可行。

  • 不能抛出或捕获泛型类的实例 

        既不能抛出也不能捕获泛型类对象。实际上,甚至泛型类扩展 Throwable都是不合法的。(这点的具体原因我也不明白) 

12.这点说一下泛型类型的继承规则。

     Pair<T>与Pair<S>没什么关系,即使T和S有继承关系,也是这样。

     但是,泛型类可以扩展或实现其他的泛型类。如ArrayList<T>类实现了List<T>接口。所以,一个ArrayList<String>可以被转换为一个List<String>。

13.这点说一下非常重要的通配符类型。也就是“?”这个符号。

      一开始我无法理解这个通配符类型,还把它与定义泛型类中的T视为差不多的东西。其实,不是这样的。理解这个通配符概念的关键就是——通配符类型就是一种类型,与什么String、Integer是一样。也就是说通配符?是一个类型实参而不是类型形参。只不过也有其特殊之处,就是能代表很多的真实类型。

   所以说:

Pair<String> p1;//可以
Pair<?>      p2;//可以
Pair<T>      p3;//报错

     其实,我觉得这个?有点类似C++11中的auto。就是我不知这到底是什么类型,但是你传给我就行了。

     我们在定义泛型类的时候可以对T进行限定。同样,我们使用通配符的时候也可以进行限定,而且还可以进行超类型限定。

      下面先说extends限定:

Pair<Manager> managerBuddies = new Pair<>(ceo,cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies;
wildcardBuddies.setFirst(lowlyEmployee);//compile-time error

   Pair<? extends Employee> 表面这个变量可以接受Employee或其子类的引用,所以第二句不会报错。但是第三句就会报错。因为这个? extends Employee的范围太过广泛,例如Employee有多个子类,它们之间就不能相互转换,所以一旦确定了?是其中一个子类就不能再设定为其他的子类了,所以这就直接规定为语法层面上不行。

      但是,使用getFirst就没有问题,因为都可以讲返回值赋值给一个Employee引用。

   接着讲一下super限定,也就是超类型限定,这是通配符类型独有的限定。 

     语法格式为: Pair<? super Manager>。

      这就把实际类型的范围限定为Manager或其的超类型。这时,我们倒是可以使用setFirst方法了,因为可以用Manager对象或其子类对象调用,因为Pair中必定是它们的祖先类引用,所以转换没有问题。

       但是,这时又到getFirst有问题了。因为Manager的超类型也太广泛了,每个祖先类并不能相互转换。因为只能统一的转换为       Object。因此,getFirst这时的实际用途不大。      

     注意,super和extends两种不能共存的。

    最后说下无限定通配符,也就是只有一个?,什么限定都没有。这种常用在与实际参数类型关系不大的泛型类中,例如Class<?>。

     对于Pair<?>,getFirst的返回值只能赋给Object。setFirst方法不能调用,甚至不能用Object调用。(可以调用setFirst(null))

     Pair<?>和Pair的本质不同在于:可以用任意Object对象来调用原始的Pair类的setObject方法。

14.最后这点说下反射与泛型。

      首先,Class类是泛型的。

      接着,Java泛型的卓越特性之一是在虚拟机中泛型类型的擦除。但是,擦除的类仍然保留了一些泛型祖先的微弱记忆。例如,原始的Pair类知道源于泛型类Pair<T>,即使一个Pair类型对象无法区分是由Pair<String>构造的还是由Pair<Employee>构造的。 

       注意,包含在类文件中的让泛型反射可用的类型信息与旧的虚拟机不兼容。

       可以通过Type接口、TypeVariable接口、WildcardType接口、ParameterizedType接口和GenericArrayType接口来实现对泛型类的反射,从而得到泛型类型的完整声明。既包括<T>等的声明。

     

 

   

 

 

         

 

   

 

 

     

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值