zoukankan      html  css  js  c++  java
  • 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 + "、") ;  
            }  
        }  
    }; 
  • 相关阅读:
    Linux 学习之DNS服务器
    Windows系统镜像自动添加驱动程序
    Linux下集群的搭建
    Heartbeat+LVS构建高可用负载均衡集群
    Keepalived高可用集群搭建(转载linuxIDC)
    CentOS 7.x设置自定义开机启动,添加自定义系统服务
    代码改变世界
    Vim食用指南
    Hibernate三种状态详解
    MySQL服务器的安装与配置
  • 原文地址:https://www.cnblogs.com/maydow/p/4801718.html
Copyright © 2011-2022 走看看