【codejava】第八版:第十二章 泛型程序设计[001] [20180106]

本文深入讲解Java泛型程序设计的基本概念、使用方法及其内部实现原理。涵盖了泛型类定义、泛型方法、类型变量限定等内容,并探讨了泛型在Java虚拟机上的表现形式及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

12.1 为什么要使用泛型程序设计

generic programming意味着编写的代码可以被很多不同类型的对象所重用,例如,我们并不希望为聚集String和File而编写不同的类,实际上也不需要这样做,因为一个ArrayList可以聚集任何类型的对象,这是一个泛型程序设计的实例

    java SE 5.0之前 java泛型程序设计是用继承实现的 ArrayList只维护一个Obj引用的数组 这样的实现有两个问题
     1当获取一个值时必须进行强制类型转换,此外这里没有检查错误,可以向数据列表中添加任何类的对象 
        ArrayList list = new ArrayList();
        list.add("lee");
        System.out.println((String)list.get(0));
        list.add(new File("..."));
对于这个调用 编译和运行都不会出错,然而在其他地方,如果将get的结果强制类型转换为String类型 就会产生一个错误

泛型提供了一个更好的解决方案 :类型参数(type parameters),ArrayList类有一个类型参数用来指示元素的类型
ArrayList<String> list = new ArrayList<String>();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象 编译器也可以很好的利用这个信息,当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String而不是Obj。
    编译器还知道list中add方法有一个类型为String的参数 这将比使用Obj类型的参数安全一些,现在,编译器可以进行检查,避免插入错误类型的对象
list.add(new File("..."));
这时是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转转异常要好得多,类型参数的魅力在于,使得程序具有更好的可读性和安全性.

        泛型程序设计划分为3个熟练级别,基本级别是,仅仅使用泛型类--典型的是像ArrayList这样的集合-不必考虑他们的工作方式与原因,大多数应用程序员将会停留在这一级别上,知道出现了什么问题(书中到此没有详细讲明哪3个级别)

12.2 简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个类型变量的类
public class Pair<T> {
  
    public Pair() {
        super();
     }
    public Pair(T first, T second) {
        super();
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
        return first;
    }
    public T getSecond() {
        return second;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public void setSecond(T second) {
        this.second = second;
    }
    private T first;
    private T second;
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面,泛型类可以有多个类型变量,例如,可以定义pair类,其中第一个域和第二个域使用不同的类型;
public class Pair<T, U>
类型变量使用大写形式,且比较短,这是很常见的,在java库中,使用变量E表示集合元素的类型,KV分别表示表的关键字的键值对(Key-Value)的类型,T(需要时还可以用临近的字母U和S)表示“任意类型”
public class PairTest1 {
    
    public static void main(String[] args) {
        String[] words = {"Mary","had"," a"," little"," lamb"};
        Pair<String> pair = PairAlg.minmax(words);
        System.out.println("min:"+pair.getFirst());
        System.out.println("max:"+pair.getSecond());
    }
}
class PairAlg{
    public static Pair<String> minmax(String[] arr){
        if (arr == null || arr.length == 0) {
            return null;
        }
        String max = arr[0];
        String min = arr[0];
        for(int i=0; i<arr.length; i++){
            if(max.compareTo(arr[i]) < 0){
                max = arr[i];
            }if(min.compareTo(arr[i]) > 0){
                min = arr[i];
            }
        }
        return new Pair<String>(min, max);
    }
}

12.3 泛型的方法
class PairAlg{
    public static <T> T getMiddle(T[] arr){
        return arr[arr.length / 2];
    }
}
这个方法是在普通类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(public static)的后面,返回类型的前面.泛型方法可以定义在普通类中,也可以定义在泛型类中. 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
         String[] words = {"Mary","had"," a"," little"," lamb"};
        Integer[] nums = {256,8,9};
        System.out.println(PairAlg.<Integer>getMiddle(nums));//注意尖括号位置
        System.out.println(PairAlg.<String>getMiddle(words));//注意尖括号位置
在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型

12.4 类型变量的限定

有时,类或方法需要对类型变量加以约束,下面这个我们计算数据中的最小元素

public static <extends Comparable> T min(T[] arr){
        if (arr == null || arr.length == 0) {
            return null;
        }
        T smallest = arr[0];
        for(int i=0; i<arr.length; i++){
            if(smallest.compareTo(arr[i]) > 0){
                smallest = arr[i];
            }
        }
        return smallest;
    }

public static <extends Comparable> 

    实际上Comparable接口本身就是一个泛型类型,目前,我们忽略其复杂性以及编译器产生的警告,在12.8节有介绍怎么适当地使用类型参数,或许大家会感到奇怪,在此为什么使用关键字extends,Comparable不是一个接口吗?

 <extends Bounding Type> 

表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口,选择关键字extends的原因是更接近子类的概念,并且JAVA的设计者也不打算在语言中再添加一个新的关键字 

    一个类型变量或通配符可以有多个限定,例如

 <extends Comparable & Serializable> 

public class PairTest2 {
    public static void main(String[] args) {
        GregorianCalendar[] birthdays = {
                new GregorianCalendar(1988, Calendar.JUNE, 15),
                new GregorianCalendar(1970, Calendar.APRIL, 6),
                new GregorianCalendar(1964, Calendar.FEBRUARY, 31),
                new GregorianCalendar(1990, Calendar.NOVEMBER, 23)
        };
        Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
        System.out.println("min:"+gregorianCalendar.getFirst());
        System.out.println("max:"+gregorianCalendar.getSecond());
    }
    
    public static <extends Comparable & Serializable> Pair<T> minmax(T[] arr){
        if (arr == null || arr.length == 0) {
            return null;
        }
        T max = arr[0];
        T min = arr[0];
        for(int i=0; i<arr.length; i++){
            if(max.compareTo(arr[i]) < 0){
                max = arr[i];
            }if(min.compareTo(arr[i]) > 0){
                min = arr[i];
            }
        }
        return new Pair<T>(min, max);
    }
}


12.5 泛型代码和虚拟机 



虚拟机没有泛型类型对象-所有对象都属于普通类,在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为1.0虚拟机上运行的类文件

    无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名.擦除(erased)类型变量,并替换为限定类型(无限定的变量用Obj)
public class Pair<T> {
  
    public Pair() {
        super();
     }
    public Pair(T first, T second) {
        super();
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
        return first;
    }
    public T getSecond() {
        return second;
    }
    public void setFirst(T first) {
        this.first = first;
    }
    public void setSecond(T second) {
        this.second = second;
    }
    private T first;
    private T second;
}
上面的Pair<T>原始类型如下
public class Pair {
  
    public Pair() {
        super();
     }
    public Pair(Object first, Object second) {
        super();
        this.first = first;
        this.second = second;
    }
    public Object getFirst() {
        return first;
    }
    public Object getSecond() {
        return second;
    }
    public void setFirst(Object first) {
        this.first = first;
    }
    public void setSecond(Object second) {
        this.second = second;
    }
    private Object first;
    private Object second;
}
    因为T是一个无限定的类型,所以直接用Obj来进行替换  的出来的结果是一个普通的类,就像泛型引入java语言之前已经实现的那样
    在程序中可以包含不同类型的Pair,例如Pair<String>,Pair<GregorianCalendar>,而擦除类型后就变成原始的Pair类型了

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

12.5.1 翻译泛型表达式
    当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,例如,下面这个语句序列
       Pair<GregorianCalendar> gregorianCalendar = minmax(birthdays);
       GregorianCalendar min = gregorianCalendar.getFirst();
  擦除getFirst()返回类型后将返回Obj类型,编译器自动插入GregorianCalendar的强制类型转换,也就是说,编译器吧这个方法调用翻译为两条虚拟机指令:
    .对原始方法Pair.getFirst()的调用
    .将返回的Obj类型强制转换为gregorianCalendar

  当存取一个泛型域时也要插入强制类型转换,假设Pair类的fiirst域和second域都是公有的(也许这不是一种良好的编程习惯,但在java中是合法的).表达式:
       GregorianCalendar min = gregorianCalendar.first();
 也会在结果字节码中插入强制类型转换.

12.5.2 翻译泛型方法 (12.5.2 & 12.5.3有点理解不了 所以都没有详细做笔记 以后再深入研究)
 类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法

是一个完整的方法族,而擦除类型之后,只剩下一个方法

12.5.3 调用遗留代码

12.6 约束和局限性

在下面几节中,将阐述使用java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的.
   
12.6.1 不能用基本类型实例化参数 没有Pair<double>只有Pair<Double>

12.6.2 运行时类型查询只适用于原始类型
    虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型(暂时跳过这一章,用的少后面不好理解,以后攻破)








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值