java中的反射机制,以及如何通过反射获取一个类的构造方法 ,成员变量,方法,详细。。

时间:2021-12-09 19:41:09

首先先说一下类的加载,流程。只有明确了类这个对象的存在才可以更好的理解反射的原因,以及反射的机制。

一、  类的加载

  • 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
  • 加载
    • 就是指将class文件读入内存,并为之创建一个Class对象--->  而这个对象就是我们反射中将要使用的对象。
    • 任何类被使用时系统都会建立一个Class对象。
  • 连接
    • 验证  是否有正确的内部结构,并和其他类协调一致;
    • 准备  负责为类的静态成员分配内存,并设置默认初始化值;
    • 解析  将类的二进制数据中的符号引用替换为直接引用。
  • 初始化 就是常规的一个类的的初始化步骤,有静态先静态。。。。忘记的自己回去看~

二、类初始化时机

  · 创建类的实例

  ·  访问类的静态变量,或者为静态变量赋值

      · 调用类的静态方法

      · 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象

· 初始化某个类的子类

      · 直接使用java.exe命令来运行某个主类

三.类加载器

   l  类加载器

  • 负责将.class文件加载到内在中,并为之生成对应的Class对象。
  • 虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。

   l  类加载器的组成

  • Bootstrap ClassLoader 根类加载器
  • Extension ClassLoader 扩展类加载器
  • Sysetm ClassLoader 系统类加载器

四、类加载器的作用

  1. Bootstrap ClassLoader 根类加载器

    也被称为引导类加载器,负责Java核心类的加载

    比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

  2.Extension ClassLoader 扩展类加载器

    负责JRE的扩展目录中jar包的加载。

    在JDK中JRE的lib目录下ext目录

  3. Sysetm ClassLoader 系统类加载器  (我们自己定义的类一般在这里)  

    负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

//------------------------------------------------代码的分割线

//下面首先介绍的是第一步如何通过Class的方法来获取指定类的class对象,没有该对象就无从反射谈起

 /*
2 * 反射:就是通过class文件对象,去使用该文件的成员变量,构造方法,成员方法。
3 * 在以前的时候一般都是通过创建对象然后通过对象取调用该类的方法
*
* Class类:通过Class来得到被加载类的三个属性同时每一个属性又通过对应的类进行创建对象来调用方法来使用被加载类的方法
* 成员变量: Field
* 构造方法: Constructor
* 成员方法: Method
*
* 获取class文件对象的方式:
* A:object类的getClass()方法
* B:数据类的静态属性class(只要是数据类型都有这个方法)
* C:class类中的静态方法
* public static Class<?> forName(String className)
* String className:此处需要的是类的带包名的全路径否则报错,java.lang.ClassNotFoundException: Person
*
*
* 实际开发的时候一般使用的是第三种。因为该方法使用的是配置文件,可以在配置文件中直接进行配置,可以让该字符串变化。
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 方式1
Person p = new Person();
Class c = p.getClass(); Person p2 = new Person();
Class c2 = p.getClass(); System.out.println(p == p2);
System.out.println(c == c2); // 方式2
Class c3 = Person.class; // 方式3
//反射机制.Person 这里很容易出错,我自己写的时候就经常不小心写错。写错了还不好检查。
/*
* 由于写一个类的全路径经常会出错,所以尽量不自己手写,
* 1.点开那个类里面有一个copy qualifield Name的选项,
* 2.直接在外面写通过辅助自动补齐手段进行检测,在需要的那个类里面找到package。
*/
Class c4 = Class.forName("反射机制.Person");
System.out.println(c == c4); }
}

//---------------------------------------------------------

//下面给出上面再main方法中使用的person类,相当简单的一个类

//在后面会陆续使用该类,进行构造方法,字段,以及成员方法的调用,后面就不再给出该类,该类一直代表person

 public class Person {
private String name;
int age;
public String address; public Person() {
} private Person(String name) {
this.name = name;
} Person(String name, int age) {
this.name = name;
this.age = age;
} public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
} public void show() {
System.out.println("show");
} public void method(String s) {
System.out.println("method " + s);
} public String getString(String s, int i) {
return s + "---" + i;
} private void function() {
System.out.println("function");
} @Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}

//---------------------------------------------

//通过反射获取一个类的构造方法并进行使用。包括如何访问Person类的公共无参构造方法,公共有参构造方法,以及私有带参构造

 import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; /*
* 通过反射机制获取构造方法
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 获取字节码文件对象,此处仍然是上面的person类.
Class c = Class.forName("反射机制.Person"); // 获取构造方法
// public Constructor [] getConstructors() => 公共构造方法所有的
// public Constructor [] getDeclaredConstructors() => 所有的构造方法 ,一旦添加Declared单词修饰获取一般是全部的声明包括自有private,protected。 // Constructor[] cons = c.getConstructors();
Constructor[] cons = c.getDeclaredConstructors(); //这种方法虽然获取到了所有的构造方法,但是没什么作用,因此需要使用下面的方法获取单个的构造方法
for (Constructor con : cons) {
System.out.println(con);
} // 获取单个构造方法,
// public Constructor getConstructor(Class<?>... parameterTypes)
// 该方法的参数表示的是:你要获取的构造方法的构造参数的个数以及class字节码文件对象 Constructor con = c.getConstructor(); // 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
// public T newInstance(Object... initargs) Object obj = con.newInstance(); // 此处创建的是Person类的对象实例, // 此处创建的是Person类的对象实例,由于是无参构造因此不需要参数。
System.out.println(obj); // Person [name=null, age=0, address=null]
/*
* 下面的代码是:通过反射区获取带参数的构造方法
*/
// 获取指定参数的构造函数
// public Constructor getConstructor(Class<?>... parameterTypes)
/*
* 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 parameterTypes 参数是 Class 对象的一个数组,
*/
Constructor con2 =c.getConstructor(String.class, int.class,
String.class); // 通过构造方法创建对象
// ublic T newInstance(Object... initargs)
Object obj2 = con.newInstance("java", 22, "武汉");
System.out.println(obj2);
//----------------------------------
//通过反射获取私有构造方法并使用。
// 获取私有构造器 ---> getDeclaredConstructor
// 如果直接使用getConstructor则会导致没有该方法的异常NoSuchMethodException
Constructor con3 = c.getDeclaredConstructor(String.class); // 私有方法直接访问:--->报错IllegalAccessException非法访问异常
/*
* 暴力访问 public void setAccessible(boolean flag) 将此对象的 accessible
* 标志设置为指示的布尔值。 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。 值为 false
* 则指示反射的对象应该实施 Java 语言访问检查。
*/ con3.setAccessible(true); // 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
Object obj3 = con.newInstance("android");
System.out.println(obj3); }
}

//-------------------------------------------------

//下面的代码是如何通过反射获取一个类的公有成员变量私有成员变量,并进行赋值显示

 import java.lang.reflect.Constructor;
import java.lang.reflect.Field; /*
* 通过反射获取成员变量并使用
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 获取字节码文件class对象
Class c = Class.forName("反射机制.Person"); // 获取所有的成员变量,
// Field[] fields = c.getFields(); 获取所有的公共成员变量,放在数组中不易操作。
// Field[] fields = c.getDeclaredFields(); 获取所有成员变量,放在数组中不易操作。 // 通过无参构造方法获取对象,有了对象才好对成员变量进行赋值,即使在反射中成员变量仍然是属于对象的。
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//System.out.println(obj); // 获取单个的成员变量,指定成员变量的名字---address
Field addressField = c.getField("address"); // 对于成员变量,一般是通过对象 : obj.address = "777"进行赋值,
// public void set(Object obj, Object value)
// 上面方法的意思是将指定对象变量obj上此 Field 对象addressField表示的字段设置为指定的新值value 。 addressField.set(obj, "java"); // 给obj对象的addressField字段设置值为value:java,这个地方很拗口,与java基本思路背道而驰。
System.out.println(obj); // 获取name并对其赋值,name是私有成员变量。
Field nameField = c.getDeclaredField("name");
//IllegalAccessException,暴力访问
nameField.setAccessible(true);
nameField.set(obj, "android");
System.out.println(obj); // 获取age并对其赋值,age是私有成员变量。
Field ageField = c.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj, 25);
System.out.println(obj); }
}

//-------------------------------------------

// 获取类中的所有方法,公共方法,私有方法。并进行使用。

 import java.lang.reflect.Constructor;
import java.lang.reflect.Method; /*
* 获取类中的所有方法
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception { // 获取字节码class对象
Class c = Class.forName("反射机制.Person"); // 获取所有方法
// Method[] methods = c.getMethods(); //该方法获取获取本身的以及父亲的所有公共方法,此处无意义。
Method[] methods5 = c.getDeclaredMethods(); // 获取自己的所有方法,加入了Declared,在实际使用中可以不管你需要的东西的公共还是私有直接使用Declared修饰的方法
// for (Method method : methods) {
// System.out.println(method);
// } // 获取对象,由于其它构造麻烦一些,在获取对象时一律使用无参构造。
Constructor con = c.getConstructor();
Object obj = con.newInstance(); // 获取单个的指定方法
// public Method getMethod(String name, Class<?>... parameterTypes)
// 第一个参数表示的是方法名,第二个参数表示的方法的参数class类型。
     // show方法是公共,无形参的方法public void show() { }
Method method1 = c.getDeclaredMethod("show");
// public Object invoke(Object obj, Object... args)
30 // 返回值是object接受,第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数(形参)。
31 // 没有返回值就不写,

method1.invoke(obj); // 调用obj对象的mehod1方法, // 带一个参数方法无返回值的方法调用, public void method(String s){}
Method method2 = c.getMethod("method", String.class);
method2.invoke(obj, "java"); // 带两个形参,有返回值的方法的调用public void method(String s),
Method method3 = c.getMethod("getString", String.class,
int.class);
Object objStr = method3.invoke(obj, "java", 50);
System.out.println("Method3" + objStr); //私有方法
Method method4 = c.getDeclaredMethod("function");
method4.setAccessible(true); //暴力访问
method4.invoke(obj); }
}