凉城旧巷
Python从入门到自闭,Java从自闭到放弃,数据库从删库到跑路,Linux从rm -rf到完犊子!!!

反射

一、反射

通过Class实例获取class信息的方法称为反射(Reflection)

  • JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息

  • 获取一个class对应的Class实例后,就可以获取该class的所有信息;

二、Class类及其实例

1、Class类

Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识,即所谓的RTTI。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
简单解释上面一段话:
1、Class类也是类的一种,只是名字和class关键字高度相似。Java是大小写敏感的语言。
2、Class类的对象内容是你创建的类的类型信息,比如你创建一个shapes类,那么,Java会生成一个内容是shapes的Class类的对象。
3、Class类的对象不能像普通类一样,以 new shapes() 的方式创建,它的对象只能由JVM创建,因为这个类没有public构造函数。
4、Class类的作用是运行时提供或获得某个对象的类型信息。

2、获取Class实例

1、Class类的静态方法forName

// 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
Class cls = Class.forName( String className);

Class cls = Class.forName("java.lang.String");

2、对象的getClass()方法

// 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取 
Student student = new Student();
 Class<? extends Student> aClass1 = student.getClass();

3、使用类名加.class

// 直接通过一个class的静态变量class获取
Class classes = Student.class;

3、获取基本信息

public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
        System.out.println("---------------------------------------------");
    }
}

三、Class实例操作class的成员变量

public class Student{
    private String name;
    private int age;
    
    public Student(){}
    
    public Student(String name) {
        this.name = name;
    }
}


public class Main{
    public static void main(String[] args){
        ...
    }
}

1、获取类的变量

注意:

getField(name)getDeclaredField(name)中 name 可能不存在,源码中用了throws来抛出可能出现的异常。所以在使用时,必须用try...catch...来捕获异常

Field getField(name)	   // 根据字段名获取包括父类字段在内的某个public的field()
Field getDeclaredField(name)		// 根据字段名获取当前类的某个field(不包括父类)
Field[] getFields()			// 获取包括父类在内的所有public的field,返回值为数组
Field[] getDeclaredFields()			// 获取当前类的所有field(不包括父类),返回值为数组
Class cls = Student.class;
try{
    Field f = cls.getDeclaredField("name");
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}


2、获取变量的属性

getName()       //返回字段名称
getType()			//返回字段类型,也是一个Class实例
getModifiers()		// 返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
Class cls = Student.class;
try{
    Field f = cls.getDeclaredField("name");
    f.getName();    // name
    f.getType();		// String
    f.getModifiers();
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}

3、获取、修改变量的值

get(obj);   // 获取obj对象xx变量值
set(obj, value);   // 设置obj对象的xx变量的值为value
// 获取s对象name的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
try{
    Field f = c.getDeclaredField("name");
    f.setAccessible(true);     // 由于name为私有变量,外部无法访问,通过setAccessible(true)可以允许访问任何权限变量
    Object value = f.get(s);    // 获取值
    System.out.println(value); // "Xiao Ming"
}catch(Exception e){
    if (e.equals('NoSuch...')){
        ......
    }
}

 
// 设置s对象的age变量的值
Object s = new Student("Xiao Ming");
Class c = s.getClass();
Field f = c.getDeclaredField("age");
f.setAccessible(true);
f.set(s, 18)    // 设置值

四、Class实例操作class的方法

public class Student{
    private String name;
    private int age;
    
    public Student(){}
    
    public Student(String name) {
        this.name = name;
    }
    
    public void setName(String name){
        this.name = name
    }
    
    public static void add(int a){
        a += 1;
    }
    
    private void check(){
        ...
    }
    
}


public class Main{
    public static void main(String[] args){
        ...
    }
}

1、获取类的方法

注意:

getMethod(name)getDeclaredMethod(name)中 name 可能不存在,源码中用了throws来抛出可能出现的异常,所以在使用时,必须用try...catch...来捕获异常

Method getMethod(name, Class...)		// 获取包括父类方法在内的某个public的Method
Method getDeclaredMethod(name, Class...)		// 获取当前类的某个Method(不包括父类)
Method[] getMethods()		// 获取包括父类方法在内的所有public的Method,返回一个数组
Method[] getDeclaredMethods()		// 获取当前类的所有Method(不包括父类),返回一个数组
Class cls = Student.class;
try {
    
    Method method = cls1.getDeclaredMethod("setName", String.class);
}catch (Exception e){
    ...
}

2、获取方法的属性

getName()		// 返回方法名称
getReturnType()     // 返回方法返回值类型,也是一个Class实例
getParameterTypes()     // 返回方法的参数类型,是一个Class数组
getModifiers()		// 返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
Class cls = Student.class;
try {
    // 获取setName方法,参数为String
    Method method = cls1.getDeclaredMethod("setName", String.class);
    System.out.println(method.getName());    //  setName
}catch (Exception e){
    ...
}

3、调用方法

invoke()     // 调用执行方法

(1)调用public非静态方法

invoke(obj, 参数)   // 对Method实例调用invoke就相当于调用该方法, 
    // invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 获取setName方法,参数为String
    Method method = cls1.getDeclaredMethod("setName", String.class);
    method.invoke(s, "john")
}catch (Exception e){
    ...
}

(2)调用public静态方法

调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 获取setName方法,参数为String
    Method method = cls1.getDeclaredMethod("add", int.class);
    method.invoke(null, 5)
}catch (Exception e){
    ...
}

(3)调用 非public 方法

对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用。

注意:

  • setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对javajavax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全
Student s = new Student("tom", 18)
Class cls = s.getClass();
try {
    // 获取setName方法,参数为String
    Method method = cls1.getDeclaredMethod("check");
    Method.setAccessible(true)
    method.invoke(s)
}catch (Exception e){
    ...
}

4、多态

使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

即先从子类中找有无方法,没有再去父类

五、调用构造方法

注意:

getConstructor(name)getDeclaredConstructor(name)中 name 可能不存在,源码中用了throws来抛出可能出现的异常,所以在使用时,必须用try...catch...来捕获异常

getConstructor(Class...)		// 获取某个public的Constructor
getDeclaredConstructor(Class...)		// 获取某个Constructor
getConstructors()			// 获取所有public的Constructor
getDeclaredConstructors()			// 获取所有Constructor
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int)
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法,实例化产生实例
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

六、获取继承关系

Class i = Integer.class;
// 获取class的父类
Class n = i.getSuperclass();

// 获取class的所有接口
Class[] is = s.getInterfaces();

// 通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
posted on 2020-01-13 11:12  凉城旧巷  阅读(394)  评论(0编辑  收藏  举报