【JavaSE】运行时类型信息(RTTI、反射)

运行时类型信息使得你能够在程序运行时发现和使用类型信息。——《Think in java 4th》
****html

一般咱们在面向对象的程序设计中咱们常用多态特性使得大部分代码尽量地少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道,这样代码会更容易写,更容易读,且便于维护,设计也更容易实现、理解和改变。因此“多态”是面向对象编程的基本目标。可是,有些时候可以知道某个泛化引用对确切类型,就可使用最简单的方式去解决它,或者咱们必须去了解其确切功能和隐藏部分去完成某个功能时,使用RTTI和反射机制能够帮助咱们去完成咱们想要实现的功能。
*****java

1. Class对象

        类是程序的一部分,每一个类都有一个Class对象。为了生成这个类的对象,jvm使用了被称为“类加载器”的子系统。类加载器子系统实际上包含一条类加载器链,可是只有一个原生类加载器,它是jvm实现的一部分(native)。若是你有特殊需求,也能够添加额外对自定义类加载器。
        全部的类都是在对其第一次使用时,动态加载到jvm中的。当程序建立第一个对类的静态成员的引用时,就会加载这个类。这个证实构造器也是类的静态方法 (引用自java编程思想第四版P315) ,即便在构造器以前并无使用static关键字。所以,使用new操做符建立类的对象也会被当作对类的静态成员引用。
        所以,Java程序在它开始运行以前并不是被彻底加载,其各个部分是在必需时才加载的。这一点与许多传统语言都不一样,动态加载使能对行为,在诸如c++这样的静态加载语言中是很难或者根本不可能复制的。
        一旦一个类的Class对象被载入内存,它就被用来建立这个类的全部对象。c++

1)获取Class类对象引用的方法

① Class.forName("全类名");
这个方法是Class类的一个static成员,Class对象就和其余对象同样,咱们能够获取并操做它的引用(这就是类加载器的工做),forName()是取得引用的一种方法。它会使用调用类的类加载器加载指定的类。若是找不到目标类则会抛出ClassNotFoundException。使用此方法,你不须要为了得到Class引用而持有该类型的对象。编程

② 对象.getClass();
若是你已经有了一个想要的类型的对象,那就能够经过调用getClass()方法来获取Class引用了,这个方法属于Object类的成员方法,它将返回表示该对象实际类型的Class引用。若是你有一个Class对象,还可使用getInterfaces()getSuperclass()等方法获取接口类Class对象和基类Class对象,isInterface()方法判断是否为接口。数组

③ 类字面常量 类.class
Java还提供了另外一种方法来生成对Class对象的引用(例:Object.class),相比于Class.forName()不只更简单、并且更安全,由于它在编译器就会受到检查(所以不须要置于try语句块中),根除了对forName()方法的调用,因此也更高效。类字面常量不只能够应用于普通的类,也能够应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象:
在这里插入图片描述
Java编程思想中建议使用.class的形式,以保持与普通类的一致性。
有趣的是,使用.class来建立对Class对象的引用时,不会自动初始化该Class对象。
*****
为了使用类而作的准备工做实际包含三个步骤:
在这里插入图片描述
仅使用.class语法来得到对类的引用不会引起初始化,可是使用Class.forName()当即就进行了初始化。若是一个static final值是“编译期常量”,那么这个值不须要进行初始化就能够被读取。安全


2)泛化的Class引用

定义一个Class对象的引用能够任意地指向任何Class对象网络

Class intClass = int.class;
intClass = double.class;

然而若是你操做有误(将本应指向int.class的引用指向了double.class),只有在运行时才可能发现错误的赋值,由于编译器不知道那你的意图,不会报错。Java SE5提供了Class泛型,对Class引用指向的Class对象进行了限定。jvm

Class<Integer> intClass = int.class;
//intClass = double.class;//编译期报错

使用Class泛型的目的是让编译期进行类型检查,误操做时编译期报错,及时发现错误。
通配符?表示任何类型测试

Class<?> intClass = int.class;
intClass = double.class;

Class<?>与Class使用效果是等价的,可是Class<?>有不一样的表示意义,Class<?>表示我就是选择了不具体的类型。ui

Integer是Number子类,但Integer的Class对象 不是 Number的Class对象 的子类

//class<Number> numberClass = int.class;//不合法

使用Class<? extends Number>表示一个范围,限定Class为Number子类的Class对象

class<? extends Number> numberClass = int.class;//OK

使用泛型的Class引用.newInstance(),返回的是确切类型的实例对象,而不是Object。
除了相似Class<? super FancyToy>的引用调用newInstance(),返回Object类型的实例。
Class<? super FancyToy>表示“某个类,是FancyToy的超类”,是含糊的,因此返回Object类型的实例。

class Toy{}

class FancyToy extends Toy{}

public class GenericClass {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Class<? super FancyToy> up = Toy.class;
        Object obj = up.newInstance();//返回Object
        Class<Toy> toyClass = Toy.class;
        Toy toy = toyClass.newInstance();//返回Toy
    }

}

3)新的转型语法

Java SE5还添加了用于Class引用的转型语法,即cast()方法:

class Building {}
class House extends Building {}

public class ClassCasts {
    public static void main(String[] args){
        Building b = new House();
        Class<House> houseType = House.class;
        House h = houseType.cast(b);    // 等价操做
        h = (House)b;                   // 等价操做
    }
}

cast() 方法接受参数对象,并将其转型为Class引用的类型,新的转型语法对于没法使用普通转型的状况显得很是有用,在你编写泛型代码时,若是你存储了Class引用,并但愿之后经过这个引用来执行转型,这种状况就能够解决。

在javaSE5中另外一个新特性就是 Class.asSubclass() ,该方法容许你将一个类对象转型为更具体的类型,它将一个类转换成另一个的实例,若是转换异常就会抛出ClassCastException异常,也就是这个类不是另一个类的实例;因此咱们能够经过它抛出异常的方式来判断一个类是不是另一个类的实例。(java编程思想中说这是个没什么用的特性) Class.asSubclass浅谈

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Teacher {

    public static void main(String[] args) {

        Class<?> clazz = B.class;
        String name = clazz.getName();
        try {
            Class classA = clazz.asSubclass(A.class);
        } catch (ClassCastException e) {
            System.out.println(name+"不是类StopThread的子类");
        }
    }
}

4)类型转换检查

在对对象进行转型的时候须要先进行类型检查,不然可能会抛出ClassCastException

① instanceof 关键字
对 instanceof 有比较严格的限制:只能够将其与命名类型进行比较,而不能与Class对象做比较。

A a = new B();
if(a instanceof B) {
    B b = (B)a;
}

须要说明的是 instanceof 关键字检查的是is-a关系,也就是能够判断继承关系,a instanceof Object总为true,而直接getClass()并进行相等(==、equals 结果一致)比较只能比较确切类型,而且不一样类加载器加载的同一个类的Class是不一样的。

② Class.isInstance() 方法
Class.isInstance() 方法提供了一种动态地测试对象的途径。

A a = new B();
if( B.class.isInstance(a) ) {
    B b = (B)a;
} // 与 instanceof 的结果一致

可以完成一些使用 instanceof 关键字 没法完成的效果。


5)反射:运行时的类信息

        若是不知道某个对象的确切类型,RTTI能够告诉你,但有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息作一些有用的事。换句话说,在编译时,编译器必须知道全部要经过RTTI来处理的类。

        在传统的编程环境中不太可能出现这种状况。但当咱们置身于更大规模的编程世界中,在许多重要状况下咱们可能须要获取一个指向某个并不在你的程序空间中的对象的引用,事实上,在编译时你的程序根本无法获知这个对象所属的类,好比从磁盘的某个文件中,或者网络链接中获取了一串字节,而且被告知这些字节表明了一个类,那么怎样才能使用这样的类呢?

        Class类与java.lang.reflect类库一块儿对反射的概念进行了支持,该类库包含了FieldMethod以及Construtor类(每一个类都实现了Member接口)。这些类型的对象是由JVM在运行时建立的,用以表示未知类里对应的成员。这样你就可使用Constructor建立新的对象,用get()和set()方法读取和修改与Field对象相关联的字段,用invoke()方法调用与Method对象相关联的方法。另外,还能够调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象数组(详情可查找Class类的JDK文档 )。

Java高级特性——反射(详细方法): https://www.jianshu.com/p/9be58ee20dee

// 使用反射展现类的全部方法, 即便方法是在基类中定义的
package typeinfo;

// Print类的print方法等价于System.Out.Println,方便减小代码量
import static xyz.util.Print.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;

// {Args: typeinfo.ShowMethods}
public class ShowMethods {
    private static String usage = 
        "usage:\n" +
        "ShowMethods qualified.class.name\n" +
        "To show all methods in class or:\n" +
        "ShowMethods qualified.class.name word\n" +
        "To search for methods involving 'word'";
    // 去掉类名前面的包名
    private static Pattern p = Pattern.compile("\\w+\\.");
    public static void main(String[] args) {
        if (args.length < 1) {
            print(usage);
            System.exit(0);
        }
        int lines = 0;
        try {
            Class<?> c = Class.forName(args[0]);
            // 反射得到对象c所属类的方法
            Method[] methods = c.getMethods();
            // 反射得到对象c所属类的构造
            Constructor[] ctors = c.getConstructors();
            if (args.length == 1) {
                for (Method method : methods)
                    print(p.matcher(method.toString()).replaceAll(""));
                for (Constructor ctor : ctors)
                    print(p.matcher(ctor.toString()).replaceAll(""));
            }
        } catch (ClassNotFoundException e) {
            print("No such class: " + e);
        }
    } /*
    public static void main(String[])
    public final void wait() throws InterruptedException
    public final void wait(long,int) throws InterruptedException
    public final native void wait(long) throws InterruptedException
    public boolean equals(Object)
    public String toString()
    public native int hashCode()
    public final native Class getClass()
    public final native void notify()
    public final native void notifyAll()
    public ShowMethods()
    */
}

Java 反射获取私有方法:http://www.javashuo.com/article/p-trszpppu-do.html

一般咱们建立一个类时,它的私有方法在类外是不可见的,可是能够经过反射机制来获取调用。
Method的setAccessible()方法能够绕过私有的安全检查,因此咱们就能够调用类的私有方法。

method.setAccessible(true);//获取私有权限
field.setAccessible(true);

参考文章:https://blog.csdn.net/gd_hacker/article/details/80272159
****

总结: 引用 《 Think in java 4th》

参考文章:http://www.javashuo.com/article/p-fdiereqw-dt.html
在这里插入图片描述

相关文章
相关标签/搜索