反射:在运行时期,通过反射可以动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段等信息);
类的加载过程(加载机制):
1. 编码
2. 类的加载器去加载(将字节码文件加载到JVM中,给每个类创建字节码对象)
3. 初始化
4. 运行期
1. Class实例
其实就是一些类型(类 接口 数组 基本数据类型 void)的字节码对象
Class
类的实例表示正在运行的 Java 应用程序中的类和接口(字节码对象);
枚举是一种类,注释(指的是注解Annotation)是一种接口;
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class
对象;
基本的 Java 类型(boolean
、byte
、char
、short
、int
、long
、float
和 double
)和关键字 void
也表示为 Class
对象;
注意:
1、 Class类和它的实例的产生: Class的实例是已经存在的类型,所以不能够直接new一个Class对象出来,而通过已知的类型和Class来获得
2、同一种类型不管通过什么方式得到Class的实例都是相等的;一个类型的字节码对象只有一份!
线程同步:同步监听对象字节码对象来充当同步监听 始终保证都共享的是同一个同步监听对象
3、Class的实例就看成是Java中我们学过的所有的数据类型在JVM中存在的一种状态(字节码对象)
String.class int.class List.class int[].class int[][].class
在 Java API 中,获取 Class 类对象有三种方法:
第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。
Class clz = Class.forName("java.lang.String");
第二种,使用 .class 方法。
这种方法只适合在编译前就知道操作的 Class。
Class clz = String.class;
第三种,使用类对象的 getClass() 方法。
String str = new String("Hello"); Class clz = str.getClass();
Class实例 的其他获得方式的一些注意点
public static void main(String[] args) throws Exception { //1. 获取接口的字节码对象的两种方式 Class list1 = Class.forName("java.util.List"); Class list2 = List.class; System.out.println(list1 == list2); //true //2. 获取数组的字节码对象的两种方式 int[] a = {1,5,8}; Class a1 = int[].class; Class a2 = a.getClass(); System.out.println(a1 == a2); //true int[] b = {}; Class b1 = b.getClass(); System.out.println(a1 == b1); //true String[] s = {}; Class s1 = s.getClass(); String[][] ss = {}; Class s2 = ss.getClass(); System.out.println(s1 == s2); //false // 综上:具有相同元素类型 和 维数的数组都共享该 Class 对象; //3. 获取基本数据类型、包装类型 的字节码对象的几种方式 Class int1 = int.class; Class int2 = Integer.TYPE; //获取 包装类型 的字节码对象的三种方式 Class intc1 = Class.forName("java.lang.Integer"); Class intc2 = Integer.class; Class intc3 = new Integer(10).getClass(); /* 注意点: Integer 是 Object Type 对象类型, int 是 Primitive Type 原始类型 Integer 有成员变量,有成员方法,int 无成员变量,无成员方法 Integer.class 与 int.class 不同 Integer.TYPE 与 int.class 相同 */ System.out.println(int1 == int2); //true System.out.println(intc1 == int2); //false System.out.println(intc1 == intc3); //true /* 4. void 和基本数据类型只能通过类型名称获取该字节码对象 还可以通过其对应的包装类的TYPE属性获取其字节码对象。 * eg 获取int的字节码对象 * int.class或者 Integer.TYPE * 获取void的字节码对象 * void .class 或者 Void.TYPE * 其中: Void类是一个不可实例化的占位符类,它持有对标识Java关键字void的Class对象的引用。 并且本身的构造函数为private,即该类是不可以实例化的 底层代码: public final class Void { public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void"); private Void() {} } */ Class v1 = void.class; Class v2 = Void.TYPE; Class vc1 = Void.class; Class vc2 = Class.forName("java.lang.Void"); System.out.println(v1 == v2); //true System.out.println(v1 == vc2); //false }
2. Constructor类是什么?
Constructor是一个类,位于java.lang.reflect包下。
在Java反射中 Constructor类描述的是 类的构造方法信息
如何获取Constructor类对象?
- getConstructors(): 获取一个类中所有非私有化的构造方法
- getConstructor(): 获取非私有 无参的构造方法
- getConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型
- getDeclaredConstructors(): 获取类中所有的构造方法(public、protected、default、private)
- getDeclaredConstructor(): 获取私有化的无参构造方法
- getDeclaredConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型
package com.ganshao.Test2; import java.lang.reflect.Constructor; //Person 类 class Person { private String name; public Person(String name) { this.name = name; } private Person() {} } //Student 类 class Student { public Student(String name) {} } public class Test1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException { Class c1 = Class.forName("com.ganshao.Test2.Person");//获取Person类的字节码对象 /* * public Constructor<T> getConstructor(Class<?>... parameterTypes) * 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。 * */ Constructor pc1= c1.getConstructor(String.class); //不可以去获取私有化的构造方法 Constructor con1 = c1.getDeclaredConstructor(); //可以去获取私有化的无参构造方法 System.out.println(pc1); //public com.ganshao.Test2.Person(java.lang.String) System.out.println(con1); //private com.ganshao.Test2.Person() System.out.println("---"); //获取一个类中所有非私有化的构造方法 Constructor[] cons = c1.getConstructors(); for (Constructor constructor : cons) { System.out.println(constructor); } System.out.println("---"); //获取一个类中所有的构造方法(跟权限无关) Constructor[] cons1 = c1.getDeclaredConstructors(); //注意末尾加了 "s" for (Constructor constructor : cons1) { System.out.println(constructor); } System.out.println("---"); Constructor s1 = Student.class.getDeclaredConstructor(); System.out.println(s1); //报错:NoSuchMethodException,因为没有私有的构造方法 } }
3. 创建对象五种的方式
* 1. new 构造器 ,可以创建有参数对象、无参数对象,不能调用priavte的构造方法
* 2. 字节码对象.newInstance() 只能够调用 无参数的 非私有的 构造方法, 故当类的 无参的构造方法 被私有化就会报错。
* 3. 通过Constructor的对象.newInstance(Object...init)获得 //可以创建有参数对象、无参数对象,通过setAccessible(true)的方式去调用priavte的构造方法
* 4. 使用clone方法,我们需要先实现Cloneable接口 例如: Employee emp4 = (Employee) emp3.clone();
* 5. 使用反序列化 (这个在io 流中说过)
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); Employee emp5 = (Employee) in.readObject();
注意:方法2.3是通过反射创建类对象的
AccessibleObject 类 是Method、Field、Constructor类的基类,
它提供了标记反射对象的能力,以抑制在使用时使用默认Java语言访问控制检查,从而能够任意调用被私有化保护的方法、域和构造函数;
提供了:void setAccessible(boolean flag) 方法
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射*的对象应该实施 Java 语言访问检查。
//Student 类 class Student { private String name; public Student(String name) {this.name = name;} private Student() {} @Override public String toString() { return "Student [name=" + name + "]"; } } public class Test1 { public static void main(String[] args) throws Exception { //2. 字节码对象.newInstance() // Class c = Student.class; // Student s = (Student)(c.newInstance()); // System.out.println(s); // 此时 如果 Student()构造方法 访问权限设置为 private 会报错。 //3. 通过Constructor方法获得 Class c = Student.class; Constructor constructor = c.getDeclaredConstructor(); constructor.setAccessible(true); //让方法的private修饰符在这一次创建对象时失效 Student s2 =(Student)(constructor.newInstance()); System.out.println(s2); //Student [name=null] Constructor constructor1 = c.getConstructor(String.class); Student s3 =(Student)(constructor1.newInstance("张三")); System.out.println(s3); //Student [name=张三] } }
4. 通过反射获取类属性、方法、构造器
我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。
而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。
其中 :注意末尾加不加 “s” ,加s 获取指定的, 不加 s 获取所有的。 getMethod(可以有参数) getMethods() 。
getMethods方式返回的自己以及父类的所有的公共的方法
getDeclaredMethod 返回自己的所有方法 不包含基类中的方法
getMethod("方法的对象",形参列表的字节码对象)
5. 通过反射执行方法
获得字节码对象-->获取方法对象 -->通过方法的对象.invoke() 去执行方法
Object invoke(Object obj, Object... args)
返回值类型Object 参数:该方法所属的对象, [...]多个实际参数
package 反射的三种用法; interface A{} @SuppressWarnings(value = { "123" })/*压缩警告*/ public class Person implements A { private String name; private int age; @Deprecated/*表示定义的方法或者字段或者类过时了*/ public int getAge() { return age; } public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person() { } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } //私有的方法 private void fun(String name){ System.out.println("你好:"+name); } //静态的方法 public static void fun1(int a){ System.out.println("你好:"+a); } }
public static void main(String[] args) throws Exception{ //1. 反射调用方法 // 1. 通过反射调用 普通方法 比如:toString()方法 Class c1 = Person.class; //获得字节码对象 Method m1 = c1.getMethod("toString");// 获取方法对象 Object obj = m1.invoke(new Person());//通过方法的对象.invoke() 去执行方法 System.out.println(obj); //Person [name=null, age=0] // 2. 通过反射调用 私有的方法 Method m2 = c1.getDeclaredMethod("fun", String.class); m2.setAccessible(true); // 若没有setAccessible设置这一次的访问权限。 会报错:IllegalAccessException m2.invoke(new Person(),"李四"); //你好:李四 // 3. 反射调用 静态的方法 只需要将invoke方法的第一个参数设为null即可. Method m3 = c1.getMethod("fun1",int.class); m3.invoke(null,11); //你好:11 //2. 反射获取字段 Field f1 = c1.getDeclaredField("name"); System.out.println(f1); //private java.lang.String 反射的三种用法.Person.name Field[] f2 = c1.getDeclaredFields(); for (Field i : f2) { System.out.println(i); } //private java.lang.String 反射的三种用法.Person.name //private int 反射的三种用法.Person.age //3. 反射获得字段的类型 ( 字段对象.getType() ) System.out.println(f1.getType()); //class java.lang.String //4. 获得修饰符 int modify = f1.getModifiers(); //获取字段的访问修饰符 System.out.println(modify); //2 //5. 获得包 c1.getPackage() 和实现的接口 c1.getInterfaces() //6. 获得注解(注释Annotation) Method m4 = c1.getDeclaredMethod("getAge");//获取方法 System.out.println(m4.getAnnotation(Deprecated.class)); //@java.lang.Deprecated() //7. 获得类型的简称 getName() System.out.println(m4.getName()); //getAge } }
6. 反射可以越过集合类的泛型检查
public static void main(String[] args) throws Exception { //反射可以越过集合类的泛型检查 ArrayList<Integer> list = new ArrayList<>(); list.add(123); list.add(new Integer(45)); //通过反射添加泛型以外的类型的数据 Class c = list.getClass(); Method m1 = c.getMethod("add",Object.class); m1.invoke(list,"nihao"); //调用list集合的add方法,添加数据 System.out.println(list); //[123, 45, nihao] }
7. 通过反射运行配置文件内容
思想: (其实在实际开发中经常遇到需求变更)那可不可以不改源程序就能应对大量的需求变更呢?
答案是可以的,通过Java给我们提供的反射机制,不改源程序,只对配置文件做修改即可, spring框架就是基于反射机制,通过修改配置文件来实现需求
class Person{ private int id; private String name; public Person(int id,String name){ this.id = id; this.name = name; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + "]"; } } public class Test2 { public static void main(String[] args) throws Exception{ //反射机制,不改源程序,只对配置文件做修改 Constructor<Person> cons = Person.class.getConstructor(int.class,String.class); //Properties类用于读取配置文件的键值对 Properties prop = new Properties(); prop.load(new FileInputStream("person.properties")); //读取 Person p = cons.newInstance(Integer.parseInt(prop.getProperty("id")),prop.getProperty("name")); System.out.println(p); //Person [id=3242, name=李四] } }
来源:https://www.cnblogs.com/gshao/p/10181651.html