zoukankan      html  css  js  c++  java
  • Java泛型学习一

    Java泛型

      所谓泛型,就是变量类型的参数化。泛型是java1.5中引入的一个重要特征,通过引入泛型,可以使编译时类型安全,运行时更少抛出ClassCastException的可能。一提到参数化,最熟悉的就是定义方法是由形参,然后调用此方法时传递实参。那么参数化该怎么理解,顾名思义,就是将类型由原来的具体类型参数化,类似于方法中的变量参数,此是类型也可定义成参数形式,然后在使用,调用时传入具体类型。使用泛型是如果不提供参数类型,即泛型类没有参数化,系统会警告,此时类型为Object。

    为什么使用泛型

      首先看一段代码:

    1 List list=new ArrayList();
    2 list.add("asd");
    3 list.add("qwe");
    4 list.add(123);
    5 for(int i=0;i<list.size();i++){
    6     String name=(String) list.get(i);//1
    7     System.out.println("name: "+name);
    8 }

      在循环当中,由于忘记了之前的list中加入了Integer类型的值或者是其他原因,很容易出现类似于//1中的错误,因为在编译阶段正常,但是在运行时会出现ClassCastException异常。当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,该对象的编译类型变成了Object类型,但其运行时类型仍然为其本身类型,因此//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现转化异常。

      泛型可以限制加入集合当中的元素类型,是编译时不出现问题,运行时就不会抛出ClassCastException异常。使用泛型的典型例子,是在集合中的泛型使用。

      在使用泛型前,存入集合中的元素可以是任何类型的,当从集合中取出时,所有的元素都是Object类型,需要进行向下的强制类型转换,转换到特定的类型

      比如:

    List myIntList = new LinkedList(); // 1
    
    myIntList.add(new Integer(0)); // 2
    
    Integer x = (Integer) myIntList.iterator().next(); // 3   

      第三行的这个强制类型转换可能会引起运行时的错误。

      泛型的思想就是由程序员指定类型,这样集合就只能容纳该类型的元素。

      使用泛型:

    List<Integer> myIntList = new LinkedList<Integer>(); // 1'
    
    myIntList.add(new Integer(0)); // 2'
    
    Integer x = myIntList.iterator().next(); // 3'

      将第三行的强制类型转换变为了第一行的List类型说明,编译器会为我们检查类型的正确性。这样,代码的可读性和健壮性也会增强。

    自定义泛型类,泛型接口,泛型方法

      先简单定义一个类: 

     1 public class GenericTest {
     2 
     3     public static void main(String[] args) {
     4 
     5         Box<String> name = new Box<String>("corn");
     6         System.out.println("name:" + name.getData());
     7     }
     8 
     9 }
    10 
    11 class Box<T> {
    12 
    13     private T data;
    14 
    15     public Box() {
    16 
    17     }
    18 
    19     public Box(T data) {
    20         this.data = data;
    21     }
    22 
    23     public T getData() {
    24         return data;
    25     }
    26 
    27 } 
    复制代码

    在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

    复制代码
     1 public class GenericTest {
     2 
     3     public static void main(String[] args) {
     4 
     5         Box<String> name = new Box<String>("corn");
     6         Box<Integer> age = new Box<Integer>(712);
     7 
     8         System.out.println("name class:" + name.getClass());      // com.qqyumidi.Box
     9         System.out.println("age class:" + age.getClass());        // com.qqyumidi.Box
    10         System.out.println(name.getClass() == age.getClass());    // true
    11 
    12     }
    13 
    14 }
    复制代码

    由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

    究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

    对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

    泛型和子类:

    List<String> ls = new ArrayList<String>(); // 1
    
    List<Object> lo = ls; // 2

     一个String类型的List是一个Object类的List吗?

     不可以,Java编译器将会在第二行产生一个编译错误,因为它们的类型不匹配。

     这样就避免了如果lo引入加入Object类型的对象,而ls引用试图将其转换为String类型而引发错误。所以编译器阻止了这种可能。

    继承泛型类别:

    父类:

    复制代码
    public class Parent<T1,T2>
    {
        private T1 foo1;
        private T2 foo2;
        
        public T1 getFoo1()
        {
            return foo1;
        }
        public void setFoo1(T1 foo1)
        {
            this.foo1 = foo1;
        }
        public T2 getFoo2()
        {
            return foo2;
        }
        public void setFoo2(T2 foo2)
        {
            this.foo2 = foo2;
        }    
    
    }
    复制代码

     子类继承父类:

    复制代码
    public class Child<T1, T2, T3> extends Parent<T1, T2>
    {
        private T3 foo3;
    
        public T3 getFoo3()
        {
            return foo3;
        }
    
        public void setFoo3(T3 foo3)
        {
            this.foo3 = foo3;
        }
        
    }
     

    实现泛型接口:

    泛型接口:

    复制代码
    public interface ParentInterface<T1,T2>
    {
        public void setFoo1(T1 foo1);
        public void setFoo2(T2 foo2);
        public T1 getFoo1();
        public T2 getFoo2();
    
    }
    复制代码

      子类实现泛型接口:

    复制代码
    public class ChildClass<T1,T2> implements ParentInterface<T1, T2>
    {
        private T1 foo1;
        private T2 foo2;
        
        @Override
        public void setFoo1(T1 foo1)
        {
            this.foo1 = foo1;
            
        }
        @Override
        public void setFoo2(T2 foo2)
        {
            this.foo2 = foo2;
        }
        @Override
        public T1 getFoo1()
        {
            return this.foo1;
        }
        @Override
        public T2 getFoo2()
        {
            return this.foo2;
        }
    
    }
    复制代码

     

    调用泛型方法语法格式如下:

           定义泛型方法时,必须在返回值前边加一个<T>,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

           Class<T>的作用就是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象。

           为什么要用变量c来创建对象呢?既然是泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用变量c的newInstance方法去创建对象,也就是利用反射创建对象。

           泛型方法要求的参数是Class<T>类型,而Class.forName()方法的返回值也是Class<T>,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class<T>就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class<User>类型的对象,因此调用泛型方法时,变量c的类型就是Class<User>,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。

           当然,泛型方法不是仅仅可以有一个参数Class<T>,可以根据需要添加其他参数。

           为什么要使用泛型方法呢?因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。

    泛型的继承规则:

    设计两个类,它们之间有继承关系:
    public class B {
        public void fun(){
            System.out.println("A.fun()");
        }
    }

    public class BSub extends B{
        @Override
        public void fun(){
            System.out.println("BSub.fun()");
        }


        public static void main(String[] args) {
            Pair<BSub> p1 = new Pair<BSub>();
           
            Pair p2 = p1;
            p2.setFirst(new Date());
            BSub b = p1.getFirst();
            b.fun();
        }
    }

    但其实Pair<B>和Pair<BSub>之间没有什么联系,绝对没有继承的关系。这种限制的原因是:
    Pair<BSub> bsp = new Pair<BSub>();
    Pair<B> bp = bsp;//不合法,错误:类型不兼容,但现在我们假设可以这样做
    bp.setFirst(new B());
    最后一句合法,但是bp与bsp是引用的同一个对象,那么可以将B对象装入Pair<BSub>中看起来也是不和情理的,退一步假设即使可以这样做,也会引发类型转换的错误,因为B是超类不能转换成子类。所以不如禁止这样做。
    但是下面的代码是合法的:

            Pair<BSub> p1 = new Pair<BSub>();
    //        Pair<B> p = p1;//不合法,类型不兼容

            Pair p2 = p1;
            p2.setFirst(new Date());
            BSub b = p1.getFirst();
            b.fun();
           
            Pair<B> p3 = new Pair<B>();
    //        Pair<BSub> p4 = p3;//不合法,同样类型不兼容

    但是会产生ClassCastException,把“谎话”说圆还是有难度的,这是Java对泛型实现的一个代价吧。
    最后,泛型类可以拓展或实现其它的泛型类。就这一点而言,与普通的类没有什么区别。例如,ArrayList<T>类实现List<T>接口,这意味着一个ArrayList<BSub>可以被转换为一个List<BSub>。但是,正像前面所讨论的,一个ArrayList<BSub>不是一个ArrayList<B>或者List<B>。
    下图显示了它们之间的关系:
    Java泛型4---泛型类型的继承规则

    参考资料:

    http://www.cnblogs.com/mengdd/archive/2013/01/21/2869778.html

    http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

    http://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html

    http://www.cnblogs.com/sunwei2012/archive/2010/10/08/1845938.html

    http://www.cnblogs.com/Fskjb/archive/2009/08/23/1552506.html

    http://www.cnblogs.com/yinhaiming/articles/1749738.html

    http://www.cnblogs.com/nerxious/archive/2012/12/21/2828121.html

    http://www.cnblogs.com/anrainie/archive/2012/03/09/2387177.html

    http://blog.sina.com.cn/s/blog_44c1e6da0100cus8.html

  • 相关阅读:
    MS SQL Server 定时任务实现自动备份
    Python日期的加减等操作
    C# DbHelperSQL 类,从东软生成器提取而来
    C# List<string>和ArrayList用指定的分隔符分隔成字符串
    自定义可视化调试工具(Microsoft.VisualStudio.DebuggerVisualizers)
    查看SQLServer最耗资源时间的SQL语句
    程序员不适合创业
    如何写高质量,不繁琐的会议记录?
    C#中的Attribute详解(下)
    微信小程序教程系列
  • 原文地址:https://www.cnblogs.com/upcwanghaibo/p/5651168.html
Copyright © 2011-2022 走看看