zoukankan      html  css  js  c++  java
  • Java基础之泛型

    转载: Java基础之泛型

    一、泛型的理解与简单使用

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

    在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

    泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

    1.1、泛型在接口上的使用:

    package com.luo.test;
    
    public interface TestInterface<T> {
    
        String objectToString(T o); 
    
    }
    

    对应实现类可以这样:

    package com.luo.test;
    
    public class TestInterfaceImpl<T> implements TestInterface<T> {
    
        public String objectToString(T o) {
            return o.toString();
        }
    
        public static void main(String args[]){
            TestInterfaceImpl<Integer> testInterfaceImpl = new TestInterfaceImpl<Integer>();
            Integer integer = new Integer(123);
            System.out.println(testInterfaceImpl.objectToString(integer));
        }
    
    }
    

    运行结果:123

    1.2、泛型在类上单独使用(不实现接口):

    package com.luo.test;
    
    public class ClassTest<T> {
    
        private T ob; // 定义泛型成员变量
    
        public T getOb() {
            return ob;
        }
    
        public void setOb(T ob) {
            this.ob = ob;
        }
    
        public void showObType() {
            System.out.println("T的实际类型是: " + ob.getClass().getName());
        }
    
        public static void main(String args[]){
            ClassTest<Integer> classTest = new ClassTest<Integer>();
            classTest.setOb(123);
            classTest.showObType();
        }
    
    }
    

    运行结果:T的实际类型是: java.lang.Integer

    1.3、泛型在方法上单独使用:

    例如想要实现:

    package com.luo.test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyTest {
    
        public <T> List<T> write(T[] array){
            List<T> list = new ArrayList<T>();
            for (int i = 0; i < array.length; i++) {
                list.add(array[i]);
            }
            return list;
        }
    
        public static void main(String args[]){
    
        }
    }
    

    二、泛型的高级使用

    2.1、通配符“?”

    先看如下代码:

    我们都知道,Object所有类的基类,但是需要注意的是

    Collection<Object>并不是所有集合的超类。
    List<Object>, List<String>是两种不同的类型,他们之间没有继承关系,即使String继承了Object。
    这就是泛型的强大之处,引入范型后,一个复杂类型如(List),就可以在细分成更多的类型。
    

    为解决上面代码报错问题,引入通配符“?”:

    这样就不会编译出错啦。这里使用了通配符“?”指定可以使用任何类型的集合作为参数。

    2.2、边界通配符“?extends”

    假定有一个画图的应用,可以画各种形状的图形,如矩形和圆形等。为了在程序里面表示,定义如下的类层次:

    public abstract class Shape {
        public abstract void draw(Canvas c);
    }
    
    public class Circle extends Shape {
        private int x,y,radius;
        public void draw(Canvas c) { ... }
    }
    
    public class Rectangle extends Shape
        private int x,y,width,height;
        public void draw(Canvas c) { ... }
    }
    
    为了画出集合中所有的形状,我们可以定义一个函数,该函数接受带有泛型的集合类对象作为参数。但是不幸的是,我们只能接收元素类型为Shape的List对象,而不能接收类型为List<Cycle>的对象,这在前面已经说过。为了解决这个问题,所以有了边界通配符的概念。这里可以采用public void drawAll(List<? extends Shape> shapes)来满足条件,这样就可以接收元素类型为Shape子类型的列表作为参数了。
    
    //使用边界通配符的版本
    public void drawAll(List<?exends Shape> shapes) {
        for (Shapes:shapes) {
            s.draw(this);
        }
    }
    

    这里就又有个问题要注意了,如果我们希望在List<?exends Shape> shapes中加入一个矩形对象,如下所示:

    shapes.add(0, new Rectangle()); //compile-time error

    那么这时会出现一个编译时错误,原因在于:我们只知道shapes中的元素时Shape类型的子类型,具体是什么子类型我们并不清楚,所以我们不能往shapes中加入任何类型的对象。不过我们在取出其中对象时,可以使用Shape类型来取值,因为虽然我们不知道列表中的元素类型具体是什么类型,但是我们肯定的是它一定是Shape类的子类型。

    为解决添加问题,引入通配符“?super”

    2.3、通配符“?super”

    List<Shape> shapes = new ArrayList<Shape>();
    List<? super Cicle> cicleSupers = shapes;
    cicleSupers.add(new Cicle()); //OK, subclass of Cicle also OK
    cicleSupers.add(new Shape()); //ERROR
    

    这表示cicleSupers列表存储的元素为Cicle的超类,因此我们可以往其中加入Cicle对象或者Cicle的子类对象,但是不能加入Shape对象。这里的原因在于列表cicleSupers存储的元素类型为Cicle的超类,但是具体是Cicle的什么超类并不清楚。但是我们可以确定的是只要是Cicle或者Circle的子类,则一定是与该元素类别兼容。

    2.4、通配符总结

    如果你想从一个数据类型里获取数据,使用 ? extends 通配符
    如果你想把对象写入一个数据结构里,使用 ? super 通配符
    如果你既想存,又想取,那就别用通配符。

    三、总结

    3.1、 类型擦除概念

    类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上,因此泛型类型中的静态变量是所有实例共享的。此外,需要注意的是,一个static方法,无法访问泛型类的类型参数,因为类还没有实例化,所以,若static方法需要使用泛型能力,必须使其成为泛型方法。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。在使用泛型时,任何具体的类型都被擦除,唯一知道的是你在使用一个对象。比如:List和List在运行事实上是相同的类型。他们都被擦除成他们的原生类型,即List。因为编译的时候会有类型擦除,所以不能通过同一个泛型类的实例来区分方法,如下面的例子编译时会出错,因为类型擦除后,两个方法都是List类型的参数,因此并不能根据泛型类的类型来区分方法。

    /*会导致编译时错误*/ 
     public class Erasure{
         public void test(List<String> ls){
             System.out.println("Sting");
         }
         public void test(List<Integer> li){
             System.out.println("Integer");
         }
      }
    

    那么这就有个问题了,既然在编译的时候会在方法和类中擦除实际类型的信息,那么在返回对象时又是如何知道其具体类型的呢?如List编译后会擦除掉String信息,那么在运行时通过迭代器返回List中的对象时,又是如何知道List中存储的是String类型对象呢?

    擦除在方法体中移除了类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点,这正是编译器在编译期执行类型检查并插入转型代码的地点。泛型中的所有动作都发生在边界处:对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。

    3.2、方法重载

    在JAVA里面方法重载是不能通过返回值类型来区分的,比如代码一中一个类中定义两个如下的方法是不容许的。但是当参数为泛型类型时,却是可以的。如下面代码二中所示,虽然形参经过类型擦除后都为List类型,但是返回类型不同,这是可以的。

    /*代码一:编译时错误*/ 
    public class Erasure{
       public void test(int i){
            System.out.println("Sting");
        }
        public int test(int i){
            System.out.println("Integer");
        }
    }
    
    /*代码二:正确 */
    public class Erasure{
        public void test(List<String> ls){
            System.out.println("Sting");
        }
        public int test(List<Integer> li){
            System.out.println("Integer");
        }
    }
    

    3.3、泛型类型是被所有调用共享的

    所有泛型类的实例都共享同一个运行时类,类型参数信息会在编译时被擦除。因此考虑如下代码,虽然ArrayList和ArrayList类型参数不同,但是他们都共享ArrayList类,所以结果会是true。

    List<String>l1 = new ArrayList<String>();
    List<Integer>l2 = new ArrayList<Integer>();
    System.out.println(l1.getClass() == l2.getClass()); //True
    

    3.4、instanceof

    不能对确切的泛型类型使用instanceOf操作。如下面的操作是非法的,编译时会出错。

    Collection cs = new ArrayList<String>();
    if (cs instanceof Collection<String>){…}// compile error.如果改成instanceof Collection<?>则不会出错。
    

    3.5、泛型数组问题

    不能创建一个确切泛型类型的数组。如下面代码会出错。

    List<String>[] lsa = new ArrayList<String>[10]; //compile error.
    

    因为如果可以这样,那么考虑如下代码,会导致运行时错误。

    List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组
    Object o = lsa;
    Object[] oa = (Object[]) o;
    List<Integer>li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[1] = li;// unsound, but passes run time store check
    String s = lsa[1].get(0); //run-time error - ClassCastException
    
    因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。
    
    List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type
    Object o = lsa;
    Object[] oa = (Object[]) o;
    List<Integer>li = new ArrayList<Integer>();
    li.add(new Integer(3));
    oa[1] = li; //correct
    String s = (String) lsa[1].get(0);// run time error, but cast is explicit
    Integer it = (Integer)lsa[1].get(0); // OK&nbsp;
    

    参考文章:

    Java泛型编程最全总结

  • 相关阅读:
    1094. Car Pooling
    121. Best Time to Buy and Sell Stock
    58. Length of Last Word
    510. Inorder Successor in BST II
    198. House Robber
    57. Insert Interval
    15. 3Sum java solutions
    79. Word Search java solutions
    80. Remove Duplicates from Sorted Array II java solutions
    34. Search for a Range java solutions
  • 原文地址:https://www.cnblogs.com/andy-zhou/p/5315858.html
Copyright © 2011-2022 走看看