美文网首页
Java 泛型你真的理解了吗?

Java 泛型你真的理解了吗?

作者: 青果果 | 来源:发表于2018-01-08 01:05 被阅读0次

    泛型是JDK 1.5引入的新特性

    泛型是jdk1.5后 引入的新特性,泛型在源码中使用的非常广泛,平常开发中也经常使用
    那既然使用的这么广泛,你是否真正理解了,并能运用自如呢?
    先抛出一个问题,既然泛型是在jdk 1.5后才引入,那之前源码是怎么写的呢?是为什么要引入呢?
    搞清楚了这个,我想才能真正理解泛型设计的精髓!

    对比不同版本的JDK api

    JDK1.6的ArrayList
    JDK1.4的ArrayList

    不同之处在于 get(int index), remove(int index)
    1.4版本返回的是object类型
    1.6版本返回的是泛型的类型

    为什么是object

    我们都知道object是所有类的超类,那么在早期没有泛型概念的时候
    其实就是用object来接收所有对象,代表任意类型

    public class ObjectDemo{
        private Object obj;
        public Object getObj() {
            return obj;
        }
        public void setObj(Object obj) {
            this.obj = obj;
        }
    }
            ObjectDemo od=new ObjectDemo();
            od.setObj(new String("qingguguo"));
            String s = (String) ot.getObj();
            System.out.println("姓名是:" + s);
    
            od.setObj(new Integer(20));
            String ss = (String) ot.getObj();//强转会报类型转换异常
            System.out.println("姓名是:" + ss);
    

    按照现在泛型的写法如下:

    public class ObjectDemo2<T>{
        private T obj;
        public T getObj() {
            return obj;
        }
        public void setObj(T obj) {
            this.obj = obj;
        }
    }
    
    ObjectDemo2<String> od=new ObjectDemo2<String>();
            od.setObj(new String("qingguguo"));
            String s =  ot.getObj();//这里都不用强转了
            System.out.println("姓名是:" + s);
    
            od.setObj(new Integer(20));//编译期就会报错
    

    在向上转型为object的时候没有任何问题,但是向下转型的时候就可能会类型转换异常
    这样的程序不安全,不健壮,所以java在JDK1.5后引入了泛型,以提高程序的安全性

    泛型的应用

    泛型类 ,如:Arraylist
    泛型方法
    泛型接口,如: List Map
    泛型高级通配符

    泛型类

    泛型类:把泛型定义在类上

    /*
     * 如果创建对象不写<T>, 那么默认就是object :new ObjectDemo()和new ObjectDemo<String>();
     */
    public class ObjectDemo<T> {
        private T obj;
    
        public T getObj() {
            return obj;
        }
    
        public void setObj(T obj) {
            this.obj = obj;
        }
    }
    

    会根据传的类型返回传入的类型 对比下,这个用法有点意思,好好体会

    public class ObjectTool<T> {
           public void show(String s) {
               System.out.println(s);
         }
    
      public void show(Integer i) {
            System.out.println(i);
          }
    
      public void show(Boolean b) {
                System.out.println(b);
            }
    
      public void show(T t) { //但是这个方法并不是泛型方法
        System.out.println(t);
        }
    }
    

    泛型方法

    泛型方法:把泛型定义在方法上
    1把泛型定义在方法上,方法上的泛型可以和类上的定义的泛型不一致,
    2
    类上不定义泛型,方法上也可以定义泛型,类上的泛型和方法上的泛型互不干涉

    在调用方法的时候指明泛型的具体类型 ,下面这个示例要好好琢磨一下

    泛型方法的基本说明:
     * @param tClass 传入的泛型实参  http://blog.csdn.net/s10461/article/details/53941091
     * @return T 返回值为T类型
     * 
     *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
     *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
     *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
     *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
     */
    public <T> T show(Class<T>  tClass) {
            System.out.println(t);
        }
    
    /*
     *把泛型定义在方法上,方法上的泛型可以和类上的定义的泛型不一致,
     *类上不定义泛型,方法上也可以定义泛型,类上的泛型和方法上的泛型互不干涉
     */
     public class ObjectTool {  //泛型方法
        public <T> void show(T t) {
            System.out.println(t);
        }
    }
    /**
    *泛型方法  泛型类,泛型可不一致
    */
     public class ObjectTool2<E> {  
            private E key;
    
            public ObjectTool2(E key) {  //这个方法并不是泛型方法
                this.key = key;
            }
    
            //虽然在方法中使用了泛型,但是这并不是一个泛型方法。
            //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
            //所以在这个方法中才可以继续使用 E 这个泛型。
            public E getKey(){
                return key;
            }
    
       //这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol M"
       //因为在类的声明中并未声明泛型M,所以在使用M做形参和返回值类型时,编译器会无法识别。
           public M show2(M m) { 
               return m;
            }
    
         //这才是一个真正的泛型方法。
         //首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
         //这个T可以出现在这个泛型方法的任意位置.
         // 泛型的数量也可以为任意多个
        public <T> void show(T t) {
            System.out.println(t);
        }
     /**
         * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
         * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
         * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
        public <T> T showKeyName(Generic<E> container){
            ...
        }  
        */
    
        /**
         * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
         * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
         * 所以这也不是一个正确的泛型方法声明。
        public void showkey(T genericObj){
    
        }
        */
    }
    

    泛型方法与可变参数

    泛型方法与可变参数

    再看一个泛型方法和可变参数的例子:吊炸天

     public static <T> void printMsg(T... args) {
            for (T t : args) {
                System.out.print(t + " ,");
            }
        }
    
    printMsg("111", 222, "aaaa", "2323.4", 55.55, true, false, 'B');
    

    输出结果:111111 ,222 ,aaaa ,2323.4 ,55.55 ,true ,false ,B ,

    静态方法与泛型

    如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

    public class StaticGenerator<T> {
        ....
        ....
        /**
         * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
         * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
         * 如:public static void show(T t){..},此时编译器会提示错误信息:
              "StaticGenerator cannot be refrenced from static context"
         */
        public static <T> void show(T t){
    
        }
    }
    

    泛型方法总结:

    泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:
    无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

    泛型接口

    /*
     * 泛型接口:把泛型定义在接口上
     */
    public interface Inter<T> {
        public abstract void show(T t);
    }
    

    那么实现类就会有两种情况要考虑:
    实现类在实现接口的时候

    //第一种情况:明确了类型
    public class InterImpl implements Inter<String> {
        @Override
        public void show(String t) {
            System.out.println(t);
        }
     }
    
    //第二种情况:还是不明确类型,比较多是这种情况
    //必须在实现类上也加上泛型
    public class InterImpl<T> implements Inter<T> {
    
        @Override
        public void show(T t) {
            System.out.println(t);
        }
    }
    

    泛型高级通配符

    • 泛型高级(通配符)
    • ?:任意类型,如果没有明确,那么就是Object以及任意的Java类了
    • ? extends E:向下限定,E及其子类
    • ? super E:向上限定,E极其父类

    泛型数组

    在java中是”不能创建一个确切的泛型类型的数组”的

        List<String>[] ls1 = new ArrayList<String>[10];//这样是不允许的
        
        List<?>[] ls2 = new ArrayList<?>[10];
        List<String>[] ls3 = new ArrayList[10];
        ArrayList[] ls4 = new ArrayList[10];
    
       //在泛型方法中添加上下边界限制的时候,
        //必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
        public <T> T showKeyName6(List<T extends String> container) {//编译器会报错:"Unexpected bound"
    
            return container;
        }
    
        public <T extends Number> T showKeyName(List<T> list) {
            System.out.println("container key :" + list.size());
            T test = list.get(0);
            return test;
        }
    

    泛型擦除

    有这样的一个问题:ArrayList<Integer>的一个对象,能在这个集合中添加一个字符串数据吗?如果能,怎么做?
    // 集合对象
    ArrayList<Integer> array = new ArrayList<Integer>();
    array.add(10);
    array.add("hello");//这样写肯定会报错
    这些都是给编译器看的!
    泛型只是在编译阶段才有,运行时是没有的,通过反编译class文件就可以看到
    运行时泛型已经擦除了,所以
    可以通过反射的方式拿到add方法,默认就是object方法,再调用array.add("hello")

    总结

    基础决定了上层建筑,源码以及很多设计模式都用到了泛型,用的如此之广泛,
    高级点的多个泛型,接口实现.如果没搞清楚,可能看的一脸懵逼。

    1.泛型把明确类型的工作推迟到创建对象或者调用方法的时候才去明确;
    2.<数据类型>数据类型只能是引用类型;
    3.如果有泛型,不传的话默认就是object,但是建议有泛型最好就传入数据类型;
    3.泛型把运行时期的类型转换问题提前到编译期,避免了强制类型转换;
    4.优化程序设计,程序更健壮。

    相关文章

      网友评论

          本文标题:Java 泛型你真的理解了吗?

          本文链接:https://www.haomeiwen.com/subject/rdkvnxtx.html