八、泛型程序设计

一、为什么使用泛型设计

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

  在 Java 中增加范型类之前, 泛型程序设计是用继承实现的。ArrayList 类只维护一个 Object 引用的数组:

 这种方法有两个问题。当获取一个值时必须进行强制类型转换;此外,这里没有错误检査。可以向数组列表中添加任何类的对象。

二、定义简单泛型类

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

Pair 类引人了一个类型变量 T,用尖括号 ( < >) 括起来,并放在类名的后面。泛型类可 以有多个类型变量。例如, 可以定义 Pair 类,其中第一个域和第二个域使用不同的类型:

public class Pair<T,U>{...}

类定义中的类型变量指定方法的返回类型以及域和局部变量的类型.换句话说,泛型类可看作普通类的工厂。

三、泛型方法

 实际上,还可以定义一个带有类型参数的简单方法。

public <T> T get(T...a){}

注意,类型变量放在修饰符(这里是 public static) 的 后面,返回类型的前面。 泛型方法可以定义在普通类中,也可以定义在泛型类中。 当调用一个泛型方法时 ’ 在方法名前的尖括号中放人具体的类型:

String middle = ArrayAlg.<String>get("]ohnM, "Q")

在这种情况(实际也是大多数情况)下,方法调用中可以省略 类型参数。编译 器有足够的信息能够推断出所调用的方法。它用 names 的类型(即 String[ ]) 与泛型类型 T[ ] 进行匹配并推断出 T 一定是 String。

四、类型变量的限定

  

class ArrayAIg
{
public static <T> T min(T[] a) // almost correct
{
if (a==null || a.length ==0) return null ;
T smallest = a[0];
for (int i = 1; i < a.length; i ++)
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}

但是,这里有一个问题。请看一下 min方法的代码内部。 变量 smallest 类型为 T, 这意 味着它可以是任何一个类的对象。怎么才能确信 T 所属的类有 compareTo 方法呢? 解决这个问题的方案是将 T 限制为实现了 Comparable 接口(只含一个方法 compareTo 的 标准接口)的类。可以通过对类型变量 T 设置限定(bound) 实现这一点:

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

表示 T 应该是绑定类型的子类型 (subtype)。 T 和绑定类型可以是类, 也可以是接口

一个类型变量或通配符可以有多个限定, 例如: T extends Comparable & Serializable 限定类型用“ &” 分隔,而逗号用来分隔类型变量。 在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用 一个类作为限定,它必须是限定列表中的第一个。

五、泛型代码与虚拟机

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

https://blog.csdn.net/mccand1234/article/details/52013918Java代码编译和执行过程。

类型擦除

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

 在程序中可以包含不N类型的 Pair, 例 如, Pair 或 Pair 。 而擦除类 型后就变成原始的 Pair 类型了。

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

为了提高效率,应该将标签(tagging) 接口(即没有方 法的接口)放在边界列表的末尾。

翻译泛型表达式

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

Pair<Employee> buddies = . .
Employee buddy = buddies.getFirstO;

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

•对原始方法 Pair.getFirst 的调用。

•将返回的 Object 类型强制转换为 Employee 类型。 当存取一个泛型域时也要插人强制类型转换。假设 Pair 类的 first 域和 second 域都是公 有的(也许这不是一种好的编程风格,但在 Java 中是合法的)。表达式: Employee buddy = buddies.first;也会在结果字节码中插人强制类型转换。

翻译泛型方法

public class A<T>{
    public T get(T a){
        //a进行一些操作
        return a;
    }
}
public class B extends A<String>{
    @override
    public String get(String a){
        //a进行一些操作
        return a;
    }
}

擦除后:

public class A{
    public Object get(Object a){
        //a进行一些操作
        return a;
    }
}
public class B extends A{
    @override
    public String get(String a){
        //a进行一些操作
        return a;
    }
}

@override意味着B对父类A中的get方法进行了重写,但是依上面的程序来看,只是重载,依然可以执行父类的方法,这和期望是不附的,也不符合java继承、多态的特性。

注:子类重写父类方法,只要此实例的实际类型是子类类型了(不是引用类型),便不能调用父类的方法,这是语言的多态特性。

为了解决这个问题,java在编译期间加入了桥接方法。编译后再翻译为java原文件,应如下所示:

public class A{
    public Object get(Object a){
        //a进行一些操作
        return a;
    }
}
public class B extends A{
    @override
    public String get(String a){
        //a进行一些操作
        return a;
    }
    public Object get(Object a){
        return get((String)a)
    }
}

总之,需要记住有关 Java 泛型转换的事实:

•虚拟机中没有泛型,只有普通的类和方法。

•所有的类型参数都用它们的限定类型替换。

•桥方法被合成来保持多态。

•为保持类型安全性,必要时插人强制类型转换。

调用遗留代码

【0】README

0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java泛型程序设计 的 调用遗留代码 的知识;

【1】调用遗留代码相关

1.1)设计java 泛型的目的: 允许泛型代码和遗留代码间能够相互操作; 
1.2)看个荔枝:要想设置一个 JSlider 标签, 使用 void setLabelTable(Dictionary table),

  • 1.2.1)在这里, Dictionary是一个原始类型, 因为实现JSlider 类时Java中还不存在泛型。不过填充字典时, 要使用泛型类型。
Dictionary<Integer, Component> labelTable = new Hashtable<>();
labelTable.put(0, new JLable(new ImageIcon("nine.gif")));
  •  
  • 1.2.2)将 Dictionary《Interger, Component》对象传递给 setLabelTable 时, 编译器会发出一个警告;
slider.setLable(labelTable); // Warnning
  •  
  • 因为, 编译器无法确定setLabelTable 可能会对Dictionary对象做什么操作;这个警告对操作并不会产生什么影响, 最多考虑一下 JSlider 有可能用 Dictionary对象做什么就可以了;

1.3)再看个荔枝:(由一个遗留的类得到一个原始类型的对象)。可以将它赋给一个参数化的类型变量, 这样做会产生一个警告;

  • 1.3.1)如:
Dictionary <Integer, Components> labelTable = slider.getLabelTable(); // Warning
  • 1.3.2)这种情况并不会比 有泛型之前的case 更糟糕,最差的case 是程序抛出一个异常;
  • 1.3.3)在查看了警告之后, 可以利用注释 使之消失。注释必须放在生成这个警告的代码所在的方法之前, 如下:
@SuppressWarnings("unchecked")
Dictionary<Integer, Components> labelTable = slider.getLabelTable(); // no warning
  •  
  • 或者, 可以标注整个方法, 如:
@SuppressWarnings("unchecked")
public void configureSlider(){......}
  • 这个标注会关闭对方法中所有代码的检查;

六、约束与局限性

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

不能用类型参数代替基本类型。因此, 没有 Pair, 只 有 Pair 。 当然, 其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double。会报错

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

虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会 得到一个编译器错误, 如果使用强制类型转换会得到一个警告。

例如:

Pair<String> stringPair = . .
Pai「<Employee〉employeePair = . .
if (stringPair.getClas() == employeePair.getClass()) // they are equal

其比较的结果是 true, 这是因为两次调用 getClass 都将返回 Pair.class。

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

不能实例化参数化类型的数组, 例如:

 Pair<String>[] table = new Pair<String>[10]; // Error 

需要说明的是, 只是不允许创建这些数组, 而声明类型为 Pair[] 的变量仍是合法 的。不过不能用 new Pair[10] 初始化这个变量。

如果需要收集参数化类型对象, 只有一种安全而有效的方法:使用 ArrayList:ArrayList<Pair<String>>????

Varargs 警告

public static <T> void addAll(Collections coll, T... ts)
{
for (t : ts) coll.add⑴;
}

为了调用这个方法,Java 虚拟机必须建立一个 Pair 数组, 这就违反了前面的规 则。不过,对于这种情况, 规则有所放松,你只会得到一个警告,而不是错误。 可以采用两种方法来抑制这个警告。一种方法是为包含 addAll 调用的方法增加注解 @ SuppressWamings("unchecked"。) 或者在 Java SE 7中, 还 可 以 用@SafeVarargs 直 接 标 注 addAll 方法 

不能实例化类型变置

不能使用像 new T(...,) newT[...] 或 T.class 这样的表达式中的类型变量。比较传统的解决方法是通过反射调用 Clasmewlnstance 方法来构造泛型对象。

public static <T> Pair<T> makePair(Class<T> cl)
{
try { return new Pair(d.newInstance(). cl.newInstance();) }
catch (Exception ex) { return null; }
}
这个方法可以按照下列方式调用:
Pair<String> p = Pair.makePair(String.class);

注意,Class类本身是泛型。 例如,String.class 是一个 Class 的实例(事实上, 它是唯一的实例)。

不能构造泛型数组

就像不能实例化一个泛型实例一样, 也不能实例化数组。利用反射可以实现:

package Test;

import java.lang.reflect.Array;

/**
 * 
 * @author QuinnNorris
 * 在泛型方法中创建泛型类型的数组
 */
public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        String a = "ccc";//创建一个String,作为泛型类型
        String[] ar = creArray(a);
        for(String art :ar)//循环打印
            System.out.println(art);
    }

    //泛型静态方法
    public static <T> T[]  creArray (T obj){
        T[] arr = (T[])Array.newInstance(obj.getClass(), 5);
        arr[1] = obj;
        System.out.println(arr[1]);
        return arr;
    }
}

上述的方法是完全可行的,我们通过用Array类的newInstance方法来构造了可指定类型的数组。使用反射来完成这个工作也应该是在情理之中。因为泛型类型T在中运行时才可能被确定下来,我们能创建泛型数组也必然是在java运行时想办法,在java运行时能起作用的技术莫过于反射。

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

因此, 禁止使用带有类型变量 的静态域和方法。

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

既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 都是不合法的。 

catch 子句中不能使用类型变量:

public static <T extends Throwable〉void doWork(Class<T> t)
{
try
{
do work
}
catch (T e) // Error can 't catch type variable
{
Logger,global.info(...)
}
}

不过, 在异常规范中使用类型变量是允许的。以下方法是合法的:

public static <T extends Throwable〉void doWork(T t) throws T // OK
try
{
do work
}
catch (Throwable real Cause)
{
t.initCause(real Cause);
throw t;
}
}

可以消除对受查异常的检查

Java 异常处理的一个基本原则是, 必须为所有受查异常提供一个处理器。不过可以利用 泛型消除这个限制。关键在于以下方法:

@SuppressWamings("unchecked")
public static <T extends Throwable〉void throwAs(Throwable e) throws T
{
throw (t) e;
}

以下代码会把所有异常都转换为编译器所认为的 非受查异常:

 try { do work } catch (Throwable t) { B1ock.throwAs(t) ; } 

这有什么意义呢? 正常情况下, 你必须捕获线程 run 方法中的所有受查异常, 把它们 “ 包装” 到非受查异常中, 因为 run 方法声明为不抛出任何受查异常。 不过在这里并没有做这种“ 包装”。我们只是抛出异常, 并“ 哄骗” 编译器, 让它认为 这不是一个受查异常。 通过使用泛型类、 擦除和 @SuppressWamings 注解, 就能消除 Java 类型系统的部分基本 限制。

注意擦除后的冲突

  • 假定像下面这样将 equals 方法添加到 Pair 类中:

public class Pair { public boolean equals(T value) { return first,equals(value) && second,equals(value); }

考虑一个 Pair。从概念上讲, 它有两个 equals 方法:

boolean equals(String) // defined in Pair

boolean equals(Object) // inherited from Object 但是,直觉把我们引入歧途。

方法擦除 boolean equals(T) 就是 boolean equals(Object) 与 Object.equals 方法发生冲突。

当然,补救的办法是重新命名引发错误的方法。

  • 泛型规范说明还提到另外一个原则:“ 要想支持擦除的转换, 就需要强行限制一个类或类 型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。” 例如, 下述代码是非法的:
class Employee implements Comparab1e<Employee> { . . . }
class Manager extends Employee implements Comparable<Hanager>
{ . . . } // Error

其原因非常微妙, 有可能与合成的桥方法产生冲突。实现了 C0mpamble 的类可以获得一 个桥方法:

public int compareTo(Object other) { return compareTo((X) other); }

对于不同类型的 X 不能有两个这样的方法。

七、泛型类型的继承规则

考虑一个类和一个子类, 如 Employee 和 Manager。Pair 是 PaiKEmployee> 的一个子类吗? 答案是“ 不是”

无论 S 与 T 有什么联系 。通常, Pair<T> 与 Pair<S>没有什么联系。

最后, 泛型类可以扩展或实现其他的泛型类。就这一点而言,与普通的类没有什么 区别。例如, ArrayList<T> 类实现 List<T> 接口。这意味着, 一个 ArrayList<Manager> 可 以被转换为一个 List<Manager>。但是, 如前面所见, 一个 ArrayList<Manager> 不是一个 ArrayList<Emplyee> 或 List<Emplyee>.

八、通配符类型

  通配符类型中, 允许类型参数变化。 例如, 通配符类型:

Pair<? extends Employee〉

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

通配符的超类型限定

  通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超 类型限定 (supertypebound), 如下所亦:

  ? super Manager

带有超类型限定的通配符可以向泛型对象写人,带有子类型限定的通配符可 以从泛型对象读取。带有extends的相反。

还可以使用无限定的通配符, 例如,Pair<?> 。初看起来,这好像与原始的 Pair 类型一样。 实际上, 有很大的不同。类型 Pair 有以下方法: ? getFi rst() void setFirst(?). getFirst 的返回值只能赋给一个 Object。setFirst 方法不能被调用, 甚至不能用 Object 调 用。Pair 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。 0 注释: 可以调用 setFirst(null)。

通配符捕获

package pair3;

/**
 * @version 1.01 2012-01-26
 * @author Cay Horstmann
 */
public class PairTest3
{
   public static void main(String[] args)
   {
      Manager ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
      Manager cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
      Pair<Manager> buddies = new Pair<>(ceo, cfo);      
      printBuddies(buddies);

      ceo.setBonus(1000000);
      cfo.setBonus(500000);
      Manager[] managers = { ceo, cfo };

      Pair<Employee> result = new Pair<>();
      minmaxBonus(managers, result);
      System.out.println("first: " + result.getFirst().getName() 
         + ", second: " + result.getSecond().getName());
      maxminBonus(managers, result);
      System.out.println("first: " + result.getFirst().getName() 
         + ", second: " + result.getSecond().getName());
   }

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

   public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
   {
      if (a.length == 0) return;
      Manager min = a[0];
      Manager max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.getBonus() > a[i].getBonus()) min = a[i];
         if (max.getBonus() < a[i].getBonus()) max = a[i];
      }
      result.setFirst(min);
      result.setSecond(max);
   }

   public static void maxminBonus(Manager[] a, Pair<? super Manager> result)
   {
      minmaxBonus(a, result);
      PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
   }
   // Can't write public static <T super manager> ...
}

class PairAlg
{
   public static boolean hasNulls(Pair<?> p)
   {
      return p.getFirst() == null || p.getSecond() == null;
   }

   public static void swap(Pair<?> p) { swapHelper(p); }

   public static <T> void swapHelper(Pair<T> p)
   {
      T t = p.getFirst();
      p.setFirst(p.getSecond());
      p.setSecond(t);
   }
}


九、反射与泛型

反射允许你在运行时分析任意的对象。如果对象是泛型类的实例,关于泛型类型参数则 得不到太多信息,因为它们会被擦除。在下面的小节中,可以了解利用反射可以获得泛型类 的什么信息。

泛型 Class 类

package genericReflection;

import java.lang.reflect.*;
import java.util.*;

/**
 * @version 1.10 2007-05-15
 * @author Cay Horstmann
 */
public class GenericReflectionTest
{
   public static void main(String[] args)
   {
      // read class name from command line args or user input
      String name;
      if (args.length > 0) name = args[0];
      else
      {
         try (Scanner in = new Scanner(System.in))
         {
            System.out.println("Enter class name (e.g. java.util.Collections): ");
            name = in.next();
         }
      }

      try
      {
         // print generic info for class and public methods
         Class<?> cl = Class.forName(name);
         printClass(cl);
         for (Method m : cl.getDeclaredMethods())
            printMethod(m);
      }
      catch (ClassNotFoundException e)
      {
         e.printStackTrace();
      }
   }

   public static void printClass(Class<?> cl)
   {
      System.out.print(cl);
      printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
      Type sc = cl.getGenericSuperclass();
      if (sc != null)
      {
         System.out.print(" extends ");
         printType(sc, false);
      }
      printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
      System.out.println();
   }

   public static void printMethod(Method m)
   {
      String name = m.getName();
      System.out.print(Modifier.toString(m.getModifiers()));
      System.out.print(" ");
      printTypes(m.getTypeParameters(), "<", ", ", "> ", true);

      printType(m.getGenericReturnType(), false);
      System.out.print(" ");
      System.out.print(name);
      System.out.print("(");
      printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
      System.out.println(")");
   }

   public static void printTypes(Type[] types, String pre, String sep, String suf, 
         boolean isDefinition)
   {
      if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })) return;
      if (types.length > 0) System.out.print(pre);
      for (int i = 0; i < types.length; i++)
      {
         if (i > 0) System.out.print(sep);
         printType(types[i], isDefinition);
      }
      if (types.length > 0) System.out.print(suf);
   }

   public static void printType(Type type, boolean isDefinition)
   {
      if (type instanceof Class)
      {
         Class<?> t = (Class<?>) type;
         System.out.print(t.getName());
      }
      else if (type instanceof TypeVariable)
      {
         TypeVariable<?> t = (TypeVariable<?>) type;
         System.out.print(t.getName());
         if (isDefinition)
            printTypes(t.getBounds(), " extends ", " & ", "", false);
      }
      else if (type instanceof WildcardType)
      {
         WildcardType t = (WildcardType) type;
         System.out.print("?");
         printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
         printTypes(t.getLowerBounds(), " super ", " & ", "", false);
      }
      else if (type instanceof ParameterizedType)
      {
         ParameterizedType t = (ParameterizedType) type;
         Type owner = t.getOwnerType();
         if (owner != null)
         {
            printType(owner, false);
            System.out.print(".");
         }
         printType(t.getRawType(), false);
         printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
      }
      else if (type instanceof GenericArrayType)
      {
         GenericArrayType t = (GenericArrayType) type;
         System.out.print("");
         printType(t.getGenericComponentType(), isDefinition);
         System.out.print("[]");
      }
   }
}

猜你喜欢

转载自blog.csdn.net/qq_39326472/article/details/87893110