10. 泛型 Part 1 --- 学习笔记

来源:互联网 发布:淘宝女装店招素材 编辑:程序博客网 时间:2024/06/05 10:18


通过本章的学习可以达到以下目标

  • 掌握泛型的基本原理及其应用
  • 掌握泛型通配符的使用
  • 指定泛型操作中的上限及下限
  • 在接口上应用泛型
  • 掌握泛型方法及泛型数组的使用
  • 如果需要更加合理的应用泛型,就需要结合类集框架及反射机制。

             所谓的泛型(Generics)就是指在对象建立时不指定类中属性的具体类型,而是由外部在声明及实例化对象时指定类型。


10.1 为什么使用泛型

应用实例:设计一个可以表示坐标点的类,昨天由X和Y组成,坐标的表示方法有以下三种:

  1. 整数表示: x = 10、y = 20
  2. 小数表示: x = 10.5、y = 20.6
  3. 字符串表示: x= "东经180度"、 y = "北纬210度"

由于x和y中所保存的数据类型有3种: int、float、String。 想要同时接收这3种数据类型,只能使用Object类。因为Object类可以接收任何类型的数据,都会发生自动向上转型操作。 但是当x、y的数据类型不一致的时候,则会产生一个运行时异常ClassCastException(编译可以成功)

       ****************接收不同类型的数据类型。避免运行时产生ClassCastException异常******************


10.2 泛型应用

       10.2.1 泛型的基本应用

            泛型可以解决数据类型的安全性问题,其主要原理是在类声明时通过一个标识标识类中某个属性的类型或者是某个方法的返回值及参数类型。这样在类声明或对象的实例化时只要指定好需要的类型即可。

           泛型类的定义格式如下:

<span style="font-size:14px;color:#33cc00;">[访问权限] class 类名称<泛型类型标识1, 泛型类型标识2, ..., 泛型类型标识n>{     [访问权限] 泛型类型标识 变量名称;     [访问权限] 泛型类型标识 方法名称(){}     [访问权限] 返回值类型声明 方法名称(泛型类型标识 变量名称){}}</span>

           泛型对象的定义格式如下:

类名称<具体类> 对象名称 = new 类名称<具体类>();

范例:声明泛型

class Point<T>{         //此处可以是任意的标识符号,T是type的简称     private T var;        //此变量的类型由外部决定     public T getVar(){                //返回值的类型由外部决定         return this.var;                 //设置的类型由外部决定     }     public void setVar(T var){         this.var = var;     }}

    上面代码中的Point类在声明时使用了“<T>”的形式,T表示此类型是由外部调用本类时指定的,这里使用任意的字母都可以,如“<A>”、“<B>”,之所以使用“<T>”是因为T是type的缩写,表示类型,这样比较好理解。

    之后再类中定义的var属性的类型也是T,这就表示var这个属性的类型也是由外部来决定的,而不是固定的。

    同理,setter方法中的参数类型以及getter方法中的返回值类型也由外部设置。

范例:使用Point类将var的类型设置成整数

class Point<T>{    private T var;    public T getVar(){        return this.var;    }    public void setVar(T var){        this.var = var;    }}public class GenericsDemo01{    public static void main(String args[]){        Point<Integer> p = new Point<Integer>();     //里面的var类型为Integer类型        p.setVar(28);                                //设置数字,自动装箱        System.out.println(p.getVar() * 2);    }}

以上程序将Point类中的var属性设置成Integer类型,所以在声明及实例化对象的时候使用Point<Integer>。 这样实际上Point类中的setter和getter方法就变成了以下形式:

public Integer getVar(){     return this.var;}public void setVar(Integer var){     this.var = var;}
上面代码只是对设置泛型之后的一种说明,一切操作都是按照数字的方式进行操作的。

范例:使用以上的Point类,将var设置成String类型

class Point<T>{    private T var;    public T getVar(){        return this.var;    }    public void setVar(T var){        this.var = var;    }}public class GenericsDemo02{    public static void main(String args[]){        Point<String> p = new Point<String>();     //里面的var类型为String类型        p.setVar("forfan06");                                //设置字符串,自动装箱        System.out.println(p.getVar().length());    }}

如果设置的内容与泛型所指定的类型不一样,则在编译时就会出错!!!!

class Point<T>{    private T var;    public T getVar(){        return this.var;    }    public void setVar(T var){        this.var = var;    }}public class GenericsDemo03{    public static void main(String args[]){        Point<Integer> p = new Point<Integer>();     //里面的var类型为Integer类型        p.setVar("forfan06");                                //设置字符串类型,自动装箱后与指定的Integer类型不一致        System.out.println(p.getVar().length());    }}

编译时出错:

GenericsDemo03.java:13: error: method setVar in class Point cannot be applied to given types;        p.setVar("forfan06");                                //设置字符串类型,自动装箱后与指定的Integer类型不一致         ^  required: Integer  found: String  reason: actual argument String cannot be converted to Integer by method invocation conversion  where T is a type-variable:    T extends Object declared in class Point

       10.2.2 使用泛型修改代码

             实现前面的问题,类Point是一个表示坐标点的类x、y。  此时Point类中必须保证x、y坐标的数据类型一致。 最好使用泛型。代码如下:

class Point<T>{    private T x;    private T y;    public T getX(){        return this.x;    }    public T getY(){        return this.y;    }    public void setX(T x){        this.x = x;    }    public void setY(T y){        this.y = y;    }}public class GenericsDemo04{    public static void main(String args[]){        Point<Integer> p = new Point<Integer>();        p.setX(17);                //设置整数,自动装箱。        p.setY(58);        int x = p.getX();           //自动拆箱!!        int y = p.getY();        System.out.println("整数表示,x坐标:" + x);        System.out.println("整数表示,y坐标:" + y);    }}

如果此时y坐标指定的类型不是Integer,则将在编译时就会出错!!!

class Point<T>{    private T x;    private T y;    public T getX(){        return this.x;    }    public T getY(){        return this.y;    }    public void setX(T x){        this.x = x;    }    public void setY(T y){        this.y = y;    }}public class GenericsDemo05{    public static void main(String args[]){        Point<Integer> p = new Point<Integer>();        p.setX(17);                //设置整数,自动装箱。        p.setY("forfan06");        //与Point声明时的数据类型不一致,编译通不过!!        int x = p.getX();           //自动拆箱!!        int y = p.getY();        System.out.println("整数表示,x坐标:" + x);        System.out.println("整数表示,y坐标:" + y);    }}

编译出错:

GenericsDemo05.java:22: error: method setY in class Point cannot be applied to given types;        p.setY("forfan06");        //与Point声明时的数据类型不一致,编译通不过!!         ^  required: Integer  found: String  reason: actual argument String cannot be converted to Integer by method invocation conversion  where T is a type-variable:    T extends Object declared in class Point1 error编译错误

  • 加入泛型的最大好处实际上就是避免了类转换异常(ClassCastException)的发生,这样使程序的操作更加安全!!!!

       10.2.3 泛型应用中的构造方法

            构造方法可以为类中的属性初始化,那么如果类中的属性通过泛型指定,而又需要通过构造设置属性内容时,构造方法的定义与之前构造方法相同。不需要象声明类那样指定类型。具体格式如下:

[访问权限] 构造方法([泛型类型 参数名称]){}

泛型类的构造方法与普通类的构造方法是一样的,也就是说即使有泛型声明,也只是在类的定义上声明,而与构造方法的定义无关!!!

       10.2.4 指定多个泛型类型

          如果一个类中有多个属性需要使用不同的泛型声明,则可以在声明类时指定多个泛型类型。

class Notepad<K, V>{                 //此处指定两个泛型类型    private K key;                   //变量key、value的类型都是由外部决定的    private V value;    public K getKey(){               //setter和getter方法        return key;    }    public void setKey(K key){        this.key = key;    }    public V getValue(){        return value;    }    public void setValue(V value){        this.value = value;    }}public class GenericsDemo06{    public static void main(String args[]){        Notepad<String, Integer> t = null;               //定义两个泛型类型的对象        t = new Notepad<String, Integer>();              //里面的key为String,value为Integer        t.setKey("forfan06");                            //设置属性内容,自动自动装箱操作        t.setValue(27);        System.out.print("姓名:" + t.getKey());        System.out.println(", 年龄:" + t.getValue());    }}


10.3 泛型的安全警告

                在泛型应用中最好在声明类对象时就指定好其内部的数据类型,如Info<String>,如果不指定类型,这样用户在使用这样的类时,就会出现不安全的警告信息。

范例:不指定范例类型

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo07{    public static void main(String args[]){        Info i = new Info();                    //警告,没有指定泛型类型        i.setVar("forfan06");        System.out.println("内容:" + i.getVar());    }}

编译时出现以下警告:

GenericsDemo07.java uses unchecked or unsafe operations.Note: Recompile with -Xlint:unchecked for details.

      以上程序虽然在编译时出现了警告,但是并不影响程序的运行,这是因为在泛型的操作中为了方便用户的使用,就算没有指定泛型程序也可以正常使用,而所有的类型统一使用Object进行接收,所以上面程序的var属性实际上是Object类型的,也就是在定义时将泛型擦除了。以上程序类似于下面的定义:

public class GenericsDemo08{    public static void main(String args[]){        Info<Object> i = new Info<Object>();                    //指定为Object为泛型类型        i.setVar("forfan06");        System.out.println("内容:" + i.getVar());    }}

               GenericsDemo08没有任何的意义,因为就算不设置泛型也是Object,但这样做的唯一好处就是,在编译时警告信息消失了。


10.4 通配符

         以上的范例程序在操作时都设置了一个固定的类型,在泛型操作中也可以通过通配符接收任意指定泛型类型的对象。

       10.4.1匹配任意类型的通配符

           在开发中对象的引用传递是最常见的,但是如果在泛型类的操作中,在进行引用传递时泛型类型必须匹配才可以传递,否则是无法传递的。

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo09{    public static void main(String args[]){        Info<String> i = new Info<String>();                    //指定String为泛型类型        i.setVar("forfan06");        fun(i);                                                 //此处无法传递    }    public static void fun(Info<Object> temp){                  //此方法可以接收Object泛型类型的Info对象        System.out.println("内容:" + temp);    }}

编译时错误:

GenericsDemo09.java:17: error: method fun in class GenericsDemo09 cannot be applied to given types;        fun(i);                                                 //此处无法传递        ^  required: Info  found: Info  reason: actual argument Info cannot be converted to Info by method invocation conversion1 error

         此时尽管String是Object类的子类,但是在进行引用传递时也同样无法进行操作。此时Info<String>、Info<Object>并无父类和子类的关系,不能够完成转型操作(向上转型)。

         Java中引入了通配符“?”,来表示可以接收此类型的任意泛型对象。所以将GenericsDemo09代码作如下修改(使用通配符?)

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo10{    public static void main(String args[]){        Info<String> i = new Info<String>();                    //指定String为泛型类型        i.setVar("forfan06");        fun(i);                                                 //此处无法传递    }    public static void fun(Info<?> temp){                  //此方法可以接收任意Info泛型类型的对象        System.out.println("内容:" + temp);    }}

在程序GenericsDemo10种的fun()方法使用了Info<?>的形式,表示可以接收任意的泛型类型对象,这样做的话,fun()方法定义的就比较合理了。但是在使用以上语法时也要注意一点: 如果使用“?”接收泛型对象时,不能设置被泛型指定的内容。    但是可以设置为null值得!!!!例如:

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo11{    public static void main(String args[]){        Info<?> i = new Info<String>();                    //使用“”接收泛型对象        i.setVar("forfan06");                              //错误,无法设置    }}

编译时得到错误:

GenericsDemo11.java:16: error: method setVar in class Info cannot be applied to given types;        i.setVar("forfan06");                              //错误,无法设置         ^  required: CAP#1  found: String  reason: actual argument String cannot be converted to CAP#1 by method invocation conversion  where T is a type-variable:    T extends Object declared in class Info  where CAP#1 is a fresh type-variable:    CAP#1 extends Object from capture of ?1 error编译错误

       10.4.2 受限泛型

             在引用传递中,在泛型操作中也可以设置一个泛型对象的范围上限和范围下限。

             范围上限使用extends关键字声明,表示参数化的类型可能是所指定的类型或者是此类型的子类;

             范围下限使用super关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类,或者是Object类。

<span style="font-size:14px;color:#6600cc;">设置上限</span>声明对象:  类名称<? extends 类> 对象名称定义类: [访问权限] 类名称<泛型标识 extends 类>{}<span style="font-size:14px;color:#6600cc;">设置下限</span>声明对象: 类名称<? super 类> 对象名称<del><span style="color:#ff0000;">定义类:[访问权限] 类名称<泛型标识 super 类>{}               //这里是错误的!!!!!!!!!!!!!!!!!!</span></del>

范例: 泛型的上限

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo12{    public static void main(String args[]){        Info<Integer> i1 = new Info<Integer>();              //声明Integer、Float泛型的对象        Info<Float> i2 = new Info<Float>();        i1.setVar(28);                                       //设置内容,自动装箱        i2.setVar(27.2f);        fun(i1);        fun(i2);    }    //接收Info对象,范围上限设置为Number类,所以只能接收数字类型的数据。此方法只能接收泛型为Number类或者Number类型的子类。此时若传递一个String类的泛型对象,则编译时将会出现错误!!!!Info<String> i3 = new Info<String>(); i3.setVar("forfan06"); fun(i3); 此时fun(i3)编译会出错    public static void fun(Info<? extends Number> temp){        System.out.print(temp + "、");    }}

也可以直接在类的声明处指定泛型的上限范围,如:

class Info<T extends Number>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo13{    public static void main(String args[]){        Info<Integer> i1 = new Info<Integer>();        System.out.println("内容: " + i1);        Info<String> i2 = new Info<String>();                           //<del><span style="color:#cc0000;">编译错误,声明的是String类型的泛型对象,不是Number类的子类!!</span></del>!!    }}

范例: 泛型的下限   当使用的泛型只能在本类及其父类类型上应用时,就必须使用泛型的范围下限进行配置:

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo14{    public static void main(String args[]){        Info<Object> i1 = new Info<Object>();              //声明Object, String泛型的对象        Info<String> i2 = new Info<String>();        i1.setVar(new Object());                                       //设置内容,自动装箱        i2.setVar("forfan06");        fun(i1);        fun(i2);        Info<Integer> i3 = new Info<Integer>();        i3.setVar(27);        fun(i3);                                    <span style="color:#ff0000;">//编译出错,不满足下限要求</span>    }    //接收Info对象,范围下限设置为String类,只能接收String或其父类Object类型的泛型    public static void fun(Info<? super String> temp){        System.out.print("内容:" + temp);    }}

不能这样子来指定泛型的下限:

<span style="font-size:14px;color:#cc0000;"><del> class A<T super String>{}</del></span>

原因如下:

Nitish B. Well bounded types cannot use "super" as every class's super is Object. Now if you say that its Object, it can be anything other than String which makes the bound useless.   Koustav C.  Hey Nitish,  Thanks for your quick response as always. But it will be good if you can be a bit elaborate.   Nitish B.  Well "T super String" => "Object". "Object" is extended by many which aren't String. So if I create a class A<T super String>{ T test; public void set(T a) {test = a}} and if its valid, I can create an instance A<Object> a = new A<Object>() and then do a.set((Some other class) object instance), what do you think will happen?   Koustav C.  Hey I got it.Thanks a lot. 

所以下面的这段代码带编译通不过!!!!!!!!!!

<del><span style="color:#ff0000;">class Info<T super String>{</span></del>    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo15{    public static void main(String args[]){        Info<String> i1 = new Info<String>();        i1.setVar("forfan06");        System.out.prinln(i1.getVar());    }}

10.5 泛型与子类继承的限制

             一个类的子类可以通过对象多态性为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接收的。 例如, Info<String>不能使用Info<Object>接收!!!!!!!

class Info<T>{    private T var;    public T getVar(){        return var;    }    public void setVar(T var){        this.var = var;    }    public String toString(){        return this.var.toString();    }}public class GenericsDemo16{    public static void main(String args[]){        Info<String> i1 = new Info<String>();      //泛型类型为String        Info<Object> i2 = null;                    //泛型类型为Object        i2 = i1;                //两个Info对象进行转换,Info<String>  -->  Info<Object>    }}

编译时出错:

GenericsDemo16.java:17: error: incompatible types        i2 = i1;                //两个Info对象进行转换,Info  -->  Info             ^  required: Info  found:    Info1 error编译错误

以上错误表示: 不匹配的类型,即Info<String>无法转换为Info<Object>。 虽然String是Object类的子类。但是在泛型操作中此概念无效。此时只能使用通配符“?”接收·!!!!

0 0
原创粉丝点击