zoukankan      html  css  js  c++  java
  • Java——擦除

    直接代码分析一波:

    import java.util.*;
    
    public class Ex12 {
        public static void main(String[] args) {
            Class c1 = new Double(0).getClass();
            Class c2 = new Integer(0).getClass();
            Class c3 = new ArrayList<String>().getClass();
            Class c4 = new ArrayList<Integer>().getClass();
            System.out.println(c1 == c2);
            System.out.println(c3 == c4);
        }
    }

    这里的c1 c2 c3 c4将分别得到Double、Integer、ArrayList<String>、ArrayList<Integer>的类,输出是:

    false
    true

    c1 == c2很明显是不一样的,这里的ArrayList<String>和ArrayList<Integer>很容易认为是不同的类型,但是其实他两是相同类型的,

    import java.util.*;
    
    class test1<T>{}
    class test2<T,Q>{}
    public class Ex12 {
        public static void main(String[] args) {
            test1<Double> t1 = new test1<Double>();
            test2<Double, Double> t2 = new test2<Double, Double>();
            System.out.println(Arrays.toString(t1.getClass().getTypeParameters()));
            System.out.println(Arrays.toString(t2.getClass().getTypeParameters()));
        }
    }

    Class.getTypeParameters()将返回一个泛型声明所声明的类型参数数组,但是只能看到T,Q,得不到Double,这是因为在java泛型是使用擦除来实现的,这就意味这当你在使用泛型的时候,任何具体点的类型信息都被擦除了,唯一知道的就是你在使用一个对象,因此List<String>和List<Integer>是相同的类型。

    1、边界

    class HasF{
        public void f() {System.out.println("f()");}
    }
    class Ma<T>{
        T obj;
        Ma(T t){ obj = t;}
        public void maf(){obj.f();}//这里是错的
    }

    假如你有一个HasF类,这个类中有一个f()方法,然后你想创建一个泛型类,并且想将obj的f()方法,这是java编译器无法实现的,因为擦除。为了调用f(),我们必须协助泛型类,给定泛型的边界,以此告知编译器只能接受遵循这个边界的类型。

    用extends关键字,就能设置边界。

    import java.util.*;
    
    class HasF{
        public void f() {System.out.println("f()");}
    }
    class Ma<T extends HasF >{
        T obj;
        Ma(T t){ obj = t;}
        public void maf(){obj.f();}//这里是错的
    }
    public class Ex12 {
        public static void main(String[] args) {
            HasF hasf = new HasF();
            Ma<HasF> ma = new Ma<HasF>(hasf);
            ma.maf();
        }
    }

    边界<T extends HasF >声明T必须具有类型HasF或者从HasF导出的类型。

    泛型类型参数将擦除到它的第一个边界,实际上编译器会把类型参数替换为它的擦除,上面的示例就是T擦除到HasF。

    2、擦除的问题

    泛型不能显式的引用运行时类型的操作中,例如转型、instanceof操作和new表达式,因为所有关于参数的类型信息都丢失了。例如:

    class Foo<T>{
        T var;
    }
    class Cat{}

    定义这样两个类,Foo是一个泛型类,Cat是一个普通类

    Foo<Cat> foo = new Foo<Cat>();

    这里看起来class Foo中的所有操作都像是使用Cat来操作,泛型好像也在告诉我们所有的类型T都将会被替换,但事实上我们必须提醒自己,被擦除了,它只是一个Object,也就是说这里没有边界,应该说边界就是Object。

    import java.lang.reflect.Array;
    import java.util.Arrays;
    
    class ArrayMaker<T>{
        private Class<T> kind;
        ArrayMaker(Class<T> kind){ this.kind = kind;}
        T[] create(int size) {
            return (T[])Array.newInstance(kind, size);//这里会有警告
        }
    }
    
    public class Ex13 {
        public static void main(String[] args) throws Exception{
            ArrayMaker<String> stringMaker =
                    new ArrayMaker<String>(String.class);
            String[] stringArray = stringMaker.create(9);
            System.out.println(Arrays.toString(stringArray));
        }    
    }

    输出:

    [null, null, null, null, null, null, null, null, null]

    即使这里的泛型被指定为Class<T>擦除就意味这kind实际被储存的是Class,由此在使用的时候,Array.newInstance(kind, size)并为拥有kind的类型信息,就是不知道kind是String.class,所以需要转型。

    但是在创建一个容器情况稍有不同

    public class Ex13<T> {
        List<T> create(){return new ArrayList<T>();}
        public static void main(String[] args) throws Exception{
            Ex13<String> ex = new Ex13<String>();
            List<String> stringList = ex.create();
        }    
    }

    这里的编译器 不会给出任何的警告,尽管我们知道create中的<T>被移除了。这样就能这样操作:

    public class Ex13<T> {
        List<T> create(T t , int n){
            List<T> result = new ArrayList<T> ();
            for(int i = 0; i < n; i++) 
                result.add(t);
            return result;
            }
        public static void main(String[] args) throws Exception{
            Ex13<String> ex = new Ex13<String>();
            List<String> stringList = ex.create("hello", 4);
            System.out.println(stringList);
        }    
    }

    输出:

    [hello, hello, hello, hello]

    但是数组就不能这样做:

    public class Ex13<T> {
        T[] create(T t , int n){
            T[] result = new T[n];//这里编译器无法编译
            for(int i = 0; i < n; i++) 
                result[i] = t;
            return result;
            }
        
        public static void main(String[] args) throws Exception{
            Ex13<String> ex = new Ex13<String>();
            List<String> stringList = ex.create("hello", 4);
            System.out.println(stringList);
        }    
    }

    因为T的类型被擦除了。一般都使用Array.newInstance()方法或者使用List。

    3、擦除的补偿

    public class Ex13<T> {
        public void f(Object o) {
            if(o instanceof T) {}//编译出错
            T var = new T();//编译出错
            T[] ts = new T[10];//编译出错
            T[] tss = (T)new Object[10];//编译出错
        }    
    }

    因为擦除使泛型代码失去某些执行某些操作的能力,想要绕过这些问题进行编程需要引入类型标签对擦除进行补偿。

    • instanceof的使用失败 可以使用动态的ISinstance)  
    public class Ex13<T> {
        Class<T> kind;
        Ex13(Class<T> kind){
            this.kind = kind;
        }
        public boolean f(Object o) {
            return kind.isInstance(o);
        }
        public static void main(String[] args) {
            Ex13<String> ex = new Ex13<String>(String.class);
            System.out.println(ex.f("233"));
            System.out.println(ex.f(233));
        }
    }

    输出:

    true
    false
    • T var = new T();创建类型实例无法实现,一部分原因是因为擦除了,另一部分原因是因为编译器不能验证T具有默认(无参)的构造器,解决方法还是同样的使用Class对象。
    public class Ex13<T> {
        Class<T> kind;
        Ex13(Class<T> kind){
            this.kind = kind;
        }
        public T f() {
            try {
                return kind.newInstance();
            } catch(Exception e) {
                throw new RuntimeException(e);
            }        
        }
        public static void main(String[] args) {
            String a = new Ex13<String>(String.class).f();
        }
    }

    调用用Class的newInstance()方法即可,但是如果在main中这样使用

        public static void main(String[] args) {
            Integer b = new Ex13<Integer>(Integer.class).f();
        }

    就会失败,因为Integer是没有任何的默认的构造器。

    可以使用显式的工厂,并限制其类型,使得只能接受实现这个类。

    interface Factoryi<T>{
        T create();
    }

    先创建一个泛型接口,create()方法将返回一个T类对象。

    可以

    class IntegerFactory implements Factoryi<Integer>{
    
        public Integer create() {
            return new Integer(0);
        }
        
    }

    可以直接实现这个接口。

    class Widest{
        public static class Factory implements Factoryi<Widest>{
            public Widest create() {
                return new Widest();
            }
        }
    }

    可以在这个类中创建一个内部类去实现这个接口,

    class Foo2<T>{
        private T x;
        public <F extends Factoryi<T>> Foo2(F factory){
            x = factory.create();
        }
    }

    这就是实际上的泛型类了,因为Integer和Widest都没有任何默认的构造器,所以就创建一个类实现Factoryi<T>接口,实现其中的create()方法然后在泛型类从使用这个Factoryi<T>作为边界,使用其中的create()方法,从而实现在泛型类从创建泛型类型实例。

    • 泛型数组

      不能创建泛型数组,一般的解决方案是使用Araayist:

    public class Ex13<T> {
        public List<T> array = new ArrayList<T>();
        public void add(T item) {
            array.add(item);
        }
        public T get(int index) {
            return array.get(index);
        }
    }

    这样和数组其实也能实现和数组差不多的功能。

    还能通过传入Class类型对象,然后使用Array.newInstance()方法创建一个数组

    class ArrayMaker<T>{
        private Class<T> kind;
        ArrayMaker(Class<T> kind){ this.kind = kind;}
        T[] create(int size) {
            return (T[])Array.newInstance(kind, size);//这里会有警告
        }
    }
    
    public class Ex13 {
        public static void main(String[] args) {
            String[] s = new  ArrayMaker<String>(String.class).create(10);
        }
    }

    5、通配符

    先看一个数组的特殊行为:

    class Fruit{}
    class Apple extends Fruit{}
    class Orange extends Fruit{}
    class Jon extends Apple{}
    public class Ex14 {
        public static void main(String[] args) {
            Fruit[] fruit = new Apple[10];
            fruit[0] = new Apple();
            fruit[1] = new Jon();
            try {
            fruit[2] = new Fruit();
            }catch(Exception e) {
                System.out.println(e.toString());
            }
            try {
            fruit[3] = new Orange();
            }catch(Exception e) {
                System.out.println(e.toString());
            }
        }
    }

    这里的输出是:

    java.lang.ArrayStoreException: Fruit
    java.lang.ArrayStoreException: Orange

    这里创建了一个Apple数组,并将其赋值给一个Fruit数组。你可以将一个Apple或者Apple的子类对象放入数组,并不会有警告,和错误。

    但是当你想要放入一个Fruit或是Orange对象的时候,编译器不会有警告但是运行的时候却能捕获到java.lang.ArrayStoreException异常,编译器不会给出警告是因为数组有一个Fruit[]引用,但是运行的时候数组机制知道这是一个Apple[]数组,所以就会抛出异常  。

    类似的当我们试图使用泛型容器来代替数组的时候:

    public class Ex14 {
        public static void main(String[] args) {
            List<Fruit> flist = new ArrayList<Apple>();
        }
    }

    这时编译器会直接给出警告,讲道理Apple是Fruit的子类,这应该属于向上转型,然而实际上这里并不是根本不是向上转型这里是两个List,Apple可以算是Fruit类型的,但是Apple的List并不是Fruit的List,这时如果想要建立某种类型的向上转型关系,就需要使用通配符了。

    public class Ex14 {
        public static void main(String[] args) {
            List<? extends Fruit> flist = new ArrayList<Apple>();
            flist.add(new Orange());
            flist.add(new Apple());
            flist.add(new Jon());
        }
    }

    然后创建List就不会报错了,但是后面的调用add()方法还是会给出警告。

    flist的类型现在是List<? extends Fruit>,可以将其读为“具有任何从Fruit继承的类型的列表”,但是并不就意味着这个List就可以持有任何类型的Fruit。通配符指定的是没有指定具体类型,其实就是只能接受不能修改

    public class Ex14 {
        public static void main(String[] args) {
            List<Apple> list = new ArrayList<Apple>();
            list.add(new Apple());
            list.add(new Jon());
            List<? extends Fruit> flist = list;
        }
    }

    这样的使用就是没错的。

  • 相关阅读:
    非GUI运行Jmeter,jtl文件没有响应数据的解决办法
    Fiddler抓取APP接口
    CentOS 7.x关闭/开启防火墙出现Unit iptables.service failed to load: No such file or directory问题解决
    Jmeter+Ant+Jenkins接口自动化持续集成环境搭建(Linux)
    Jenkins持续集成环境部署
    性能测试流程介绍
    MySQL性能优化
    Linux监控命令之==>ps
    Linux监控命令之==>lsof
    Zabbix监控基础
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/7719749.html
Copyright © 2011-2022 走看看