目录
反射原理
通过一个名叫Class的类去加载一个Cat类,返回一个Class对象。这个Class对象就是Cat类反射的对象,他会保存Cat类的完整结构。然后你可以通过Class类的对象去调用不同的方法,得到Cat类里面的各种信息。
最大的感受:当你启动一个程序的时候,我可以通过修改日志使你这个程序改变,而不需要改变源码。
反射原理图
当我们在new一个Cat对象的时候,JVM底层会把字节码文件Cat.class类加载到堆里面,形成一个名叫“Class”的类对象,然后这个Class类对象里面包含了Cat.class的完整结构,当我们在使用Cat对象的时候他会自动匹配找到在堆里面去找到这个“Class”类对象的。(为啥会自动找到?因为他这个Class对象对应的Class类其实就是Cat对象的运行类型)
反射需要掌握的四个类:
上述四个类中,一个Class对象实例就代表目标你那个.class字节码文件。他和.class字节码文件是映射的关系。一个Method类的对象就代表目标类中的某个具体方法,一个File类对象就代表目标类中的某个成员变量,一个Constructor类对象就代表目标类中的某个构造器。但是后面三种类都需要通过前面这个Class对象才能操作。也就是说当一个类映射成一个Class对象后,这个Class对象中包含了这个类中的所有结构(方法、属性、等等),此时我这个Class对象就相当于这个类了,然后我可以通过Method、Field、Constructor等方法去操作这个Class对象里面的映射内容。
深入理解Class类
我要对上述图片中的第二点:Class对象不是new出来的,而是系统创建的解释一下。
系统在什么样的情况下会创建Class对象?在我们传统的new Cat();这样创建一个对象的时候,他会先生成一个.class字节码文件,然后经过类加载ClassLoader类创建了一个Class对象,那这个Class对象就是系统创建的,他跟Cat类不就是反射关系,下面源码中这个String name参数就是.class字节码的路径
同样的,在我们在直接调用Class.forName(全类名)生成Class对象的时候也是调用这个ClassLoader类去创建Class对象。
第三点:
这个指的是系统在通过字节码文件去创建Class对象他只会创建一次。假如你前面用了传统的方法,new Cat();接着再用映射的方法Class.forName(全类名);
你在进行Class.forName(全类名);的时候他就不会在给你创建一个Class对象了,因为前面传统方法中new Cat();系统已经给你创建好了这个Class对象了。注意这个Class对象是放在堆里面的,堆是在内存里面开辟的。注意:全类名是全级 包名+类名。这个全集包名不包括src喔
最后一点:
解析:在对Cat.class进行类加载的时候,他先把你这个Cat.class节码文件读入到方法区,然后在内存里面开辟堆空间,堆里面通过loadClass类生成一个Class类对象。此时的Class就是一个数据结构了,数据结构对我们来说是很方便操作的,这也是为什么是直接对这个Class来操作而不是对下面那个二进制文件来操作,二进制文件难操作。
Class类常用的方法
//实体类User public class User { public String name = "Tom"; public int age = 13; public int id = 10086; }
//<?>表示不知道你这个路径下的目标类是啥类,所以可以接受任何类
Class<?> c1 = Class.forName("com.yzh666.User");//获取目标类User的Class对象
//会显示ci对应的目标对象User
System.out.println(c1);//com.yzh666.User
System.out.println(c1.getClass());//c1的运行类型就是java.lang.Class
//得到c1对应的User类的全级包的名字
System.out.println(c1.getPackage().getName());//com.yzh666
//得到c1对应的User的全路径
System.out.println(c1.getName());//com.yzh666.User
//通过映射对象c1,获取对应的User实例对象
Object o = c1.newInstance();//此时的o对象就是User类型
User user = (User)o;//可以转User
//通过反射对象获取name字段
Field field = c1.getField("name");//他会把你这个name字段放在一个Field对象里面
System.out.println(field.get(user));//Tom
//field.get(user)这种方式获取字段的对应的值你这个字段在user对象中必须是public
//私有属性怎么获取??思考,后面再说
//通过反射对象c1设置user对象中的属性值
field.set(user,"Jack");//因为你这个field对象前面已经对应了是"name"这个字段
System.out.println(field.get(user));//Jacks
//通过反射对象c1获得对应User对象中的属性名字
Field[] fields = c1.getFields();
for(Field f : fields){
System.out.println(f.getName());//name,age,id
}
获得Class对象的四种方式
在不同的阶段有不同的方式获取(目前来说掌握前面三种足矣)
// 方式一:Class.forName(全类名)
Class<?> c1 = Class.forName("com.yzh666.User");
// 方式二:类名.class
Class c2 = User.class;
// 方式三:对象名.getClass获取
User user = new User();
Class c3 = user.getClass();
//看到这里,你会发现卧槽原来那个对象的运行类型就是Class
//原来那个运行类型就是通过实体类映射而成的反射对象Class
// 方式四:通过ClassLoader对象获取(知道就行)
ClassLoader classLoader = user.getClass().getClassLoader();
Class c4 = classLoader.loadClass("com.yzh666.User");
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
输出结果是:
显然都是同一个User对象
哪些类型有Class对象
静态与动态加载
case “1”中的Dog是静态加载类,他在javac编译的时候就会开始加载。不管你在执行过程中有没有进入该选项case都会加载,如果Dog类出错,或者不存在则会报错,即使你没有进入。
case“2”是反射类,动态加载,只有在运行时真的执行到这个类才会加载,也就是说他不仅是运行时加载而且还要执行到该代码才会加载。在执行过程中进入到case“2”这个选项才会加载这个Person类。如果你在执行过程中没有进入到这里,即使你这个Person类不能存在他也不会报错。
类加载的五个流程(面试题)
必须记住这个点,在类加载完后,在堆空间生成一个Class对象,方法区会生成这个类对应的字节码二进制数据文件。
类加载的三个阶段(面试点)
类加载的三个阶段中,前面两个加载和连接都是由JVM机来控制完成的,我们程序员是无法控制的,
第一个阶段没啥好说就是把你编译好的字节码文件.class(或者其他数据源比如jar包、网络)读入到内存中,那么此时内存中就有一个二进制的字节码文件了,然后在堆里面生成一个Class对象。这个Class对象是依赖于内存中的二进制文件来创建的。
第二个阶段中,
验证:主要目的是对前面内存中读入二进制文件的进行校验,比如文件描述符、字节码文件、元数据等信息符不符合当前虚拟机的要求,并且检查会不会危害虚拟机的自身安全。
如果你的这个项目比较大的话可以考虑使用-Xverify:none参数关闭掉大部分验证,达到缩短虚拟机验证的时间。
准备:准备阶段指的是对我们的静态变量进行一个默认的初始化。比如static int a = 3; a会默认赋值0。并且为其分配空间
解析:把符号引用转成直接引用。
初始化阶段:主要是加载执行我们程序员写的静态代码赋值,比如你前面不是给
static int a = 3;他这里才真正执行给这个3赋值,前提是你这个是静态的啊,因为静态成员,是类加载相关联的。
这里面,这个连接的准备阶段我要单独拉出来说一下,不同的修饰符修饰的属性他在这个阶段是怎么赋值的
一些常用的API
通过反射创建实例(反射爆破创建实例)
/**
* @author 演示通过反射创建实例
* @version 1.0
*/
public class ReflectCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1.获取Class对象
Class<?> user1Class = Class.forName("com.yzh666.User1");
//2.通过public的无参构造器创建实例
Object o = user1Class.newInstance();
//3.通过public的有参构造器创建实例
//分两步,先得到对应的构造器,再来创建实例对象
Constructor<?> constructor = user1Class.getConstructor(int.class);
Object o1 = constructor.newInstance(13);
//4.通过非public的构造器创建实例
Constructor<?> constructor1 = user1Class.getDeclaredConstructor(int.class,String.class);
constructor1.setAccessible(true);//爆破(设置可访问)
Object o2 = constructor1.newInstance(18, "唐海生");
//解读:首先获取非public的构造器得用getDeclaredConstructor
//因为getConstructor这个只能用获取public修饰的的构造器
//第二步,必须使用setAccessible方法爆破这个private,因为如果你不破解的话直接
//调用newInstance他只能返回public类的啊。
}
}
class User1{
private int age = 10;
private String name = "原始狗";
public User1() {
System.out.println("这是public无参构造器");
}
public User1(int age) {
this.age = age;
System.out.println("这是public的有参构造器"+age);
}
private User1(int age,String name) {
this.age = age;
this.name = name;
System.out.println("这是private的有参构造器");
}
@Override
public String toString() {
return "User1{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
public void test(){
System.out.println("测试");
}
}
//反射可以获取私有的方法属性,在反射面前一切都是纸老虎
通过反射爆破操作私有属性
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
Class<?> studentClass = Class.forName("com.yzh666.Student");
Object o = studentClass.newInstance();
//操作public属性
Field field = studentClass.getField("age");
//此时这个field对象已经对应了一个名为age的属性,但是还没有指明是哪一个对象中的age属性
field.set(o,13);//指名o对象中的age属性,通过反射操作age属性
System.out.println(field.get(o));//get方法直接返回值,但需要传入o对象,打印:13
//操作private属性
Field declaredField = studentClass.getDeclaredField("name");
//此时这个declaredField里面有一个private属性name
//所以需要对他进行爆破
declaredField.setAccessible(true);
// declaredField.set(o,"温龙威"); 可以用这种
declaredField.set(null,"唐海生");//也可以用这种
System.out.println(declaredField.get(o));//唐海生
//为什么可以用null,因为我的这个name属性是静态的,我不需要通过对象去操作
//而我的这个declaredField对象在前面是已经关联到了studentClass这个Class对象了
//所以他不通过对象,直接用null,是可以直接定位到studentClass这个类去操作静态属性的
}
}
class Student{
public int age;
private static String name;
public Student(){}
@Override
public String toString() {
return "Student{" +
"age=" + age +
"name=" + name +
'}';
}
}
通过反射爆破调用私有方法
public class ReflectAccessibleMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class<?> bossClass = Class.forName("com.yzh666.Boss");
Object o = bossClass.newInstance();
//调用public的方法
Method hi = bossClass.getMethod("hi",String.class);
Object o1 = hi.invoke(o, "Jeremy");
//调用非public的方法
Method say = bossClass.getDeclaredMethod("say",int.class,String.class,char.class);
say.setAccessible(true);
Object tom = say.invoke(o,13, "Tom",'6');
}
}
class Boss {
public int age;
private static String name;
public Boss() {
}
public void hi(String name){
System.out.println("hi " + name);
}
private static void say(int n, String s, char c) {
System.out.println(n + " " + s + " " + c);
}
}
反射作业
public class homework01 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
/**
* 练习1:
* 通过反射修改私有成员变量 com.hspedu.homework
* Homework01.java定义PrivateTest类,
* 有私有name属性,并且属性值为hellokitty,提供getName的公有方法
* 创建PrivateTest的Class类,利用Class类得到私有的name属性,
* 修改私有的name属性值并调用getName()的方法打印name属性值
*/
//Class<?> PrivateTest_Class = Class.forName("com.yzh666.PrivateTest");//OK
Class<PrivateTest> PrivateTest_Class = PrivateTest.class;
Object o = PrivateTest_Class.newInstance();
Field declaredField = PrivateTest_Class.getDeclaredField("name");
declaredField.setAccessible(true);
System.out.println(declaredField.get(o));//hello,kitty
declaredField.set(o,"hello,Tom");//修改属性值
Method getName = PrivateTest_Class.getMethod("getName");
Object invoke = getName.invoke(o);//可转String
System.out.println(invoke);//hello,Tom
}
}
class PrivateTest{
private String name = "hello,kitty";
public String getName(){
return name;
}
}
//不管你需要对成员属性、方法做啥操作你都需要先获取到对应的属性对象和方法对象。
public class homework02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException {
/**
* 利用Class类的forName方法得到File类的class 对象
* 在控制台打印File类的所有构造器通过newInstance的方法创建File对象,
* 并创建D: mynew.txt文件
*/
Class<?> fileClass = Class.forName("java.io.File");
Constructor<?> constructor = fileClass.getConstructor(String.class);
Object o = constructor.newInstance("D:\\my.txt");
Constructor<?>[] declaredConstructors = fileClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
// File file = (File)o;
// file.createNewFile();
//如果你向上面这样做去创建这个文件,虽然可以但是这不是反射
Method createNewFile = fileClass.getMethod("createNewFile");
Object invoke = createNewFile.invoke(o);
}
}