java中的类型信息

java类型信息

本博客参考<< thinking in java >> 第十四章类型信息,做一些适当的学习笔记.注意,列举的代码并不完全是书上的代码,有些是自己学期期间测试的代码.

java运行时识别对象和类的信息


方法有如下两种:
1. “传统的”RTTI, 它假定我们在编译时已经知道了所有类型.
2. 反射机制,允许我们在运行时发现和使用类的信息.

RTTI含义


在运行时识别一个对象,使用RTTI可以查询某个基类引用指向的对象的确切类型.然后选择或者剔除.

Class对象


  1. 每一个类都有一个Class对象,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中).为了生成这个类的对象,运行这个程序的JVM将使用被称为”类加载器”的子系统.
  2. java程序在它开始运行之前并非被完全加载,其中一些部分是在必需时才加载的.
  3. 类加载器首先会检查这个类的Class对象是否被加载,如果尚未被加载,默认的类加载器就会根据类名查找.class文件
  4. 一旦某个类的Class对象被载入内存,那么它就被用来创建这个类的所有对象.Class对象仅在需要的时候才被加载,static初始化是在类加载时进行的.

    package type;
    public class Animal {
        static {
            System.out.println("loading animal");
        }
        {
            System.out.println("hello");
        }
        public Animal(){
            System.out.println("In animal constructor");
        }
    }
    
    package type;
    
    public class TestReflection {
        public static void main(String[] args) throws ClassNotFoundException {
            Class.forName("type.Animal"); // output : loading animal
        }
    }
    
  5. 使用class对象的getConstructor().newInstance()方法可以创建一个对象,如果这个class没有指定相应的泛型,那么产生的对象是Object类型的,还需要自己向下转换为对应的类型.

类字面常量


  1. 累字面常量:java提供的另外一种生成对Class对象的引用.
  2. ClassType.class就可以获得对应类的Class对象.其中ClassType可以是普通的类,接口,数组和基本类型.而且这种方法更为安全,因为它在编译的时候就会收到检查,不需要置于try语句中,因为它根除了forName()方法的调用.
  3. 当使用”.class”来创建Class对象的引用时,不会自动初始化该Class对象,为了使用类而做的准备工作实际包含三个步骤:

    • 加载.由类加载器执行,这一步会寻找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象.
    • 链接.将会验证类中的字节码,为静态域分配储存空间,而且如果必须的话,将会解析这个类中创建对其他类的所有引用.
    • 初始化.如果该类具有超类,则会对其初始化,执行静态初始化器和静态初始化块.

      package type;
      public class Animal {
          static {
              System.out.println("loading animal");
          }
          {
              System.out.println("hello animal");
          }
          public Animal(){
              System.out.println("In animal constructor");
          }
      }
      
      package type;
      public class Cat extends Animal{
          static {
              System.out.println("loading cat");
          }
          {
              System.out.println("hello cat");
          }
          public Cat(){
              System.out.println("In cat constructor");
          }
      }
      
      package type;
      public class TestReflection {
          public static void main(String[] args) throws ClassNotFoundException {
              // 下面三句单独作用的话,输出如下
              //Class c = Cat.class; // 没有任何输出
              //Class.forName("type.Cat"); // output : 1. loading animal 2. loading cat
              //Animal a = new Cat(); // output : 1. loading animal 2. loading cat 3. hello animal 4. in animal constructor 5. hello cat 6.in cat constructor
          }
      }
      
    • 总结一下:仅使用.class语法来获得对类的引用不会引发初始化,但是用Class.forName()立刻会进入初始化.

泛化的Class引用


  1. 引入泛型, Class<Classtype> c = ClassType.class, “宽松型”的也可以定义为 Class<? extends BaseType> c = new ChildType.class 或者 Class<? super ChildType> c = new BaseType.class, 而且 Class<?> 等价于平凡的Class,但是前者更好,表明了你故意选择一个非具体的引用.向Class引用添加泛型语法仅仅是为了提供编译器类型检查.

instanceof关键字


  1. 告诉我们对象是不是某个特定类型的实例.
  2. 使用方法: obj(某个实例对象) instanceof ClassName(注意只是类名而不是一个Class对象) 例如 x instanceof Dog

动态的instanceof


  1. Class.isInstance()方法可以动态测试对象类型:例如Animal.class.isInstance(dog),其中dog为Dog(继承animal)类型的一个实例,输出true,表示为dog是Animal或者Animal子类的一个实例,即SuperClass.isInstance(child)永远为真.

Class.isAssignableFrom


  1. 和上面isInstance的作用类似,只是传递的参数不是一个实例对象而是一个Class,于是有SuperClass.isAssignableFrom(child.getClass()) == SuperClass.isInstance(child).

instanceof和Class的等价性


  1. 使用instanceof和Class.isInstance()生成的结果是一样的,使用Class.equals()和==的结果也是一样的,但是这两组的作用是不一样的,前者能够支持继承关系,但是后者不支持,只有两个Class确实是一样的类型才会返回true.

接口和类型信息


  1. 使用语句BaseInterface a = new ChildClass(),假如BaseInterface中只有method1()这个方法,而childClass(继承了BaseInterface)中有一个自己的方法method2(),我们可以发现a.method2()会产生一个编译错误,即使它的实现是ChildClass.但是可以通过向下转型来调用method2();
  2. 再次考虑上面的问题,假如ChildClass的定义变为以下:

    class ChildClass implements BaseInteface{
        public void method1()
        void method2(){...};
        public method3(){...};
        private method4(){...};
    }
    

因为考虑了包访问权限,所以在包外就不可以看到这个类了.
3. 有趣的是,如果有一个类:

    public class Hidden{
        public static BaseInteface getBase(){
            return new ChildClass();
        }
    }

即使在类外,使用instanceof方法测试getBase()方法的返回值,发现它仍然是ChildClass类型的,即使在包外你不能使用除了BaseInterface之外的所有方法.当热了,你也不可以向下转型为ChildClass的类型,因为在包外根本没有这个类的访问权.
4.虽然不可以向下转型为子类,但是不代表不能够调用子类中的方法(甚至连private的方法也可以调用).

static void callHiddenMethod(Object obj, String methodName) throw Exception{
    Method m = obj.getClass().getDeclaredMethod(methodName);
    g.setAccessible(true); // 设置为true后连private的方法也可以访问!
    g.invoke(obj);
}

你可能认为通过在会发布编译后的代码来阻止这种情况,但是这并不可以解决问题,因为只要运行javap,一个随JDK的反编译器即可突破这一限制,如使用javap -private ChildClass,-private表示所有成员都应该显示,甚至是私有成员,它的输出包括这个类中的定义的所有方法,所有所有人都可以获得你最私有的方法名字和签名从而调用他们.我们还能知道,不管是方法,甚至是所有的数据域,都能够用类似的方法访问到他们.如果想避免private域被修改,可以加上final关键字,即使在运行时对一个final域进行修改不会抛出异常,但是实际上不会发生任何的修改.

猜你喜欢

转载自blog.csdn.net/qq_37993487/article/details/79998269