java 泛型程序设计

泛型介绍

泛型程序设计 :能够被不少不一样类型的对象所重用。比那些直接使用Object变量,而后强制类型转换的代码具备跟好的安全性和可读性。java

使用类型参数能够将须要使用的类型,提早声明数组

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

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

当实现一个泛型的时候很是不容易,由于你须要知道这个这个类对应的全部用途及其类型,因此java提供了通配符类型,来解决这个问题。
bash

定义简单泛型类

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

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

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

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

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,说明该方法是泛型方法code

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类中存在两个同样的方法,这是不容许的。
相关文章
相关标签/搜索