核心技术(卷一)06、第12章-泛型程序设计

泛型程序设计

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

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

  2. 定义简单泛型类

    public class Pair<T>{ //T:类型变量
      private T first;
      private T second;
    
      public Pair(){
        first = null;
        second = null;
      }
    
      public Pair(T first,T second){
        this.first = first;
        this.second = this.second;
      }
    
      public void setFirst(T first){
        this.first = first;
      }
      public T getFirst(){
        return first;
      }
    
      public void setSecond(T second){
        this.second = second;
      }
      public T getSecond(){
        return second;
      }
    }
    
    /**
     *实例化泛型类
     *Sting:类型参数
     */
    Pair<String> p = new Pair<>();
    

    泛型类可以有多个类型变量,如public class Pari<T,U>{}

  3. 泛型方法

    除了可以定义泛型类外,还可以定义带类型参数的简单方法

    class ArrayAlg{
      public static <T> T getMiddle(T...a){
        return a[a.length/2];
      }
    }
    
    //调用泛型方法
    String middle = ArrayAlg.<String>getMiddle("a","b","c");
    /*
     * 下面的代码编译器会自动打包参数为1个Double和两个Integer,然后寻找这两个类的公共超类型
     * 找到2个这样的超类,一个是Number,一个是Comparable,将会报错
     * 解决的办法就是将所有参数写为double值
     */
    double middle = ArrayAlg.getMiddle(2.3,10,0);
    

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

    调用泛型方法时,类型参数放在方法名之前的尖括号中。

  4. 泛型变量的限定

    有时,类或方法需要对类型变量加一约束。

    class ArrayAlg{
      /*
       *约束方法调用者传递的类型参数必须实现Comparable接口
       *不管绑定类型是接口还是类,都使用关键字extends
       *一个类型变量可以有多个限定:<T extends Comparable & Serializable>
       */
      public static <T extends Comparable> T min(T[] a){
        if (a == null || a.length == 0){
          return null
        }
        T smallest = a[0];
        for (int i = 1; i < a.length; i++){
          if (a[i] < smallest){
             smalleset = a[i];
          }
        }
      }
    }
    
  5. 擦除类型

    在程序中可以有不同类型的Pair,如Pair<String>,Pair<Date>,擦除类型后,就变成原始类型Pair
    原始类型Pair如下:

    /*
     * 类型查出后,无限定的变量用Object替换,
     * 有限定的变量用限定的第一个类型替换,如:
     * public class Pair<T extends Comparable & Serializable>
     * 擦除类型后,用Comparable代替;
     * 类型限定时,将标签接口放在限定的最后
     */
    public class Pair{
      private Object first;
      private Object seconde;
    
      public Pair(Object first,Object second){
        this.first = first;
        this.second = this.second;
      }
    
      public void setFirst(Object first){
        this.first = first;
      }
      public Object getFirst(){
        return first;
      }
    
      public void setSecond(Object second){
        this.second = second;
      }
      public Object getSecond(){
        return second;
      }
    }
    
  6. 翻译泛型表达式

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

    Pair<Employee> buddies = ...;
    /*
    * 擦除getFirst的返回类型后将返回Object,编译器将自动插入Employee的强制类型转换
    */
    Employee buddy = buddies.getFirst();
    
  7. 翻译泛型方法

    方法的类型擦除:

    public static <T extends Comparable> T min(T[] a);
    //擦除类型后:
    public static Comparable min(Comparable[] a);
    

    方法的擦除带来一个复杂的问题:

    class DateIntervel extends Pair{
      public void setSecond(Date second){
        if (second.compara(getFirst())>=0){
          super.setSecond(Seconde);
        }
      }
    }
    

    DateIntervel继承自擦除类型的Pair,则DateIntervel将有两个setSecond方法:

    public void setSecond(Date second)

    和继承自Pair的:

    public void setSecond(Object second)

    显然,DateIntervel类中定义的setSecond方法应该是对父类方法的覆盖才对。这时,擦除参数与多态发生了冲突。解决此问题的方式是:编译器在DateIntervel类中生成一个桥方法

    public void setSecond(Object second){
      setSecond((Date)second);
    }
    
  8. 约束与局限(大多数限制都是由于类型擦除引起的)

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

    2、 运行时类型检查只适用于原始类型:

    if (a instanceof Pair<String>) //只能判断变量a是否引用了Pair类型,无法具体到Pair<String>
    
    //同理,getClass 也将返回原始类型
    Pair<String> stringPair = ...;
    stringPari.getClass();//Pair.class
    

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

 例如:

 ```java
 Pair<String>[] table = new Pair<>[10]; //这是错误的
 ```

 擦除之后,`table` 的类型是Pair[],可以类型转换为Object[]。

 ```java
 Pair[] table = new Pair[10];
 Object[] objarray = table;

 /*
  * objarray[0]引用了table[0],table[0]是Pair类型
  */
 objarray[0] = "string"; // java.lang.ArrayStoreException

 /*
  * 能够通过数组类型检查,但是任然会导致一个类型错误(不是Pair<String>)
  */
 objarray[0] = new Pair<Employee>();
 ```

 >只是不能创建参数化类型数组,声明类型为`Pair<String>[]`的变量任然是合法的,但是不能用`new Pair<String>[10]`来初始化变量。

 参数化类型集合使用`ArrayList`来实现。

 4、不能实例化类型变量

 不能使用像`new T(...)`、`new T[...]`或`T.class`这样的表达式中的类型变量

 在泛型类中,可以这样来创建一个类型变量实例:

 ```java
 public static <T> Pair<T> makePair(Class<T> cl){
   try{
     return new Pair<>(cl.newInstance(),cl.newInstance());
   }catch(Exception e){
     return null;
   }
 }
 /*
  * Class本身是一个泛型类。String.class 是Class<String>的唯一实例
  */
 Pair<String> p = Pair.makePair(String.class);
 ```

 5、禁止使用带有类型变量的静态域和方法。

 6、不能捕获或者抛出泛型类的实例
  1. 泛型类型的继承

    无论ST有什么关系,Pair<S>Pair<T>之间都没有任何联系

    但是,永远可以将一个参数化类型转换为一个原始类型。如:Pair<Manager>是原始类型Pair的一个子类。

    Pair<Manager> manager = new Pair<>(new Manager(),new Manager());
    Pair rowManager = manager;
    Manager m1 = manager.getFirst();//ok
    Manager m2 = rowManager.getFirst();//错误: 不兼容的类型: Object无法转换为Manager
    

    泛型类可以实现或者扩展其他的泛型类。


    5893832-beaec005d203a47a.png
    泛型类继承
  2. 通配符类型

    Pair<? extends Employee>表示任何泛型类型,它的类型参数是Employee的子类,如Pair<Manager>,但不是Pair<String>

    作用:

    public static void printBuddies(Pair<Employee> p){
      Employee first = p.getFirst();
      Employee second = p.getSecond();
      System.out.println(first.getName() + " and " + secon.getName() + " are buddies");
    }
    

    调用printBuddies()方法,只能向其传Pair<Employee>类型的参数,而不能传Pair<Manager>类型的参数,因为这两个类型之间没有继承关系。

    为了解除这一限制:

    public static void pringBuddies(Pair<? extends Employy> p){
      ...
    }
    

    这样只要是Pair<Employee>类或其子类都可以作为printBuddies()的参数。

    5893832-af258dace941e38a.png
    通配符类型继承关系

    不安全的修改器方法:

    Pair<Manager> m = new Pair<>(new Manager("Bob",1300,new Address()),
    new Manager("Jack",1500,new Address()));
    Pair<? extends Employee> e = m;
    e.setFirst(new Manager("Tony",1200,new Address()));
    
    5893832-eba6b6d64979a9fb.png
    不安全的修改器方法

    对于Pair<? extends Employee>,它的访问器和修改器似乎是:

    ? extends Employee getFirst();
    
    void setFirst(? extends Employee);
    

    不能调用setFirst,编译器只知道需要某个Employee类的子类,而不知道具体需要哪个类型。
    getFirst的返回值赋给Employee的一个引用就没有问题。

    5893832-92326b2784c1b2a3.png
    image
  3. 通配符的超类型限定

    通配符还可以指定一个超类型限定:? super Manager,这个通配符限制为Manager的所有超类型。

    5893832-aa3902ef843fc501.png
    超类型限定继承关系

    在子类型限定中,可以调用访问器方法,不能调用修改器方法;在超类型限定中,可以调用修改器方法,不能调用访问器方法。Pair<? super Manager>的方法似乎是:

    void setFirst(? super Manager);
    
    ? super Manager getFirst();
    

    调用修改器方法,将Manager类或者其子类作为参数,都是可行的。但是调用访问器方法时,返回的对象类型就得不到保证,只能用Object来接收。

    5893832-ed09e8f6ddfaf1b7.png
    image

  4. 无限定通配符

    Pair<?>有方法:

    ? getFirst();
    void setFirst(?);
    

    getFirst的返回值只能赋给Object,不能调用setFirst方法。

    PairPair<?>的区别:可以使用任何Object类型调用Pair原始类型的setFirst方法。

    5893832-fcd985d0bcfe3e1d.png
    Pair<?>继承结构

    根据该继承结构,Pair<?>实现许多简单的操作非常有用(只涉及到调用访问器的一些操作),如判断一个pair是否包含一个null引用:

    public static boolean hasNulls(Pair<?> p){
      return p.getFirst() == null || p.getSecond() == null;
    }
    
  5. 通配符捕获

    编写一个交换Pair元素的方法

    public static void swap(Pair<?> p){
      //因为不知道Pair元素的具体类型,我们无法暂存第一个元素
    
      swapHelper(p);//调用辅助方法
    }
    
    public static <T> void swapHelper(Pair<T> p){
      //参数T捕获通配符,此时T不再是类型变量,而是一个具体的类型
      T t = p.getFirst();
      p.setFirst(p.getSecond());
      p.setSecond(t);
    }
    
    Pair<Manager> m = new Pair<>(new Manager("Bob",1300,new Address()),new Manager("Jack",1500,new Address()));
    Pair.swap(m);
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值