• java学习笔记10--泛型总结


    java学习笔记系列:

    java学习笔记9--内部类总结

    java学习笔记8--接口总结

    java学习笔记7--抽象类与抽象方法

    java学习笔记6--类的继承、Object类

    java学习笔记5--类的方法 

    java学习笔记4--对象的初始化与回收

    java学习笔记3--类与对象的基础 

    java学习笔记2--数据类型、数组

    java学习笔记1--开发环境平台总结

    本文地址:http://www.cnblogs.com/archimedes/p/java-study-note10.html,转载请注明源地址。

    集合类中的数据类型

    集合类中可以存储各种数据,数据一旦存入,其类型均会转化为Object类型。从集合类中取出数据时,一般均需要将Object类型转换回存入之前的实际类型

    Vector v=new Vector();
    v.add("张三");   //存入字符串
    String name=(String)v.get(0);  //强制类型转换,OK
    v.add(new Date());  //存入当前时间对象,OK
    
     // 由于Date类型不能转换为String,下面语句会在运行时发生错误,但这种错误在编译时不会被检查出来
    String date=(String)v.get(1); //编译器不会发现这里有问题

    强类型集合

    传统的集合类的实例中可以存储任意类型数据,这种集合类称为弱类型集合类。JDK1.5以后,引入了强类型集合类:

    • 强类型集合类中,只能存储指定类型的数据

    • 在强类型集合类中取出数据时,无需进行类型转换处理,如果数据类型不配备,编译时会直接报错

    • 强类型集合并没有引入新的类名,只需在定义原有集合对象时,用尖括号(<>)指明其存储的数据类型名称即可。

    举个例子:

    //下面的向量类的实例中只能存储字符串类型数据
    Vector<String> v=new Vector<String>(); 
    v.add("hello"); //加入的是字符串,OK
    String name=v.get(0); //取出时,无需做类型转换,如果想在这种强类型集合中加入日期数据,在编译时就会报告错误
    v.add(new Date()); //编译器会直接报告类型不匹配错误

    泛型类

    定义泛型(Generics)类

    强类型集合采用了JDK1.5引入的泛型语法。泛型相当于类中一种特殊的类型,这种类型的特点是在实例化该类时可指定为某个具体的实际类型。

    声明包含泛型的类的格式如下:

     [访问修饰符]  class 类名<泛型1,泛型2,…> {
           泛型1  泛型成员1;
           泛型2  泛型成员2;
           //....
     }

    声明中的泛型1、泛型2等等泛型符号可以是任意合法的Java标识符。

    泛型类的声明示例

    //此处声明了一个包含泛型T的泛型类,T代表所有可能的类型,而T的实际类型在Generic类实例化时指定。
    class Generic<T> {
         private T f;  //f为泛型成员
         public void setF(T f) {//setF方法的参数类型为泛型T
              this.f = f;
         }
         public T getF() {//getF方法的返回类型为泛型T
              return f;
         }
    }

    泛型类的实例化

    创建泛型类的实例时,可以使用一对尖括号指定泛型的真正类型

    public class test {  
        public static void main(String args[ ]) { 
            //f1中的泛型T在此指定为Boolean类型
            Generic<Boolean> f1 = new Generic<Boolean>();
            
            //f2中的泛型T在此指定为Integer类型
            Generic<Integer> f2 = new Generic<Integer>();
            
            //f1的setF方法只能接受Boolean类型的数据
            f1.setF(new Boolean(true));
            Boolean b = f1.getF();
            System.out.println(b);
            
            //f2的setF方法只能接受Integer类型的数据
            f2.setF(new Integer(10));
            Integer i = f2.getF();
            System.out.println(i);
       }
    }

    实例化时的泛型的默认类型

    泛型类实例化时,并不一定要指明泛型对应的实际类型,此时会使用Object作为泛型的默认类型

    Generic f3 = new Generic();
    f3.setF(new Boolean(false));

    编译时编译器会发出警告:

    Note: Generic.java uses unchecked or unsafe operations.

    Note: Recompile with -Xlint:unchecked for details.

    建立类型为泛型类的数组

    如果要建立泛型类的数组,需要注意new关键字后面不要加入泛型的实际类型名,如下所示:

    Generic<String>[] gs;  //声明泛型类的数组
    
    //先对泛型数组进行初始化
    gs = new Generic[5]; //不要写成new Generic<String>[5]
    
    //再分别为每一个数组元素进行初始化
    gs[0] = new Generic<String>();//为第一个数组元素赋值
    //....

    包含多个泛型的类定义示例

    包含有两个泛型定义的类声明和实例化:

    class Generic2<T1, T2> {
        private T1 f1;
        private T2 f2;
        //...
    }
    
    //给出泛型T1, T2的实际类型
    Generic<Integer, Boolean> f = new Generic<Integer, Boolean>();
    
    //没有给出泛型T1, T2的实际类型
    Generic f1 = new Generic(); //T1, T2将默认为是Object类型

    泛型成员的使用

    在泛型类中的泛型成员不能直接实例化,其实例必须要通过方法的参数传递给泛型成员:

    class Generic<T> {
        private T[] array;    //此处不能用new T[]实例化array
        public void setArray(T[] array) {
            this.array = array;
        }
        public T[] getArray() {
            return array;
        }
    }

    测试程序:

    public class test {  
        public static void main(String args[ ]) { 
            String[] strs = {"red", "black", "green"};
            Generic<String> f = new Generic<String>();
            //向泛型成员array传递实际的字符串数组
            f.setArray(strs);
            //读取泛型成员array的值,将其赋给字符串数组变量strs
            strs = f.getArray();
       }
    }

    泛型成员的可用方法

    由于泛型类型只有在类实例化后才能确定,类中的泛型成员只能使用Object类型中的方法:

    class Generic<T>{
        T f;
        void setF(T f){ this.f = f; }
        //....
        void doSome(){
            //getClass和toString都是Object中的方法
            System.out.println(f.getClass().getName());
            System.out.println(f.toString());
        }
    }

    测试程序:

    public class javatest {  
        public static void main(String args[ ]) { 
            String strs = "hello";
            Generic<String> f = new Generic<String>();
            f.setF(strs);
            f.doSome();
       }
    }

    限制泛型上限类型

    extends关键字用来指定泛型的上限,在实例化泛型类时,为该泛型指定的实际类型必须是指定类的子类或指定接口的子接口

    import java.util.List;
    public class ListGeneric<T extends List> {
        private T list;
        public void setList(T list) {
            this.list = list;
        }
        public T getList() {
            return list;
        }
    }

    在限定泛型的类型时,无论要限定的是接口或是类,都要使用extends关键词

    测试例子:

    ListGeneric<Vector> f1 = new ListGeneric<Vector>();
    ListGeneric<ArrayList> f2 = new ListGeneric<ArrayList>();

    如果不是List的类型,编译时就会发生错误:

    ListGeneric<HashMap> f3 =  new ListGeneric<HashMap>();
    type parameter java.util.HashMap is not within its bound
    ListGeneric<HashMap> f3 = new ListGeneric<HashMap>();

    默认的泛型限制类型

    定义泛型类别时,如果只写以下代码:

    class Generic<T> {
        //...
    }

    相当于下面的定义方式:

    class Generic<T> extends Object {
        //...
    }

    限定泛型上限后的成员可用方法:

    泛型类型的上限一经限定,类中的泛型成员就可使用上限类型中的方法和其他可用成员:
    class ListGeneric<T extends List>{
        private T list;
        public void setList(T list) {
            this.list = list;
        }
        public void doSome() {
            //ad、get方法都是List接口中定义的方法
            list.add(new Integer(0));
            System.out.println(list.get(0));
        }
    }

    Object是所有类的父类,因此,所有的类型的实例都可赋值给声明为Object类型的变量

    Boolean f1 = new Boolean(true);
    Integer f2 = new Integer(1);
    Object f = f1;    //ok
    f = f2;        //ok

    在实例化泛型类时,将泛型指定为Object类型却不存在着和其他类型之间的兼容性:

    Generic<Boolean> f1 = new Generic<Boolean>();
    Generic<Integer> f2 = new Generic<Integer>();
    Generic<Object> f=f1; //f1和f类型并不兼容,发生编译错误
    f=f2;  //f2和f类型同样不兼容,也会发生编译错误 

    泛型通配字符(Wildcard)

    泛型类实例之间的不兼容性会带来使用的不便。使用泛型通配符(?)声明泛型类的变量可以解决这个问题

    Generic<Boolean> f1 = new Generic<Boolean>();
    Generic<Integer> f2 = new Generic<Integer>();
    Generic<Object> f3 = new Generic<Object>();
    Generic<?> f;
    f = f1;    //ok
    f = f2;    //ok
    f = f3;    //ok

    通配符也可以用于方法的参数类型的声明,表示该参数可接受对应泛型类型的任意实例。

    以下类定义中的printCollection方法可以打印任意强类型集合中的内容

    class test {
        //Collection<?>可以匹配任意强类型的集合
        static void printCollection(Collection<?> c) {
            for(Object o : c)
                System.out.println(o);
        }
    }

    和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限:

    Generic<? extends List> f = null;
    f = new Generic<ArrayList>();    //ok
    //...
    f = new Generic<Vector>();    //ok
    //...
    //以下语句会发生编译错误,因为HashMap没有实现List接口
    f = new Generic<HashMap>();
    incompatible types
    found : Generic<java.util.HashMap>
    required: Generic<? extends java.util.List>
    f = new Generic<HashMap>();

    限定通配符匹配类型的下限

    还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型:

    //将f限定为只能代表采用java.sql.Date的父类实例化的
    Generic<? super java.sql.Date> f = null;
    f = new Generic<java.sql.Date>();    //ok
            
    //OK,java.util.Date是java.sql.Date的父类
    f = new Generic<java.util.Date>();
        
    //错误,因为String不是java.sql.Date的父类
    f = new Generic<String>();

    泛型方法

    不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:

    访问修饰符 <泛型列表> 返回类型 方法名(参数列表){

        实现代码

    }

    其中泛型列表为用逗号分隔的合法Java标识符。

    在泛型列表中声明的泛型,可用于该方法的返回类型声明、参数类型声明和方法代码中的局部变量的类型声明。类中其他方法不能使用当前方法声明的泛型。使用泛型方法可以解决上述的泛型通配符造成的问题

    泛型方法声明示例:

    class cc{
        /*
         方法fun声明了一个泛型T,该方法将任意类型的数组a中的所有
         元素复制到相应的强类型集合c当中而不会导致编译错误。
         此处的泛型声明T仅作用于fun方法的声明部分和实现代码部分。
         */
         public static <T> void fun(T[] a, Collection<T> c){
             for(T o : a)
                 c.add(o);    //不会出现类似通配符的编译错误
         }
    }

    调用泛型方法和调用普通方法没有任何不同,只需要传递含有具体类型的实参即可:

    泛型方法的调用示例:

    //对cc中定义的泛型方法fun进行调用测试
    public class javatest {  
        public static void main(String args[ ]) { 
            String[] sa = new String[100];
            Collection<String> cs = new Vector<String>();
            Collection<Object> co = new Vector<Object>();
            
            cc.fun(sa, cs);    //fun中的泛型T此时匹配类型String
            cc.fun(sa, co);    //fun中的泛型T此时匹配类型Object
       }
    }

    限定泛型方法中泛型类型

    泛型方法中的声明的泛型,同样可以使用extends关键字限定其类型的下限:

    class cc{
      //限定aToC方法中的泛型T必须是实现了序列化接口的类型
       public static <T extends java.io.Serializable> void fun(T[] a,Collection<T> c){
             for(T o : a)
                 c.add(o);
        }
    }

    原始类型和向后兼容

    先看一个泛型类定义

    public class GenericStack<E> {
        ArrayList<E> list = new ArrayList<E>();
        public int getSize() {
            return list.size();
        }
        public E peek() {
            return list.get(getSize() - 1);
        }
        public void push(E o) {
            list.add(o);
        }
        public E pop() {
            E o = list.get(getSize() - 1);
            list.remove(getSize() - 1);
            return o;
        }
        public boolean isEmpty() {
            return list.isEmpty();
        }
    }

    可以使用泛型类而无需指定具体类型:

    GenericStack stack = new GenericStack();
    
    //大体等价于于下面的语句
    GenericStack<Object> stack = new GenericStack<Object>();

    再看下面的一个例子:

    class Max {
        public static Comparable max(Comparable o1, Comparable o2) {
            if(o1.compareTo(o2) > 0)
                return o1;
            return o2;
        }
    }
    
    public class javatest {  
        public static void main(String args[]) { 
            Max.max("welcome", 12);
       }
    }

    得到一个运行错误:

    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

    结论:原始类型是不安全的,尽量使用泛型类型

    一个更好的编写max方法的方式是使用泛型:

    class Max {
        public static <E extends Comparable<E>> E max(E o1, E o2) {
            if(o1.compareTo(o2) > 0)
                return o1;
            return o2;
        }
    }

    再次调用上面的命令就会显示一个编译错误,由于max方法的两个参数必须是相同的类型

    继承中的泛型

    继承时如需保留父类泛型,需要在声明时加入父类泛型

    class subGeneric<T1, T2, T3> extends Generic<T1, T2> {
        private T3 f3;
        public void setF3(T3 f3) {
            this.f3 = f3;
        }
        public T3 getF3() {
            return f3;
        }
    }

    如果不保留父类中的泛型声明,则继承下来的T1与T2自动变为Object类型。建议父类中的泛型声明在子类中都要保留

    继承时指定父类的泛型类型

    public class SubGeneric<T3> extends Generic<String, Object> {
        private T3 f3;
        public void setF3(T3 f3) {
            this.f3 = f3;
        }
        public T3 getF3() {
            return f3;
        }
    }

    泛型接口

    接口也可包含泛型的声明:

    interface I<T1, T2> {
        T1 getT1();
        T2 getT2();
        //...
    }

    实现泛型接口时,类在定义时可以不声明泛型接口中的泛型,此时接口中的泛型也会自动变为Object类型:

    class IC implements I {
        public Object getT1() { }
        public Object getT2() { }
        //...
    }

    泛型接口的实现

    class IC<T1, T2> implements I<T1, T2> {
        public T1 getT1() { }
        public T2 getT2() { }
        //...
    }
    
    I<String, Integer> i = new IC<String, Integer>();

    实现泛型接口时指定泛型类型

    在实现泛型接口时,也可直接指定接口中的泛型的实际类型:

    interface I<T1, T2> {
        T1 getT1();
        T2 getT2();
        //...
    }
    //实现接口I时,直接指定泛型T1、T2的类型
    class IC implements I<String, Integer> {
        //由于指定接口I中T1类型为String,getT1返回类型必须为String
        public String getT1() { }
        //由于指定接口I中T2类型为Integer,getT2返回类型必须为Integer
        public Integer getT2() { }
        //...
    }

    泛型和枚举

    由于枚举类型不能直接实例化,所以枚举的定义中不能含有泛型的声明,但枚举中可包含泛型方法的定义。

    public enum TrafficLight{
      Red,Amber,Green;
      private int duration;
      public static <T> void avgDuration(Collection<T> carType){
         //....
      }
      //....
    }
  • 相关阅读:
    BZOJ2457 双端队列 题解
    POJ1723,1050,HDU4864题解(贪心)
    Splay与FHQ-Treap
    POJ3179 Corral the Cows题解
    使用easypoi根据表头信息动态导出excel
    Spring @Configuration注解
    vue脚手架vue-cli的搭建
    使用poi导出excel
    mybatis中的一对多和多对一
    angularjs模态框的使用
  • 原文地址:https://www.cnblogs.com/wuyudong/p/java-study-note10.html
Copyright © 2020-2023  润新知