阅读 308

java 泛型程序设计

泛型介绍

泛型程序设计 :可以被很多不同类型的对象所重用。比那些直接使用Object变量,然后强制类型转换的代码具有跟好的安全性和可读性。

使用类型参数可以将需要使用的类型,提前声明

ArrayList<String> newlist = new ArrayList<String>();复制代码

使用类型参数可以告知这个类适用于什么类型,当调用对应的get()方法的时候,不需要进行强制类型转换,编译器本事就知道其对应的类型。

当实现一个泛型的时候非常不容易,因为你需要知道这个这个类对应的所有用途及其类型,所以java提供了通配符类型,来解决这个问题。

定义简单泛型类

类型变量使用大写形式,且比较短,这是很常见的。在java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字和值的类型。T(需要时还可以用临近的字母U和S)表示类型。

泛型类,就是指具有一个或者多个类型变量,也就是说这个类适应这几种类型,对于之后在那类来说,我们只关注泛型,而不会为数据村吃的细节烦恼。

使用类型变量T,用<>括起来,放在类名后面。这个泛型可以有多个类型变量,如<T,U>

可以使用类定义的类型变量指定类中属性和方法的类型。

public class Pari<T> {  
  
    private T first;  
    private T second;  
      
    public Pari(){  
        first = null;  
        second = null;  
    }  
    public Pari(T first,T second){  
        this.first = first;  
        this.second = second;  
    }  
      
    public T getFirst(){  
        return first;  
    }  
      
    public T getSecond(){  
        return second;  
    }  
      
    public void setFirst(T value){  
        first = value;  
    }  
    public void setSecond(T value){  
        second = value;  
    }  
} 
复制代码

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

其实泛型类可以看做是普通类的工厂。

泛型方法

泛型方法既可以在普通类中,也可以在泛型类中,定义方式是在方法名前加<T> T,说明该方法是泛型方法

class ArrayAlg
{
    public static<T> T getMiddle(T... a)
    {
        return a[a.length / 2];
    }
}
复制代码

当调用一个泛型方法时, 在方法名前的尖括号中放入具体的类型

String middle = ArrayAlg.<String>getMiddle("john", "Q.", public ");复制代码

方法调用中可以省略 类型参数, 编译器有足够的信息能偶推断出所调用的方法;

也就是说, 可以调用

String middle = ArrayAlg.getMiddle("john", "Q.", public ");复制代码

 对于泛型方法的引用都没有问题。 偶尔, 编译器也会提示错误,

double middle = ArrayAlg.getMiddle(3.14, 0, 1729);1复制代码
编译器将会自动打包参数为 1个 Double 和 2个Integer 对象,而后寻找这些类的共同超类型。
事实上, 找到2个这样的超类型:Number 和 Comparable 接口, 其本身也是一个泛型类型。 在这种情况下, 可以采取的补救措施是 将 所有的参数写为 double 值;

类型变量的限定

有的时候,比如对于特定的方法执行特定的操作,但是该操作不适用于一些类型,这时可以对类型变量T设置限定,可以使其集成特别的类或者接口(没错,在这里对于接口也是使用继承,因为使用extends更加接近子类的意思)

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

比如:T extends Comparable & Srializable

public static <T extends Comparable> Pari<T> getMinMax(T[] word){  
      
    if(word == null || word.length == 0)  
        return null;  
    T min = word[0];  
    T max = word[0];  
    for(int i=1;i<word.length;i++){  
        if(word[i].compareTo(max) > 0)  
            max = word[i];  
        if(word[i].compareTo(min) < 0)  
            min = word[i];  
    }  
    return new Pari<T>(min,max);  
}
复制代码

JVM中没有泛型,只有普通的类和方法

所有的类型参数都是用他们的限定类型转换(如果没有类型参数,则使用Object类型),这个过程称为擦除(erased),擦除类型变量,并替换为限定类型

有时为保持类型安全性,需要插入强制类型转换

约束与局限性

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

不能用类型参数来代替基本类型。就是没有Pair<double>,只有Pair<Double>。当然主要是原因是类型擦除。擦除之后,Pair类含有Obkect类型的域,而Object不能存储double的值。

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

虚拟机中的对象总有一个特定的非泛型类型。因此,所有类型查询只产生原始类型。

如:if(a instanceof Pari<String>)是错误的,因为只能查询原始类型,即Pari,if(a instanceof Pari<T>)是错误的

又如:

Pari<String> pari1 = (Pari<String>) a

无论何时使用instanceod或者设计泛型类型的强制类型转换表达式都会看到一个编译器警告。

同样道理,getClass方法总是返回原始类型。

if (pari1.getClass() == pari2.getClass())返回true,因为两次getClass()都是返回Pari.class

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

Couple<Employee>[] couple = new Couple<Employee>[5] ;这种声明式不合法的,这里面有一个问题还是通过类型擦除机制来解释,类型擦除后couple的类型是Couple[],考虑一下两种赋值方式:
1.couple[0] = "wife"时,编译器会报错,这个很明显的错误,couple每个元素都是Couple类型。
2.couple[0] = new Couple<String>(),类型擦除后这个可以通过数组检测,但仍然是错误的类型,因为couple在声明的时候定义的是Couple<Employee>,所以会出现问题。
如果要存放参数化类型对象的集合,可以考虑使用ArrayList<Couple<Employee>>进行声明,而且Java中建议优先使用集合,这样既安全又有效。

不能抛出也不能捕获泛型类实例

 在Java中,public class GenericException <T> extends Exception {...}这种泛型类扩展子Throwable是不合法的,不能通过编译器。
不能再catch子句中使用类型参数,如:
public static <T extends Throwable> void doWork(Class<T> t) {  
     try {  
           // do work...  
     } catch (T e) {  
           e.printStackTrace();  
     }  
  }  // 错误 复制代码

而这个是合法的

public static <T extends Throwable> void doWork(T t) throws T {  
          try {  
                    // do work...  
         } catch (Throwable e) {  
                   e.printStackTrace();  
                    throw t;  
         }  
}// 正确  复制代码

java 异常处理的一个基本原则是,必须为所有已检查异常提供一个处理器。不过可以利用泛型消除这个限制。

不能实例化类型变量 

不能使用像new<T>(...),new t[...]或T.class这样的表达式中的类型变量。例如,下面的Pair<T>构造器就是非法的:

public Pair()
{
first =new T();
 second=new T ();
}//ERROR

复制代码

类型擦除将T改变成Object。而且本意肯定不希望调用newObject(),但是可以通过反射调用Class.newInstance方法来构造泛型对象。

遗憾的是T.class在Java中也是不被支持使用的,所以一种弥补的方式,传入一个类型阐述为T的Class对象,如Class<T>

 public static <T> Couple<T> createInstance(Class<T> clazz) {  
          try {  
                    return new Couple<T>(clazz.newInstance(), clazz.newInstance());  
         } catch (Exception e) {  
                    return null ;  
         }  
}  
复制代码

     初学者对Java反射不熟悉不用着急,这里只要知道不能实例化类型参数即可,同理,不能实例化一个泛型数组,如 

public static <T> T[] maxTwo(T[] values) {
    T[] array = new T[2];
} // 错误   

复制代码

     泛型构建数组是不合法的,因为这么构建在擦除之后构造的永远是new Object[2],这不是我们所希望的结果。而且这样会导致一些运行时错误。为了更进一步说明隐患问题,来看看下面代码: 

public static <T extends Comparable<T>> T[] maxTwo(T[] array) {  
     Object[] result = new Object[2];  
     return (T[]) result; // Type safety: Unchecked cast from Object[] to T[]  
}  
复制代码

     这种方式会产生变异警告:Object[]转换为T[]是没有被检查的。我们来试一试这个调用: maxTwo(new String[] { "5", "7" , "9" });,运行后,发生了类型转换异常,因为方法在调用的时候将Object[]转换为String[],失败的类型转化。怎么解决呢?同样这里可以使用Java发射来解决: 

public static <T extends Comparable<T>> T[] maxTwo(T[] array) {  
     // Type safety: Unchecked cast from Object[] to T[]  
     return (T[]) Array.newInstance(array.getClass().getComponentType(), 2) ;  
}  
复制代码

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

不能在静态域或方法中引用类型变量。

public class Singleton<T>
{
    public static T singleInstacne;//ERROR
    public static T getSingleInstance();//ERROR
    {
        if(singleInstacne==null)
        return singleInstance;
    }
}复制代码

如果这个程序能够运行,就可以声明一个Singleton<Random>共享随机数生成器,声明一个Singleton<JFileChooser>共享文件选择器对话框。但是,这个程序无法工作。类型擦除之后,只剩下Singleton类,他只包含一个singleInstance域。

注意擦除后的冲突

public class NameClash<T> {  
      public boolean equals(T value) {  
               return false ;  
     }  
}  
复制代码

     从这个类的定义中来看,存在两个equals方法,一个是自身定义的public boolean equals(T value) {...},一个是从Object继承的public boolean equals(Object obj) {...},但类型擦除以后,前者方法成为了public boolean equals(Object value) {...},而在一个类中同时存在两个方法名和参数一样的方法是不可能的,所以这里引发的冲突是没法通过编译器的。可以通过重新命名方法进行修正。
擦除引起的冲突还体现在另一点上,再看一段错误的代码:

class Calendar implements Comparable<Calendar> {...}  
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar> {...}  复制代码
上述代码是非法的,为什么?回顾一下类型擦除后,虚拟机会为
Calendar
类合成桥方法,实现了Comparable<Calendar>获得一个桥方法:
public int compareTo (Object o) {return compareTo((Calendar)o);}
而实现了Comparable<GregorianCalendar >在类型擦除后,虚拟机为GregorianCalendar合成一个桥方法:
public int compareTo (Object o) {return compareTo((GregorianCalendar )o);}
这样一来在GregorianCalendar类中存在两个一样的方法,这是不允许的。



关注下面的标签,发现更多相似文章
评论