您的位置:首页 > 编程语言 > Java开发

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

2018-01-06 14:25 435 查看
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表示集合元素的类型,K和V分别表示表的关键字的键值对(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 = {2, 5, 6,8,9};

        System.out.println(PairAlg.<Integer>getMiddle(nums));//注意尖括号位置

        System.out.println(PairAlg.<String>getMiddle(words));//注意尖括号位置

在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型

12.4 类型变量的限定

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






public static <T 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 <T extends Comparable> 


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



 <T extends Bounding Type> 


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

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



 <T 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 <T 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 运行时类型查询只适用于原始类型

    虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型(暂时跳过这一章,用的少后面不好理解,以后攻破)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息