Java中的反射机制(详解)

反射机制

     在运行状态中。对于任意一个类,都能够知道这个类的所有的属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能就是反射机制

Class类

1.在Java中,数据类型分为两大类:基本数据类型,引用数据类型;出来基本数据类型的int,double,float等等,Java的其他类型全部都是class(包括interface),也就是引用数据类型,例如:

  • 字符串:String
  • Object
  • Runnable
  • Exception

    进过思考,我们可以得出结论:class(包括interface)的本质是数据类型(Type),无继承关系的数据类型无法赋值:

例如:

Number n = new Double(123,456); //正确,public final class Doubleextends Number
String s = new Double(123,456); //错误,String类与Double类没有任何关系

    而 class 是由 JVM 在执行过程中动态加载的。
    JVM在第一次读取到一种class 类型时,将其加载进内存。

     每加载一种class(类),JVM就为其创建一个class类型的实例,并关联起来。 注意:这里的Class类型是一个名叫Class的class。如下为Class的样子:

public final class Class{
    private Class(){}
} 

    以String类为例,当JVM加载String类时,它首先读取 String.class 文件到内存,然后为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

    这个Class实例是JVM内部创建的,如果我们查看JDK源码,就可以发现Class类的构造方法时private私有的,只有JVM能创建Class实例,我们自己的Java程序是无法创建class实例的。
    所以说,JVM持有的每个Class实例都指向一个数据类型(class或interface):

Java中的反射机制(详解)_第1张图片
    一个Class实例包含了该class的所有完整信息,如下:
Java中的反射机制(详解)_第2张图片
    String类的具体信息:
Java中的反射机制(详解)_第3张图片
    由于JVM为每个加载的 class 创建了对应的 Class 实例,并在实例中保存了该 class 的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个 Class 实例,我们就可以通过这个 Class 实例获取到该实例对应的 class 的所有信息。

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

Java中的反射机制(详解)_第4张图片

    如何获取一个class的class实例?有四个方法:

1.知道具体类的情况下可以使用: 直接通过一个 class 的静态变量 class 获取

Class cls = String.class;

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化

2.通过 Class.forName()传入类的路径获取: 如果知道一个 class 的完整类名,可以通过静态方法 Class.forName() 获取

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

3.通过对象实例instance.getClass()获取: 如果我们有一个实例变量,可以通过该实例变量提供的 getClass() 方法获取

String s = new String("Hello");
Class cls = s.getClass();

4.通过类加载器xxxClassLoader.loadClass()传入类路径获取:

Class clazz = ClassLoader.loadClass("java.lang.String");

    通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行

    因为 Class 实例在JVM中是唯一的,所以,上述方法获取的 Class 实例是同一个实例。可以用== 比较两个 Class 实例:

例如:

package reflect;

//反射 反编译:.class -> .java 通过反射可以直接动态的访问Java对象的属性,方法,构造方法等等
//注意:在运行期间,一个类,只会产生一个class对象,所以下面三种方式判断是否为同一个class对象打印出来的都是true
public class reflect01 {
    //获取class对象的三种方法
    public static void main(String[] args) {
        //第一种方法获取Class对象
        TargetObject to1 = new TargetObject();//这里一new 产生一个TargetObject对象,一个Class类
        Class to1Class = to1.getClass();//获取Class对象
        System.out.println(to1Class.getName());

        //第二种方式获取class对象
        Class to2Class = TargetObject.class;
        System.out.println(to1Class == to2Class);//判断第一种方式获取的Class对象和第二种是否相同

        //第三种方式获取class对象(常用)
        try {
            Class to3class = Class.forName("reflect.TargetObject");//注意这里的字符串必须是真实路径,就是带包名的类路径,包名.类名
            System.out.println(to1Class == to3class);//判断第三种方式是否获取的是同一个class对象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
class TargetObject{
    private String value;
    public TargetObject() {
        value = "JavaGuide";
    }
    public void publicMethod(String s) {
        System.out.println("I love" + s);
    }
    private void privateMethod(){
        System.out.println("value is" + value);
    }
}

    注意一下class实例比较和instanceof的差别:

Integer n = new Integer(123); 
boolean b1 = n instanceof Integer; // true,因为n是Integer类型 
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类 
boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class 
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

    用 instanceof 不但匹配指定类型,还匹配指定类型的子类。而用 == 判断 class 实例可以精确地判断数据类型,但不能作子类型比较。

    通常情况下,我们应该用 instanceof 判断数据类型,因为面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个 class 的时候,我们才使用 == 判断 class 实例。

    因为反射的目的是为了获得某个实例的信息。 因此,当我们拿到某个 Object 实例时,我们可以通过反射获取该 Object 的 class 信息:

void printObjectInfo(Object obj) { 
    Class cls = obj.getClass(); 
}

    要从 Class 实例获取获取的基本信息,参考下面的代码:

package reflect;

// Class 实例获取获取的基本信息
public class reflect09 {
    public static void main(String[] args) {
        printClassInfo("".getClass()); //String类
        System.out.println("====================================");
        printClassInfo(Runnable.class); //Runnable接口
        System.out.println("====================================");
        printClassInfo(java.time.Month.class);//枚举
        System.out.println("====================================");
        printClassInfo(String[].class); //数组类型
        System.out.println("====================================");
        printClassInfo(int.class); //基本类型
    }
    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getClass());
        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());// 判定指定的 Class 对象是否表示一个接口类型
        System.out.println("is enum: " + cls.isEnum()); //当且仅当该类声明为源代码中的枚举时返回
        System.out.println("is array: " + cls.isArray()); //判定此 Class 对象是否表示一个数组类
        System.out.println("is primitive: " + cls.isPrimitive());//判定指定的 Class 对象是否表示一个基本类型
    }
}

    注意到数组(例如 String[] )也是一种 Class ,而且不同于 String.class ,它的类名是[Ljava.lang.String 。此外,JVM为每一种基本类型如int也创建了 Class ,通过 int.class 访问。

    如果获取到了一个 Class 实例,我们就可以通过该 Class 实例来创建对应类型的实例

// 获取String的Class实例: 
Class cls = String.class; 
// 创建一个String实例:
String s = (String) cls.newInstance();//newInstance()创建此 Class 对象所表示的类的一个新实例

动态加载

    JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如:

public class Main { 
    public static void main(String[] args) { 
        if (args.length > 0) { 
            create(args[0]); 
        } 
    }
    static void create(String name) { 
        Person p = new Person(name); 
    } 
}

    当执行 Main.java 时,由于用到了 Main ,因此,JVM首先会把 Main.class 加载到内存。然而,并不会加载 Person.class ,除非程序执行到 create() 方法,JVM发现需要加载 Person 类时,才会首次加载 Person.class 。如果没有执行 create() 方法,那么 Person.class 根本就不会被加载。

    这就是JVM动态加载 class 的特性。

    动态加载 class 的特性对于Java程序非常重要。利用JVM动态加载 class 的特性,我们才能在运行期根据条件加载不同的实现类。

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

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

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

JVM总是动态加载 class ,可以在运行期根据条件来控制加载class

访问字段(Field 成员变量)

    对任意的一个 Object 实例,只要我们获取了它的 Class ,就可以获取它的一切信息。

    Field 成员变量:每个成员变量有类型java.lang.reflect.Field 为我们提供了获取当前对象的成员变量的类型,和重新设值的方法。

​     我们先看看如何通过 Class 实例获取字段信息。 Class 类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名(成员变量)获取某个public的filed(包括父类)
  • Field getDeclaredField(name):根据字段名(成员变量)获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
package reflect;

public class reflect10 {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student10.class;
        //获取public字段"score":
        System.out.println(stdClass.getField("score"));
        //获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        //获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}

class Student10 extends Person {
    public int score;
    public int grade;
}
class Person {
    public String name;
}

    上述代码首先获取 Student 的 Class 实例,然后,分别获取 public 字段、继承的 public 字段以及 private 字段,打印出的 Field 类似:

Java中的反射机制(详解)_第5张图片

一个 Field 对象包含了一个字段的所有信息:

  • getName() :返回字段名称,例如, “name” ;

  • getType() :返回字段类型,也是一个 Class 实例,例如, String.class ;

  • getModifiers() :返回字段的修饰符,它是一个 int ,不同的bit表示不同的含义。

以 String 类的 value 字段为例,它的定义是:

public final class String { 
    private final byte[] value; 
}

我们用反射获取该字段的信息,代码如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Field f = String.class.getDeclaredField("value");
        f.getName(); // "value"
        f.getType(); // class [B 表示byte[]类型
		//作为整数返回由此 Member 所表示的成员或构造方法的 Java 语言修饰符。应该使用 Modifier 类解码整数中的修饰符。 
        int m = f.getModifiers();//返回:底层成员的 Java 语言修饰符
        Modifier.isFinal(m); // true //如果整数参数包括 final 修饰符,则返回 true,否则返回 false。
        Modifier.isPublic(m); // false //如果整数参数包括 public 修饰符,则返回 true,否则返回 false。
        Modifier.isProtected(m); // false //如果整数参数包括 protected 修饰符,则返回 true,否则返回 false。
        Modifier.isPrivate(m); // true //如果整数参数包括 private 修饰符,则返回 true,否则返回 false。
        Modifier.isStatic(m); // false //如果整数参数包括 static 修饰符,则返回 true,否则返回 false。

获取字段值

    利用反射拿到字段的一个 Field 实例只是第一步,我们还可以拿到一个实例对应的该字段的值。

    例如,对于一个 Person 实例,我们可以先拿到 name 字段对应的 Field ,再获取这个实例的name 字段的值:

package reflect;

import java.lang.reflect.Field;

public class reflect11 {
    public static void main(String[] args) throws Exception {
        Object p = new Person11("Xiao Ming");
        Class c = p.getClass(); //获取Person11类的所有信息
        Field f = c.getDeclaredField("name");//获取私有字段,并调用
        f.setAccessible(true);//暴力反射,解除私有限定
        Object value = f.get(p);//获取到字段值
        System.out.println(value);
    }
}
class Person11 {
    private String name;

    public Person11(String name) {
        this.name = name;
    }
}

    上述代码先获取 Class 实例,再获取 Field 实例,然后,用 Field.get(Object) 获取指定实例的指定字段的值。

​    运行代码,如果不出意外,会得到一个 IllegalAccessException ,这是因为 name 被定义为一个 private 字段,正常情况下, Main 类无法访问 Person 类的 private 字段。要修复错误,可以将 private 改为 public ,或者,在调用 Object value = f.get§; 前,先写一句:

 f.setAccessible(true);//暴力反射,解除私有限定

    调用 Field.setAccessible(true) 的意思是,别管这个字段是不是 public一律允许访问。可以试着加上上述语句,再运行代码,就可以打印出 private 字段的值。

    有童鞋会问:如果使用反射可以获取 private 字段的值,那么类的封装还有什么意义?答案是正常情况下,我们总是通过 p.name 来访问 Personname 字段,编译器会根据publicprotectedprivate 决定是否允许访问字段,这样就达到了数据封装的目的。

​ 而反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。

    此外, setAccessible(true) 可能会失败。如果JVM运行期存在 SecurityManager那么它会根据规则进行检查,有可能阻止 setAccessible(true) 。例如,某个 SecurityManager 可能不允许对 javajavax 开头的 package 的类调用 setAccessible(true) ,这样可以保证JVM核心库的安全。

设置字段值

    通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。

    设置字段值是通过 Field.set(Object, Object) 实现的,其中第一个 Object 参数是指定的实例,第二个 Object 参数是待修改的值。示例代码如下:

package reflect;

import java.lang.reflect.Field;

public class reflect12 {
    public static void main(String[] args) throws Exception {
        Person12 p = new Person12("Xiao Ming");
        System.out.println(p.getName());//获取一开始的名字
        Class c = p.getClass();//获取Person12类的所有信息
        Field f = c.getDeclaredField("name");//获取私有字段,并调用
        f.setAccessible(true);//暴力反射,解除私有限定
        f.set(p,"Xiao Hong");//重新更改字段名 set(Object obj,Object value)
        System.out.println(p.getName());
    }
}
class Person12 {
    private String name;

    public Person12(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

    运行上述代码,打印的 name 字段从 Xiao Ming 变成了 Xiao Hong ,说明通过反射可以直接修改字段的值。

Java中的反射机制(详解)_第6张图片

    同样的,修改非 public 字段,需要首先调用 setAccessible(true)

​ Java的反射API提供的 Field 类封装了字段的所有信息:

​ 通过 Class 实例的方法可以获取 Field 实例: getField()getFields()getDeclaredField()getDeclaredFields()

​ 通过Field实例可以获取字段信息: getName()getType()getModifiers()

​ 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true) 来访问非 public 字段。

​ 通过反射读写字段是一种非常规方法,它会破坏对象的封装。

实例:

package reflect;

import java.lang.reflect.Field;

/*
 * 获取成员变量并调用:
 *
 * 1.批量的
 * 		1).Field[] getFields():获取所有的"公有字段"
 * 		2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
 * 2.获取单个的:
 * 		1).public Field getField(String fieldName):获取某个"公有的"字段;
 * 		2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
 *
 * 	 设置字段的值:
 * 		Field --> public void set(Object obj,Object value):
 * 					参数说明:
 * 					1.obj:要设置的字段所在的对象;
 * 					2.value:要为字段设置的值;
 */
public class reflect03 {
    public static void main(String[] args) throws Exception {
        //1.获取Class对象
        Class stuclass = Class.forName("reflect.Student03");
        //2.获取字段
        System.out.println("***********获取所有公有的字段************");
        Field[] fieldArray = stuclass.getFields();
        for(Field f : fieldArray) {
            System.out.println(f);
        }
        System.out.println("***********获取所有字段(包括私有,受保护,默认的)************");
        fieldArray = stuclass.getDeclaredFields();
        for (Field f : fieldArray) {
            System.out.println(f);
        }
        System.out.println("***********获取公有字段,并调用***********");
        Field f = stuclass.getField("name");
        System.out.println(f);
        //获取一个对象(产生student03对象 --> Student03 stu = new Student03)
        Object obj = stuclass.getConstructor().newInstance();
        //为字段设置值
        f.set(obj,"刘德华");
        //验证
        Student03 stu = (Student03) obj;
        System.out.println("验证姓名:" + stu.name);

        System.out.println("***********获取私有字段,并调用************");
        f = stuclass.getDeclaredField("phonenum");
        System.out.println(f);
        f.setAccessible(true);//暴力反射,解除私有限定
        //为字段设置值
        f.set(obj,"1234567899999");
        System.out.println("验证电话:" + stu);
    }
}
class Student03 {
    public String name;
    protected int age;
    char sex;
    private String phonenum;

    public Student03(){

    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ",sex=" + sex + ",phonenum=" + phonenum + "]";
    }
}

调用方法

    我们已经能通过 Class 实例获取所有 Field 对象(成员变量),同样的,可以通过 Class 实例获取所有 Method 信息。 Class 类提供了以下几个方法来获取 Method

  • Method getMethod(name, Class...) :获取某个 publicMethod (包括父类)
  • Method getDeclaredMethod(name, Class...) :获取当前类的某个 Method (不包括父类)
  • Method[] getMethods() :获取所有 publicMethod (包括父类)
  • Method[] getDeclaredMethods() :获取当前类的所有 Method (不包括父类)

    我们来看一下示例代码:

package reflect;

public class reflect13 {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student13.class;//获取Student13所有的信息
        // 获取public方法getScore,参数为String:
        System.out.println(stdClass.getMethod("getScore",String.class));
        // 获取继承的public方法getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取private方法getGrade,参数为int:
        System.out.println(stdClass.getDeclaredMethod("getGrade",int.class));
    }
}
class Student13 extends Person13 {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}
class Person13 {
    public String getName() {
        return "Person";
    }
}

    上述代码首先获取 Student 的 Class 实例,然后,分别获取 public 方法、继承的 public 方法以及 private 方法,打印出的 Method 类似:

Java中的反射机制(详解)_第7张图片
    一个 Method 对象包含一个方法的所有信息:

  • getName() :返回方法名称,例如: “getScore” ;
  • getReturnType() :返回方法返回值类型,也是一个Class实例,例如: String.class ;
  • getParameterTypes() :返回方法的参数类型,是一个Class数组,例如:{String.class, int.class} ;
  • getModifiers() :返回方法的修饰符,它是一个 int ,不同的bit表示不同的含义。

调用方法

    当我们获取到一个 Method 对象时,就可以对它进行调用。我们以下面的代码为例:

String s = "Hello world";
String r = s.substring(6); // "world"

    如果用反射来调用 substring 方法,需要以下代码:

package reflect;

import java.lang.reflect.Method;

public class reflect14 {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring",int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s,6);
        // 打印调用结果
        System.out.println(r);
    }
}

    注意到 substring() 有两个重载方法,我们获取的是 String substring(int) 这个方法。思考一下如何获取 String substring(int, int) 方法。

public String substring(int beginIndex, int endIndex)

返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 beginIndex 处开始,一直到索引 endIndex - 1 处的字符。

    对 Method 实例调用 invoke 就相当于调用该方法, invoke 的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。

调用方法:
 Method --> public Object invoke(Object obj,Object... args):
 参数说明:             
 	obj : 要调用方法的对象;             
 	args:调用方式时所传递的实参;

调用静态方法

    如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke 方法传入的第一个参数永远为 null 。我们以 Integer.parseInt(String) 为例:

package reflect;

import java.lang.reflect.Method;

public class reflect14 {
    public static void main(String[] args) throws Exception {
        // 获取Integer.parseInt(String)方法,参数为String: 
        Method m = Integer.class.getMethod("parseInt", String.class); 
        // 调用该静态方法并获取结果: 
        Integer n = (Integer) m.invoke(null, "12345"); 
        // 打印调用结果: 
        System.out.println(n);
    }
}

    实例:获取Student类的main方法、不要与当前的main方法搞混了;

package reflect;

import java.lang.reflect.Method;

/**
 * 获取Student类的main方法、不要与当前的main方法搞混了
 */
public class reflect05 {
    public static void main(String[] args) throws Exception {
        //1.获取Class对象
        Class stuclass = Class.forName("reflect.Student05");
        //2.获得main方法 (第一个参数:方法名称,第二个参数:方法形参的类型)
        Method methodmain = stuclass.getMethod("main",String[].class);
        //3.调用main方法 (因为主方法是静态的,所以为空,第二个参数因为主方法里面是String数组类型)
        methodmain.invoke(null,(Object)new String[]{"a","b","c"});//这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
    }
}
class Student05{
    public static void main(String[] args) {
        System.out.println("main方法执行了....");
    }
}

调用非public方法

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

public class Main { 
    public static void main(String[] args) throws Exception { 
        Person p = new Person(); 
        Method m = p.getClass().getDeclaredMethod("setName", String.class); 
        m.setAccessible(true); 
        m.invoke(p, "Bob"); 
        System.out.println(p.name); 
    } 
}
class Person { 
    String name; 
    private void setName(String name) { 
        this.name = name; 
    } 
}

    此外, setAccessible(true) 可能会失败。如果JVM运行期存在 SecurityManager那么它会根据规则进行检查,有可能阻止 setAccessible(true) 。例如,某个 SecurityManager 可能不允许对 javajavax 开头的 package 的类调用 setAccessible(true) ,这样可以保证JVM核心库的安全。

实例:

package reflect;
import java.lang.reflect.Method;

/*
 * 获取成员方法并调用:
 *
 * 1.批量的:
 * 		public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
 * 		public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
 * 2.获取单个的:
 * 		public Method getMethod(String name,Class... parameterTypes):
 * 					参数:
 * 						name : 方法名;
 * 						Class ... : 形参的Class类型对象
 * 		public Method getDeclaredMethod(String name,Class... parameterTypes)
 *
 * 	 调用方法:
 * 		Method --> public Object invoke(Object obj,Object... args):
 * 					参数说明:
 * 					obj : 要调用方法的对象;
 * 					args:调用方式时所传递的实参;
):
 */
public class reflect04 {
    public static void main(String[] args) throws Exception {
        //1.获取Class对象
        Class stuclass = Class.forName("reflect.Student04");
        //2.获取所有公有方法
        System.out.println("************获取所有的公有方法*************");
        Method[] methodArray = stuclass.getMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }
        System.out.println("*************获取所有的方法,包括私有的**************");
        methodArray = stuclass.getDeclaredMethods();
        for (Method m : methodArray) {
            System.out.println(m);
        }
        System.out.println("**************获取公有的show01()方法***************");
        Method m = stuclass.getMethod("show01",String.class);
        System.out.println(m);
        //实例化一个Student对象
        Object obj = stuclass.getDeclaredConstructor().newInstance();
        m.invoke(obj,"刘德华");

        System.out.println("**************获取公有的show04()方法**************");
        m = stuclass.getDeclaredMethod("show04",int.class);
        System.out.println(m);
        //实例化
        m.setAccessible(true);
        Object result = m.invoke(obj,20);
        System.out.println("返回值:" + result);
    }

}
class Student04{
    //***********成员方法*************
    public void show01(String s) {
        System.out.println("调用了公有的String参数的show01(): s = " + s);
    }
    protected void show02(){
        System.out.println("调用了收保护的无参的show02()");
    }
    void show03() {
        System.out.println("调用了默认的无参的show03()");
    }
    private String show04(int age) {
        System.out.println("调用了私有的并且有返回值的int参数的show04(): age=" + age);
        return "abcd";
    }
}

多态

    我们来考察这样一种情况:一个 Person 类定义了 hello() 方法,并且它的子类 Student 也覆写了 hello() 方法,那么,从 Person.class 获取的 Method ,作用于 Student 实例时,调用的方法到底是哪个?

package reflect;

import java.lang.reflect.Method;

//一个 Person 类定义了 hello()方法,
// 并且它的子类 Student 也覆写了 hello()方法,
// 那么,从 Person.class 获取的 Method ,作用于 Student实例时,调用的方法到底是哪个?
//多态
public class reflect15 {
    public static void main(String[] args) throws Exception {
        //获取Person的hello方法:
        Method h = Person.class.getMethod("hello");
        //对Student15实例调用hello方法:
        h.invoke(new Student());
    }
}
class Person15 {
    public void hello() {
        System.out.println("Person:hello");
    }
}
class Student15 extends Person13 {
    public void hello(){
        System.out.println("Student:hello");
    }
}

    运行上述代码,发现打印出的是 Student:hello ,因此,使用反射调用方法时,仍然**遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。**上述的反射代码:

Method m = Person.class.getMethod("hello"); 
m.invoke(new Student());

实际上相当于:

Person p = new Student(); 
p.hello(); 

​ Java的反射API提供的Method对象封装了方法的所有信息:

​ 通过 Class 实例的方法可以获取 Method 实例: getMethod() , getMethods() , getDeclaredMethod() , getDeclaredMethods() ;

​ 通过 Method 实例可以获取方法信息: getName() , getReturnType() , getParameterTypes() , getModifiers() ;

​ 通过 Method 实例可以调用某个对象的方法: Object invoke(Object instance, Object… parameters) ;

​ 通过设置 setAccessible(true) 来访问非 public 方法;

​ 通过反射调用方法时,仍然遵循多态原则。

调用构造方法

    我们通常使用 new 操作符创建新的实例

Person p = new Person();

​ 如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();

    调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

    为了调用任意的构造方法Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。**Constructor对象和Method非常类似,**不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

package reflect;

import java.lang.reflect.Constructor;

public class reflect16 {
    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实例获取Constructor的方法如下:

  • getConstructor(Class…) :获取某个 public 的 Constructor ;
  • getDeclaredConstructor(Class…) :获取某个 Constructor ;
  • getConstructors() :获取所有 public 的 Constructor ;
  • getDeclaredConstructors() :获取所有 Constructor 。

注意 Constructor 总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

    调用非 public 的 Constructor 时,必须首先通过 setAccessible(true) 设置允许访问。setAccessible(true) 可能会失败。

​ Constructor 对象封装了构造方法的所有信息;

​ 通过 Class 实例的方法可以获取 Constructor 实例: getConstructor() , getConstructors() , getDeclaredConstructor() , getDeclaredConstructors() ;

​ 通过 Constructor 实例可以创建一个实例对象: newInstance(Object… parameters) ; 通过设置 setAccessible(true) 来访问非 public 构造方法。

实例:

package reflect;

import java.lang.reflect.Constructor;

/*
 * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
 *
 * 1.获取构造方法:
 * 		1).批量的方法:
 * 			public Constructor[] getConstructors():所有"公有的"构造方法
            public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
 * 		2).获取单个的方法,并调用:
 * 			public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
 * 			public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
 * 		3).调用构造方法:
 * 			Constructor-->newInstance(Object... initargs)
 */
public class reflect02 {
    public static void main(String[] args) throws Exception {
        //1.加载class对象
        Class clazz = Class.forName("reflect.Student0_");

        //2.获取所有的公有构造函数
        System.out.println("**************所有公有构造函数***************");
        Constructor[] conArray = clazz.getConstructors();
        for(Constructor c : conArray) {
            System.out.println(c);
        }

        System.out.println("**************所有的构造方法(包括:私有,受保护,默认,公有)**************");
        conArray = clazz.getDeclaredConstructors();
        for (Constructor c : conArray) {
            System.out.println(c);
        }

        System.out.println("**************获取公有、无参的构造方法***************");
        Constructor con = clazz.getConstructor(null);
        //这里注意null指的是类型参数是空,是参数的类型,不写也可以
        System.out.println("con = " + con);

        //调用构造函数
        Object obj = con.newInstance();//打印公有,无参构造函数

        System.out.println("****************获取私有构造函数,并调用*****************");
        con = clazz.getDeclaredConstructor(char.class);
        System.out.println(con);
        //调用构造函数
        con.setAccessible(true);//暴力访问(忽略访问修饰符)
        obj = con.newInstance('女');
    }
}
class Student{
    //构造方法
    //默认的构造方法
    Student(String str) {
        System.out.println("默认的构造方法s = " + str);
    }
    //无参构造函数
    public Student(){
        System.out.println("公有,无参构造函数");
    }
    //一个参数的构造函数
    public Student(char name){
        System.out.println("姓名:" + name);
    }
    //多个参数的构造函数
    public Student(String name, int age){
        System.out.println("姓名:" + name + "年龄:" + age);
    }
    //受保护的构造函数
    protected Student(boolean n){
        System.out.println("受保护的构造函数 n = " + n);
    }
    //私有构造函数
    private Student(int age) {
        System.out.println("私有的构造函数 年龄:" + age);
    }
}

获取继承关系

    当我们获取到某个 Class 对象时,实际上就获取到了一个类的类型:

Class cls = String.class; // 获取到String的Class

    还可以用实例的 getClass() 方法获取:

String s = ""; 
Class cls = s.getClass(); // s是String,因此获取到String的Class

    最后一种获取 Class 的方法是通过 Class.forName("") ,传入 Class 的完整类名获取:

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

    这三种方式获取的 Class 实例都是同一个实例因为JVM对每个加载的 Class 只创建一个Class 实例来表示它的类型。

获取父类的Class

package reflect;

public class reflect17 {
    public static void main(String[] args) {
        Class i = Integer.class;
        //获取 Integer类的父类
        Class n = i.getSuperclass();
        System.out.println(n);
        //获取 Integer类的父类 的父类
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}

    运行上述代码,可以看到, Integer 的父类类型是 Number , Number 的父类是 Object , Object 的父类是 null 。除 Object 外,其他任何非 interface 的 Class 都必定存在一个父类类型。

Java中的反射机制(详解)_第8张图片

获取interface

    由于一个类可能实现一个或多个接口,通过 Class 我们就可以查询到实现的接口类型。例如,查询 Integer 实现的接口:

package reflect;

//获取interface
public class reflect18 {
    public static void main(String[] args) {
        Class s = Integer.class;//获取Integer类的信息
        Class[] is = s.getInterfaces(); //获取interface
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

    运行上述代码可知, Integer 实现的接口有:java.lang.Comparable ;

    要特别注意: getInterfaces() 只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:

//获取父类的interface
public class reflect18 {
    public static void main(String[] args) {
        Class s = Integer.class.getSuperclass();//获取Integer类的信息
        Class[] is = s.getInterfaces(); //获取interface
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

    Integer 的父类是 Number , Number 实现的接口是 java.io.Serializable 。

// java.io.FilterInputStream,因为DataInputStream继承自FilterInputStream
System.out.println(java.io.DataInputStream.class.getSuperclass());
// null,对接口调用 getSuperclass()总是返回null,获取接口的父接口要用getInterfaces()
System.out.println(java.io.Closeable.class.getSuperclass());

    如果一个类没有实现任何 interface ,那么 getInterfaces() 返回空数组。

继承关系

    当我们判断一个实例是否是某个类型时,正常情况下,使用 instanceof 操作符:

Object n = Integer.valueOf(123); 
boolean isDouble = n instanceof Double; // false 
boolean isInteger = n instanceof Integer; // true 
boolean isNumber = n instanceof Number; // true 
boolean isSerializable = n instanceof java.io.Serializable; // true

    如果是两个 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

​ 通过 Class 对象可以获取继承关系:

  • Class getSuperclass():获取父类类型;

  • Class[] getInterfaces() :获取当前类实现的所有接口。

通过 Class 对象的 isAssignableFrom() 方法可以判断一个向上转型是否可以实现。

你可能感兴趣的:(高级应用篇,java,网络,开发语言)