zoukankan      html  css  js  c++  java
  • 初识Java泛型以及桥接方法

    泛型的由来

    在编写程序时,可能会有这样的需求:
    容器类,比如java中常见的list等。为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型。
    此时可能会有IntList、LongList、StringList、XXXList等多个List容器类,但是容器类内部的各个方法,除了add、remove、get方法操作的数据类型不同意外,几乎全部相同。为了方便起见,可以使用ObjectList,但是在get的数据是Object类型,需要进行强制类型转换,这就会造成一些程序运行时的隐患。

    通过查询网络资料时,发现这个需求对于泛型的需要最迫切。于是在jdk1.5(2004年)中,java加入了泛型。

    什么是泛型

    泛型:参数化类型。将类类型,作为参数,进行传递
    泛型参数,可以为多个,使用尖括号括起来。
    比如,通过实例化List容器时,指定容器类类型String,实现了String容器。

    List<String> list= new LinkedList<String>();
    list.add("1");//没有编译错误
    list.add(1);//编译错误

    如何实现泛型

    在java中,可实现泛型接口泛型类泛型方法

    1、泛型接口:常见的List接口,Map接口,都是泛型接口。
    举例:

    public interface Test<T>{
      public void add(T element);
      public T get(int index);
    }

    2、泛型类:LinkedList、ArrayList、HashMap、TreeMap等都是泛型类,并且他们实现了泛型接口;

    举例:

    public class Target<T, E> implements Test<T>{
        private Object[] list=new Object[100];
        private int index=0;
        public void add(T element){
        list[index++]=element;
    }
        public T get(int index){
            return (T)list[index];
        }
    }

    3、泛型方法:同泛型接口和泛型类的定义稍微有些不同。

    举例:

    public <T> T add( Class<T> clz){
        T instance=clz.newInstance();
        return instance
    }

    在定义泛型接口、类、方法时,可以添加多个泛型形参,一般使用大写字母表示,并用逗号分隔,泛型定义处,使用尖括号将所有泛型形参括起来。

    泛型接口、类的泛型形参,放置在接口或类名的后面。
    泛型方法的泛型形参,放置在方法返回类型的前面即可。

    注意,java的泛型属于code share,只是在编译时使用,用于检查传递给泛型类、方法的数据类型是否符合泛型定义,泛型类和方法中的泛型会在编译成功后擦出,成为Object类型,编译器并会在泛型数据操作时,自动添加类型转换。由于编译期间保证了泛型类型的正确使用,因此自动添加的类型转换是安全的。
    举例:
    使用List容器,调用其方法

    public class Main{
        public static void main(String[] args){
            List<Long> ttt=new LinkedList<Long>();
            tt.add(1L);
            Long value=tt.get(0);
        }
    }

    在编译后的class文件,可翻译为:

    public class Main{
        public static void main(String[] args){
            List ttt=new LinkedList();
            tt.add(1L);
            Long value=(Long)tt.get(0);
        }
    }

    编译时,编译器会检查凡是使用泛型类实例的地方,是不是按照泛型的规定,进行传参的。

    比如ttt.add(1L),编译器会检查传入的参数是不是Long型;还有tt.get(0),编译器会自动加上类型转换。最终看到编译后,泛型类的泛型被擦出了。
    类型擦出导致,以下输出会为true,因为泛型类不会因为不同的泛型类型,导致生成不同的类。

    List<String> list1=new LinkedList<String>();
    List<Integer> list2=new LinkedList<Integer>();
    System.out.println(list2.getClass()==list1.getClass());//输出为true

    泛型定义时的约束

    定义泛型形参时,可以使用extends限制泛型的范围。
    比如使用<T extends K>,表示T必须是类K的子类,或者接口K的实现类。
    比如使用<T super K>,表示T必须是类K的父类,或类K实现的接口。
    注意:extends并不表示类的继承含义,只是表示泛型的范围关系。
    注意:extends中可以指定多个范围,实行泛型类型检查约束时,会以最左边的为准。

    java泛型实现的原理

    在C++中也有泛型的概念,但java的泛型实现原理同C++是不一样的。
    泛型实现原理有两种:
    1、为使用的泛型类,单独生成一份非泛型的具体类。
    比如List<String> list=new ArrayList<String>,在程序编译时,会单独生成一份StringArrayList类,并且类里面所有的操作元素都是String类。
    2、采用擦出机制,泛型接口或类中,所有使用泛型形参的地方,全部擦除,替换为Object类型(java中所有类的父类)。所有相同泛型类的实例共享使用泛型类的代码。在泛型类的实例进行数据操作的地方(泛型类外部,自定义的程序部分),由编译器检查操作的参数是否为泛型类实例定义时的类类型,必要时自动添加强制类型转换。

    在擦除时,如果<T extends XXClass>,T擦出后会变为XXClass。
    如果<T extends AClass,BClass>,T擦出后,类型为AClass,以最左边的类型为最终类型,这点要注意。

    桥接方法

    首先举一个例子。

    public class A<T>{
        public T get(T a){
            //a进行一些操作
            return a;
        }
    }
    public class B extends A<String>{
        @override
        public String get(String a){
            //a进行一些操作
            return a;
        }
    }

    由于类型擦出机制的存在,按理说编译后的文件在翻译为java应如下所示

    public class A{
        public Object get(Object a){
            //a进行一些操作
            return a;
        }
    }
    public class B extends A{
        @override
        public String get(String a){
            //a进行一些操作
            return a;
        }
    }

    @override意味着B对父类A中的get方法进行了重写,但是依上面的程序来看,只是重载,依然可以执行父类的方法,这和期望是不附的,也不符合java继承、多态的特性。

    注:子类重写父类方法,只要此实例的实际类型是子类类型了(不是引用类型),便不能调用父类的方法,这是语言的多态特性。

    为了解决这个问题,java在编译期间加入了桥接方法。编译后再翻译为java原文件,应如下所示:

    public class A{
        public Object get(Object a){
            //a进行一些操作
            return a;
        }
    }
    public class B extends A{
        @override
        public String get(String a){
            //a进行一些操作
            return a;
        }
        public Object get(Object a){
            return get((String)a)
        }
    }

    类B中的第二个方法,即为桥接方法。桥接方法重写了父类相同的方法,并且桥接方法中,最终调用了期望的重写方法,并且桥接方法在调用目的方法时,参数被强制转换为指定的泛型类型。

    桥接方法测试:

    A a=new B();
    a.get(new Object());

    由于A未声明泛型类型,编译期间会警告存在类型转换错误。

    运行期间,还真抛出了类型转换错误,Object to String。

    实例a实际类型为B,在调用a.get方法时最终调用了B类中的桥接方法,保证了传入参数为子类继承父类使用的泛型类型。

  • 相关阅读:
    安卓使用socket建立通信客户端不显示数据
    使用bmob后端云中listview如何实现数据显示在第一个item
    安卓如何实现点击一个按钮退出整个程序?
    Python安装与准备
    敏感度——区分平庸和伟大
    fragment中设置点击事件(onclick)使用intent进行通信
    listview的几种简单用法
    Android开发现在是不是该学?
    数据包重放
    Sql注入一种dump所有数据的方法
  • 原文地址:https://www.cnblogs.com/mahuan2/p/6073493.html
Copyright © 2011-2022 走看看