反射
类的加载
- 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化
- 加载
- 就是指将class文件读入内存,并为它创建一个Class对象(字节码文件对象)
- 任何类被使用时系统都会建立一个Class对象
- 连接
- 验证:是否有正确的内部结构,并且和其他类协调一致
- 准备:负责为类的静态成员分配内存,并设置默认初始化值
- 解析:将类的二进制数据中的符号引用替换为直接引用
- 初始化:就是我们以前讲过的初始化步骤
类加载器
- 负责将class文件加载到内存中,并为它生成对应的Class对象
- 虽然我们不需要关系类加载机制,但是了解这个机制就能更好的理解程序的运行
- 类加载器的组成
- Bootstrap ClassLoder 根类加载器
- Extension ClassLoder 扩展类加载器
- System ClassLoder 系统类加载器
类加载器的作用
- Bootstrap ClassLoder 根类加载器
- 也被称之为引导类加载器,负责Java核心类的加载(比如System,String类等,在JDK中JRE的lib目录下的rt.jar文件中)
- Extension ClassLoder 扩展类加载器
- 负责JRE的扩展目录中的jar包的加载(在JDK中JRE的lib目录下的ext目录中的内容)
- System ClassLoder 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径(我们自己写的类)
反射(reflect)
- Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称之为Java语言的反射机制。
- 要想解剖一个类,必须要先获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应Class类型的对象。
获取字节码文件对象的方式
Person类
package com.darksnow.reflect;
public class Person {
private String name;
int age;
public String address;
public Person() {
}
private Person(String name) {
this.name = name;
}
public 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 funtion() {
System.out.println("function...");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
获取方式
package com.darksnow.reflect;
/*
* 反射:就是通过Class文件对象(字节码文件对象),去使用该文件中的成员变量,构造方法,成员方法。
*
* Person p = new Person();
* p.xxx
*
* 如果要使用反射,那么首先必须得到Class文件对象(字节码文件对象),字节码文件对象是Class类类型的。
*
* 获取Class文件对象的三种方式:
* 1.通过Object类的getClass()方法
* 2.数据类型的静态属性class
* 3.Class类中的静态方法
* public static Class forName(String className)
*
* 在反射中用Constructor代表构造方法,
*
* 一般我们到底用哪一种呢?
* 1.开发 用三种,因为第三种给的是一个字符串,而不是一个具体的类名,这样我们就可以把这个字符串配置到配置文件中
* 2.自己玩儿 任选一种,但是第二种是最方便的
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一种:通过Object类的getClass()方法
//一个类的字节码文件对象是唯一的
Person p = new Person();
Class<? extends Person> c = p.getClass();
Person pp = new Person();
Class<? extends Person> cc = pp.getClass();
System.out.println(p == pp); //false
System.out.println(c == cc); //true
//第二种:数据类型的静态属性class
Class<? extends Person> c2 = Person.class;
System.out.println(cc == c2); //true
//第三种:Class类中的静态方法
//public static Class forName(String className):这个方法的参数需要传入一个全限类名
//全限类名:包名 + "." + 类名
//ClassNotFoundException:找不到类的异常
Class<?> c3 = Class.forName("com.darksnow.reflect.Person");
System.out.println(cc == c3);
}
}
通过反射获取无参构造方法并使用
package com.darksnow.reflect;
import java.lang.reflect.Constructor;
/*
通过反射获取无参构造方法并使用
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class<?> c = Class.forName("com.darksnow.reflect.Person");
//获取构造方法
//public Constructor<?>[] getConstructors():获取所有公共构造方法
//public Constructor<?>[] getDeclaredConstructors():获取所有构造方法
Constructor<?>[] cs = c.getDeclaredConstructors();
for(Constructor<?> con : cs) {
System.out.println(con);
}
//获取单个构造方法
//public Constructor<T> getConstructor(Class<?>... parameterTypes)
//Class<?>... parameterTypes参数表示的是:你要获取的构造方法的构造参数个数以及数据类型的字节码文件对象
Constructor<?> con = c.getConstructor(); //返回的是无参构造方法对象
/*
* Person p = new Person();
* System.out.println(p);
*
* public T newInstance(Object... initargs)
* 使用上面的Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例
*/
Object obj = con.newInstance();
System.out.println(obj);
Person p = (Person)obj;
p.show();
//我在通过某个类的字节码文件对象,来获取该类的实例 这就是反射。
}
}
通过反射获取该构造方法并使用的案例
package com.darksnow.reflect;
import java.lang.reflect.Constructor;
/*
* 需求:通过反射去获取该构造方法并使用的案例
* public Person(String name, int age, String address)
*
* 实现下面的代码的效果(得到了一个Person对象,并且这个对象的属性值都是被指定过的)
* Person p = new Person("DarkSnow",25,"长沙");
* System.out.println(p);
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class<?> c = Class.forName("com.darksnow.reflect.Person");
//获取带参数的构造方法对象
//public Constructor<T> getConstructor(Class<?>... parameterTypes)
//Class<?>... parameterTypes参数表示的是:你要获取的构造方法的构造参数个数以及数据类型的字节码文件对象
Constructor<?> con = c.getConstructor(String.class,int.class,String.class);
//通过带参数的构造方法对象来创建Person对象
//public T newInstance(Object... initargs)
Object obj = con.newInstance("DarkSnow",25,"长沙");
System.out.println(obj);
}
}
通过反射获取私有构造方法并使用
package com.darksnow.reflect;
import java.lang.reflect.Constructor;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class<?> c = Class.forName("com.darksnow.reflect.Person");
//获取的是私有构造方法对象
//NoSuchMethodException:当无法找到特定方法时抛出此异常,说明通过getConstructor方法是无法访问私有构造函数的
Constructor<?> con = c.getDeclaredConstructor(String.class);
//用该私有构造方法创建对象
//IllegalAccessException:非法的访问异常
//暴力访问
con.setAccessible(true);//值为true则表示反射的对象在使用时取消Java访问检查。
Object obj = con.newInstance("赵甲弟");
System.out.println(obj);
}
}
通过反射获取成员变量并使用
package com.darksnow.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class<?> c = Class.forName("com.darksnow.reflect.Person");
//获取所有的public修饰的成员变量
Field[] fields = c.getFields();
//获取所有的成员变量
Field[] declaredFields = c.getDeclaredFields();
for(Field field : declaredFields) {
System.out.println(field);
}
//通过无参构造方法创建对象
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
//获取单个成员变量address
//获取address并对其赋值
Field addressField = c.getField("address");
//public void set(Object obj,Object value):为成员变量指定值
addressField.set(obj, "怀化");
System.out.println(obj);
//获取name并对其赋值
Field nameField = c.getDeclaredField("name");
//暴力访问
nameField.setAccessible(true);
nameField.set(obj, "李德斯拉弗兰克康健");
System.out.println(obj);
//获取age并对其赋值
Field ageField = c.getDeclaredField("age");
ageField.set(obj, 27);
System.out.println(obj);
}
}
通过反射获取成员方法并使用
package com.darksnow.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//获取字节码文件对象
Class<?> c = Class.forName("com.darksnow.reflect.Person");
//获取所有的成员方法
Method[] methods = c.getMethods(); //获取自己的还包括父类的public修饰的方法
Method[] declaredMethods = c.getDeclaredMethods(); //获取自己的所有方法
for(Method method : declaredMethods) {
System.out.println(method);
}
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
//获取单个方法并使用
//public Method getMethod(String name,Class<?>... parameterTypes):获取一个指定的成员方法
//第一个参数表示的是方法名,第二参数表示的是方法的参数类型字节码文件对象
//我想public void show()方法
Method m1 = c.getMethod("show");
//public Object invoke(Object obj,Object... args):调用上述代码获取的成员方法
//第一个参数表示对象是谁(你要调用的方法是属于哪个对象的),第二个参数表示调用该方法的实际参数
m1.invoke(obj); //调用obj对象的m1方法
System.out.println("----------------------------------");
//调用method方法
//public void method(String s)
Method m2 = c.getMethod("method", String.class);
Object invoke = m2.invoke(obj, "JavaSE");
System.out.println("----------------------------------");
//调用getString方法
//public String getString(String s, int i)
Method m3 = c.getMethod("getString", String.class, int.class);
String objString = (String) m3.invoke(obj, "JavaEE",666);
System.out.println(objString);
System.out.println("----------------------------------");
//调用function方法
//private void funtion()
Method m4 = c.getDeclaredMethod("funtion");
//暴力访问,因为function方法是私有的
m4.setAccessible(true);
m4.invoke(obj);
}
}
通过反射运行配置文件内容
在工程下面直接建立一个class.txt
文件,内容如下:
className=com.darksnow.reflect.Teacher
methodName=love
新建三个类
package com.darksnow.reflect;
public class Teacher {
public void love() {
System.out.println("爱生活,爱音乐");
}
}
package com.darksnow.reflect;
public class Worker {
public void love() {
System.out.println("爱生活,爱看书");
}
}
package com.darksnow.reflect;
public class Student {
public void love() {
System.out.println("爱生活,爱Java");
}
}
启动类
package com.darksnow.reflect;
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//反射前的做法
// Student s = new Student();
// s.love();
//加载配置文件中键值对数据
Properties prop = new Properties();
FileReader fr = new FileReader("class.txt");
prop.load(fr);
fr.close();
//获取配置文件中的数据
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//根据全限类名获取字节码文件对象
Class<?> c = Class.forName(className);
//根据字节码文件对象获取构造函数对象
Constructor<?> con = c.getConstructor();
//通过构造函数对象的得到该构造函数所属的类的对象
Object obj = con.newInstance();
//需要获取你想调用的那个方法的Method对象
Method m = c.getMethod(methodName);
//通过方法所属的对象来调用方法
m.invoke(obj);
}
}
通过反射越过泛型检查
package com.darksnow.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 我给你一个ArrayList<Integer>的一个对象,
* 我想在这个集合中添加一个字符串,如何实现?
*
* 从下述代码来看,可以知道反射会打破类的封装性
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//创建集合对象
ArrayList<Integer> arrayList = new ArrayList<>();
// arrayList.add(1);
// arrayList.add("你好?");
//获取集合arrayList的字节码文件对象
Class<? extends ArrayList> c = arrayList.getClass();
//指定方法名,获取该方法的Method对象
Method m = c.getMethod("add", Object.class);
//通过所属对象(arrayList)来调用指定方法(add)
m.invoke(arrayList, "你好?");
m.invoke(arrayList, 1);
System.out.println(arrayList);
}
}