zoukankan      html  css  js  c++  java
  • Java类型信息之RTTI

    软件工程的一个核心问题就是软件的复用和扩展。面向对象思想通过封装,继承,派生等机制有效地解决了这个问题。但需求总是变幻莫测,不可琢磨,在面向对象这栋恢宏的大厦旁,还漂浮着一朵乌云,从而导致了RTTI的登场。

    正是因为RTTI的存在,在软件世界里,无间道是会分分钟暴露的:

    https://img3.mukewang.com/5b78cdfc0001a24a04940277.jpg

    1.缘起

    考虑一下面向对象思想发源地之一的CAD系统:

    package typeinfo._01_rtti;
    
    import java.util.Arrays;
    import java.util.List;
    
    interface Shape {
        void draw();
    }
    
    class CAD {
        public void draw(List<Shape> shapes) {
            for (Shape shape : shapes) {
                shape.draw();
            }
        }
    }
    
    class Circle implements Shape {
        @Override
        public void draw() {
            System.out.println("Circle draw()");
        }
    }
    
    class Square implements Shape {
        @Override
        public void draw() {
            System.out.println("Square draw()");
        }
    }
    
    class Triangle implements Shape {
        @Override
        public void draw() {
            System.out.println("Triangle draw()");
        }
    }
    
    public class Shapes {
        public static void main(String[] args) {
            List<Shape> shapes = Arrays.asList(
                    new Circle(), new Square(), new Triangle()
            );
    
            CAD cad = new CAD();
            cad.draw(shapes);
        }
    }

    输出结果:

    Circle draw()
    Square draw()
    Triangle draw()

    代码点评:

    Shape是基类,根据多态机制,Shape引用可以被它的子类引用赋值,并通过动态绑定在虚函数draw调用时正确调用子类的draw。

    依靠这种机制,我们可以轻松的在上层的CAD类中写出通用的draw方法,而不用管Shape到底会有多少子类。

    现在忽然来了新的需求,要求CAD在draw的时候把用户指定类型的对象高亮显示,假设用户指定了Circle类型,那就是要求把当前图形中的所有的Circle对象高亮显示。因为上层的CAD代码无法分辨具体的子类类型,所以功能无法实现。

    痛定思痛,这些面向对象的大师们决定引入RTTI(Run-Time Type Identification)运行时类型识别,简单的说就是运行时你只要给我一个引用,就我能准确的告诉你它是什么具体类型的。

    尽管RTTI名声不好,大师们的建议是能少用就少用,但是具有讽刺意味的是,几乎没有哪门语言能跳过RTTI,不同的只是各种各样的语法区别:

    • Python语言的type

    • Java语言的getClass

    • javascript语言的typeof

    • C++语言的typeid

    • C#语言的GetType

    • 。。。

    2.Java语言的class

    Java语言的class其实是一个Class类的对象,任何类都有一个这样的对象,实际上,这是对象又是Java类加载器加载类时自动带上的,要理清这些角色之间的复杂关系,请看下图:

    https://img1.mukewang.com/5b78ce510001dfc008100650.jpg

    1. 所有new出来的对象共享该类型的class对象

    2. class对象是Class类的一个实例

    3. Class类是被ClassLoader带进来的

    4. ClassLoader通过加载class字节码导入类

    口说无凭,直接来段测试代码吧:

     1 @Test
     2 public void testClassLevel() {
     3     class Test {
     4     }
     5 
     6     Test a = new Test();
     7     System.out.println(a.getClass());
     8     Test b = new Test();
     9     System.out.println(b.getClass());
    10 
    11     System.out.println(a.getClass() == a.getClass());
    12 
    13     System.out.println(Test.class);
    14     System.out.println(Test.class.getClassLoader());
    15 }

    输出结果:

    class typeinfo._01_rtti.ClassClassTest$1Test
    class typeinfo._01_rtti.ClassClassTest$1Test
    true
    class typeinfo._01_rtti.ClassClassTest$1Test
    jdk.internal.loader.ClassLoaders$AppClassLoader@726f3b58

    代码点评:

    new出来的对象可以通过getClass得到该类型的class对象

    所有new出来的对象getClass得到的class对象都是同一个

    class对象可以通过Test.class的形式快速引用

    class对象可以通过getClassLoader得到类加载器

    关于类加载器的细节,将单独有一篇文章介绍

    2.引用class

    引用class最简单的方式就是通过XXX.class的语法直接引用:

    public void print(Class meta) {
        System.out.println(meta);
    }
    
    @Test
    public void testClassClass() {
        print(boolean.class);
        print(char.class);
        print(byte.class);
        print(short.class);
        print(int.class);
        print(long.class);
        print(float.class);
        print(double.class);
    
        print(void.class);
        print(Object.class);
        print(String.class);
    }

    输出结果:

    boolean
    char
    byte
    short
    int
    long
    float
    double
    void
    class java.lang.Object
    class java.lang.String

    代码点评:

    没错,基本类型也有class,甚至是void也有class

    class及其Class就是Java类型体系的核心

    还有内置的数组呢?同样不能例外:

    @Test
    public void testClassClassArray() {
        print(boolean[].class);
        print(char[].class);
        print(byte[].class);
        print(short[].class);
        print(int[].class);
        print(long[].class);
        print(float[].class);
        print(double[].class);
        
        //print(void[].class);
        print(Object[].class);
        print(String[].class);
    }

    输出结果:

    class [Z
    class [C
    class [B
    class [S
    class [I
    class [J
    class [F
    class [D
    class [Ljava.lang.Object;
    class [Ljava.lang.String;

    代码点评:

    void[]是不存在的

    输出的这些奇怪的字符,这是历史原因造成的,其实它就表示数组

    通过对象得到class要用getClass:

     1 @Test
     2 public void testClassGetClass() {
     3     Boolean t = true;
     4     Character c = 'A';
     5     Byte b = 0;
     6     Short s = 0;
     7     Integer i = 0;
     8     Long l = 0L;
     9     Float f = 1.0f;
    10     Double d = 1.0;
    11 
    12     print(t.getClass());
    13     print(c.getClass());
    14     print(b.getClass());
    15     print(s.getClass());
    16     print(i.getClass());
    17     print(l.getClass());
    18     print(f.getClass());
    19     print(d.getClass());
    20 
    21     //Void v = new Void();
    22     Object obj = new Object();
    23     String str = new String();
    24     //print(v.getClass());
    25     print(obj.getClass());
    26     print(str.getClass());
    27 }

    输出结果:

    class java.lang.Boolean
    class java.lang.Character
    class java.lang.Byte
    class java.lang.Short
    class java.lang.Integer
    class java.lang.Long
    class java.lang.Float
    class java.lang.Double
    class java.lang.Object
    class java.lang.String

    代码点评:

    基本类型不能生成对象引用,只好用它们的包装类对象引用

    除了Void,其它的与XXX.class是一致的

    其实数组也是对象,所以下面的代码也是成立的:

     1 @Test
     2 public void testClassGetClassArray() {
     3     boolean[] t = {true};
     4     char[] c = {'A'};
     5     byte[] b = {0};
     6     short[] s = {0};
     7     int[] i = {0};
     8     long l[] = {0L};
     9     float f[] = {1.0f};
    10     double[] d = {1.0};
    11 
    12     print(t.getClass());
    13     print(c.getClass());
    14     print(b.getClass());
    15     print(s.getClass());
    16     print(i.getClass());
    17     print(l.getClass());
    18     print(f.getClass());
    19     print(d.getClass());
    20 
    21     Object[] obj = {new Object()};
    22     String[] str = {new String()};
    23     print(obj.getClass());
    24     print(str.getClass());
    25 }

    输出结果:

    class [Z
    class [C
    class [B
    class [S
    class [I
    class [J
    class [F
    class [D
    class [Ljava.lang.Object;
    class [Ljava.lang.String;

    对于Java的基本类型,还可以通过XXX.TYPE的形式引用class,这是基本类型所特有的绿色通道:

     1 @Test
     2 public void testClassType() {
     3     print(Boolean.TYPE);
     4     print(Character.TYPE);
     5     print(Byte.TYPE);
     6     print(Short.TYPE);
     7     print(Integer.TYPE);
     8     print(Long.TYPE);
     9     print(Float.TYPE);
    10     print(Double.TYPE);
    11 
    12     print(Void.TYPE);
    13     //print(Object.TYPE);
    14     //print(String.TYPE);
    15 }

    输出结果:

    boolean
    char
    byte
    short
    int
    long
    float
    double
    void

    3.class与泛型

    原则上,所有的class都是Class类的对象,但是这太粗略了。我们可以通过泛型进一步细化Class类:

    1 @Test
    2 public void testGeneric() {
    3     Class intClass = int.class;
    4     intClass = double.class;
    5 
    6     Class<Integer> genericIntClass = int.class;
    7     genericIntClass = Integer.class; // Same thing
    8     // genericIntClass = double.class; // Illegal
    9 }

    代码点评:

    默认的Class即可以引用int.class,也可以引用double.class

    Class<Integer>就只能引用int.class了,不可以引用double.class

    当然了,在泛型的世界里,更习惯用通配符Class<?>代替Class,以表明它那高贵的泛型身份:

    1 @Test
    2 public void testWildcard() {
    3     Class<?> intClass = int.class;
    4     intClass = double.class;
    5     intClass = String.class;
    6 }

    当然了,还不能忘了泛型的限定符:

    1 @Test
    2 public void testBounded() {
    3     Class<? extends Number> numberClass = int.class;
    4     numberClass = double.class;
    5     numberClass = Number.class;
    6     //numberClass = String.class;
    7 }

    这里通过限定符的限制,有效的避免了String.class的引用。

  • 相关阅读:
    【python学习笔记02】python的数据类型2
    元器件选型(一)ESD、TVS参考资料
    【python学习笔记01】python的数据类型
    你不知道的vue生命周期钩子选项(三)
    vue源码解析之选项合并(二)
    vue源码解析之选项合并(一)
    聊聊var与let 在window下面的区别(除开作用域)
    浅谈JavaScript中的防抖和节流
    Redhat6.4下安装Oracle10g
    Ubuntu Server 14.04 集成
  • 原文地址:https://www.cnblogs.com/oowgsoo/p/9501088.html
Copyright © 2011-2022 走看看