JAVA SE 基础复习-泛型的使用

前言

  泛型 Generic type是JDK1.5引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

        List list=new ArrayList();
        list.add(5);
        list.add("list");

  假如不指定实参类型参数,可以向List中添加任意类型的对象,但是这个在遍历的时候就会遇到麻烦,可能会跑出ClassCastException。

  要想能够明确遍历的对象,就必须指定类型参数,如下:

        List<Integer> list=new ArrayList<Integer>();
        list.add(5);
        list.add("list");//这里编译通不过

  编译器会告诉我们必须添加Integer类型的对象,String类型不能添加。

  使用泛型后,代码可读性更强,且减少了出错机会。

类型擦除

  正确理解泛型概念的首要前提是理解类型擦除(type erasure) Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。

很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:

  • 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
  • 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
  • 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:

class MyString implements Comparable<String> 
{      
        public int compareTo(String str) 
        {                  
                return 0;          
        }  
}     

当类型信息被擦除之后,上述类的声明变成了class MyString implements Comparable。但是这样的话,类MyString就会有编译错误,因为没有实现接口Comparable声明的int compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。

非协变性与通配符

  个人理解,协变性就是  子类能够以和父类相同的方式被支持。 

  数组的协变性(covariant)是指如果类Base是类Sub的父类,那么Base[]就是Sub[]的父类.

        List[] lists=new List[10];
        lists[0]=new LinkedList();
        lists[1]=new ArrayList();
     List[] lists=new ArrayList[10];

  上面的这个数组的两个元素类型不同,但是都实现了List接口。下面这个编译就通不过。

        ArrayList[] lists=new ArrayList[10];
        lists[0]=new LinkedList();//编译通不过
lists[
1]=new ArrayList();

  泛型是不支持协变的。例如:

    public static void main(String[] args) {
        List<Number> list1=new ArrayList<Number>();
        list1.add(1);
        System.out.println(new Main().getString(list1));
        List<Integer> list2=new ArrayList<Integer>();
        list2.add(2);
        System.out.println(new Main().getString(list2));//编译通不过
    } 
    
    public  int getString(List<Number> list)
    {
        if(list.size()>0)
            return (Integer)(list.get(0));
        return 0;
    }

  要解决这个问题,可以采用通配符  ?

    public  int getString(List<?> list)
    {
        if(list.size()>0)
            return (Integer)(list.get(0));
        return 0;
    }

  这时就能够正常通过编译了。注意这里的?与下面的T的区别,?指的是一个具体的具体类型参数,而T是一个形式类型参数,有点类似形参和实参。

  但是有不知道函数是怎么实现的,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。

  因为对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。List<? extendsNumber>说明List

中可能包含的元素类型是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定

义的方法。比如访问 List<? extends Number>的时候,就可以使用Number类的intValue等方法。

  对于使用通配符的方法可以做什么呢?可以从中检索元素,但是不能添加元素(可以添加null);

  因为编译器无法确定具体类型。

    public  int getString(List<?> list)
    {
        if(list.size()>0)
            return (Integer)(list.get(0));
        list.add(5);//编译通不过
        return 0;
    }

自定义泛型类

  下面是一个简单的例子。

class Point<T>{       // 此处可以随便写标识符号,T是type的简称  
    private T var ; // var的类型由T指定,即:由外部指定  
    public T getVar(){  // 返回值的类型由外部决定  
        return var ;  
    }  
    public void setVar(T var){  // 设置的类型也由外部决定  
        this.var = var ;  
    }  
};  
public class GenericsDemo06{  
    public static void main(String args[]){  
        Point<String> p = new Point<String>() ; // 里面的var类型为String类型  
        p.setVar("it") ;        // 设置字符串  
        System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
    }  
};  

   有上限的泛型类

class Info<T extends Number>{ // 指定上限,只能是数字类型  
    private T var ;     // 此类型由外部决定  
    public T getVar(){  
        return this.var ;     
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public String toString(){       // 覆写Object类中的toString()方法  
        return this.var.toString() ;      
    }  
};  
public class GenericsDemo27{  
    public static void main(String args[]){  
        Info<Integer> i = fun(30) ;  
        System.out.println(i.getVar()) ;  
    }  
    public static <T extends Number> Info<T> fun(T param){//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定  
        Info<T> temp = new Info<T>() ;      // 根据传入的数据类型实例化Info  
        temp.setVar(param) ;        // 将传递的内容设置到Info对象的var属性之中  
        return temp ;   // 返回实例化对象  
    }  
}; 

 

自定义泛型方法

class Demo{  
    public <T> T fun(T t){            // 可以接收任意类型的数据  
        return t ;                  // 直接把参数返回  
    }  
};  
public class GenericsDemo26{  
    public static void main(String args[]){  
        Demo d = new Demo() ;   // 实例化Demo对象  
        String str = d.fun("汤姆") ; //   传递字符串  
        int i = d.fun(30) ;     // 传递数字,自动装箱  
        System.out.println(str) ;   // 输出内容  
        System.out.println(i) ;     // 输出内容  
    }  
}; 

为什么您选择使用泛型方法,而不是将类型T添加到类定义呢?(至少)有两种情况应该这样做: 
* 不要求该类是泛型类。
* 当泛型方法是静态的时,这种情况下不能使用类类型参数。 
* 当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名。 

 

泛型接口

interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    private T var ;             // 定义属性  
    public InfoImpl(T var){     // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
};  
public class GenericsDemo24{  
    public static void main(String arsg[]){  
        Info<String> i = null;        // 声明接口对象  
        i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象  
        System.out.println("内容:" + i.getVar()) ;  
    }  
};  
----------------------------------------------------------  
interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
}  
class InfoImpl implements Info<String>{   // 定义泛型接口的子类  
    private String var ;                // 定义属性  
    public InfoImpl(String var){        // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(String var){  
        this.var = var ;  
    }  
    public String getVar(){  
        return this.var ;  
    }  
};  
public class GenericsDemo25{  
    public static void main(String arsg[]){  
        Info i = null;      // 声明接口对象  
        i = new InfoImpl("汤姆") ;    // 通过子类实例化对象  
        System.out.println("内容:" + i.getVar()) ;  
    }  
};  

 

泛型数组

public class GenericsDemo30{  
    public static void main(String args[]){  
        Integer i[] = fun1(1,2,3,4,5,6) ;   // 返回泛型数组  
        fun2(i) ;  
    }  
    public static <T> T[] fun1(T...arg){  // 接收可变参数  
        return arg ;            // 返回泛型数组  
    }  
    public static <T> void fun2(T param[]){   // 输出  
        System.out.print("接收泛型数组:") ;  
        for(T t:param){  
            System.out.print(t + "、") ;  
        }  
    }  
}; 
posted @ 2015-09-11 18:34  Maydow  阅读(189)  评论(0编辑  收藏  举报