java反射(入门)

快速入门

通过外部文件配置, 在不修改源码情况下, 来控制程序, 也符合设计模式的ocp原则(开闭原则: 不修改源码, 扩容功能)

创建一个类对象

public class Cat {
    private String name = "招财猫" ;

    public Cat() {
    }

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

    public void hi() {
        System.out.println("hi " + name);
    }

    public void cry() {
        System.out.println(name + " 喵喵叫...");
    }
}

创建一个properties配置文件

classfullpath=com.test.Cat
method=hi
#method=cry  无需该动源代码, 直接修改配置文件即可更改调用的方法

测试

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //根据配置文件 re.properties指定信息,创建Cat对象并调用方法hi
        //传统的方式 new 对象  ->  调用方法
//        Cat cat = new Cat();
//        cat.hi();

        //使用反射

        //1. 使用Properties类,可以读写配置文件
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString(); //com.test.Cat
        String methodName = properties.get("method").toString(); //hi
        System.out.println("classfullpath=" + classfullpath);
        System.out.println("methodName=" + methodName);

        //2.创建对象, 传统的方法, 行不通 -> 反射机制
//        new classfullpath();

        //3.使用反射机制解决
        //(1)加载类, 返回Class类型的对象cls
        Class cls = Class.forName(classfullpath);
        //(2)通过cls得到加载的类 com.test.Cat 的对象实例
        Object o = cls.newInstance();
        System.out.println("o的运行类型=" + o.getClass()); //运行类型
        //(3)通过cls得到加载的类 com.test.Cat 的 methodName "hi" 的方法对象
        //   即: 反射中, 可以把方法视为对象 (万物皆对象)
        Method method = cls.getMethod(methodName);
        //(4)通过method 调用方法: 即通过方法对象来实现调用方法
        method.invoke(o); //传统方法 对象.方法() , 反射机制 方法.invoke(对象)
    }
}

java程序在计算机有三个阶段

1.代码阶段/编译阶段
    将.java源代码编译成.class代码(字节码文件)
2.Class类阶段(加载阶段)
    Class类对象/堆:
        成员变量 Field[] fields
        构造器 Constructor[] cons
        成员方法 Method[] methods
    方法区:
        类的字节码二进制数据/元数据
3.Runtime运行阶段

在编译阶段, 会通过类加载器(ClassLoader中的loadClass()方法)(体现反射), 将编译好的.class文件加载到Class类阶段, 变成Class类对象, 存放在jvm堆中, 将类的字节码二进制数据/元数据放到方法区中, 然后在运行阶段, 可以通过new来创建一个对象, 也可以得到Class对象后, 1.创建对象, 调用对象方法 2.操作属性等等; 创建好的对象也会存在jvm堆中, 并且, 该对象知道他是属于哪个Class对象

反射机制作用

1.在运行时判断任意一个对象所属的类
2.在运行时构造任意一个类的对象
3.在运行时得到任意一个类所具有的成员变量和方法
4.在运行时调用任意一个对象的成员变量和方法
5.生成动态代理

反射相关的主要类

1.java.lang.Class: 代表一个类, Class对象表示某个类加载后在堆中的对象
2.java.lang.reflect.Method: 代表类的方法, Method对象表示某个类的方法
3.java.lang.reflect.Field: 代表类的成员变量, Field对象表示某个类的成员变量
4.java.lang.reflect.Constructor: 代表类的构造方法, Constructor对象表示构造器

这些类都在java.lang.reflect包下

反射优点和缺点

优点: 可以动态的创建和使用对象(也是框架底层核心), 使用灵活, 没有反射机制, 框架技术就失去底层支撑

缺点: 使用反射机制是解释执行, 对执行速度有影响

反射调用调优 - 关闭访问检查

1. Method和Field、Constructor对象都有setAccessible()方法
2. setAccessible作用是启动和禁用访问安全检查的开关
3. 参数值为true表示 反射的对象在使用时取消访问检查, 提高反射的效率, 参数值为false则表示反射的对象执行访问检查

Class类

基本介绍

1. Class也是类, 因此也继承Object类
2. Class类对象不是new出来的, 而是系统创建的
3. 对于某个类的Class类对象, 在内存中只有一份, 因此类只加载一次
4. 每个类的实例都会记得自己是由哪个Class实例所生成
5. 通过Class对象可以完整地得到一个类的完整结构, 通过一系列API
6. Class对象是存放在堆中
7. 类的字节码二进制数据, 是放在方法区的, 有的地方称为类的元数据(包括 方法代码, 变量名, 方法名, 访问权限等)

Class常用方法

package com.example.demo;

public class Car {
    public String brand = "宝马";
    public int price = 500000;
    public String color = "白色";
}
import com.example.demo.Car;

import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        String classAllPath = "com.example.demo.Car";

        //1. 获取到Car类 对应的 Class对象
        //<?> 表示不确定的java类型
        Class<?> cls = Class.forName(classAllPath);
        //2. 输出cls
        System.out.println(cls); //显示cls对象, 是哪个类的Class对象 com.example.demo.Car
        System.out.println(cls.getClass()); //输出cls运行类型 java.lang.Class
        //3. 得到包名
        System.out.println(cls.getPackage().getName());
        //4. 得到全类名
        System.out.println(cls.getName());
        //5. 提供cls创建对象实例
        Car car = (Car) cls.newInstance();
        System.out.println(car); //car.toString()
        //6.通用反射获取属性 brand
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car)); //宝马
        //7. 通过反射给属性赋值
        brand.set(car, "奔驰");
        System.out.println(brand.get(car)); //奔驰
        //8. 得到所有的属性(字段)
        Field[] fields = cls.getFields();
        for (Field f : fields) {
            System.out.println(f.getName()); //字段名称
        }
    }
}

获取Class对象6种方式

import com.example.demo.Car;

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. Class.forName
        String classAllPath = "com.example.demo.Car"; //通过读取配置文件获取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);

        //2. 类名.class , 应用场景: 用于参数传递
        Class<Car> cls2 = Car.class;
        System.out.println(cls2);

        //3. 对象.getClass(), 应用场景: 有对象实例
        Car car = new Car();
        Class<? extends Car> cls3 = car.getClass();
        System.out.println(cls3);

        //4 .通过类加载器[4种]来获取到类的Class对象
        //(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到Class对象
        Class<?> cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);

        //cls1 , cls2 , cls3 , cls4 其实是同一个对象
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
        System.out.println(cls4.hashCode());

        //5. 基本数据(int, char, boolean, float, double, byte, long, short) 按如下方式得到Class类对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass); //int

        //6. 基本数据类型对应的包装类, 可以通过 .TYPE 得到Class类对象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE;
        System.out.println(type1); //int

        System.out.println(integerClass.hashCode()); //460141958
        System.out.println(type1.hashCode()); //460141958
    }
}

哪些类型有Class对象

import lombok.Data;

import java.io.Serializable;

public class Test5 {
    public static void main(String[] args) {
        //外部类, 成员内部类, 静态内部类, 局部内部类, 匿名内部类
        Class<String> cls1 = String.class; //外部类 ()
        Class<Serializable> cls2 = Serializable.class; //接口
        Class<Integer[]> cls3 = Integer[].class; //数组
        Class<float[][]> cls4 = float[][].class; //二维数组
        Class<Data> cls5 = Data.class; //注解
        Class<Thread.State> cls6 = Thread.State.class; //枚举
        Class<Long> cls7 = long.class; //基本数据类型
        Class<Void> cls8 = void.class; //void数据类型
        Class<Class> cls9 = Class.class;
    }
}

类加载

动态和静态加载

反射机制是java实现动态语言的关键, 也就是通过反射实现类动态加载
1.静态加载: 编译时加载相关的类, 如果没有则报错, 依赖性太强
    比如 new Dog(); 如果Dog类不存在编译就会报错
2.动态加载: 运行时加载需要的类, 如果运行时不用该类, 即使不存在该类, 也不报错. 降低了依赖性
    比如 Class<?> aClass = Class.forName("Person"); 
    如果Person类不存在编译不会报错,只有真正运行到这行代码才会报错

类加载时机
1.当创建对象时(new) //静态加载
2.当子类被加载时, 父类也加载 //静态加载
3.调用类中的静态成员时 //静态加载
4.通过反射 //动态加载

类加载过程

1.java源代码 (com.Cat.java)
	javac编译
2.字节码文件 (com.Cat.class)
	java运行
3.类加载
	加载 Loading
		将类的class文件读入内存, 并为之创建一个java.lang.Class对象, 此过程由类加载器完成
	连接 Linking   将类的二进制数据合并到JRE中
		验证 verification
			对文件的安全进行校验, 比如说:文件的格式是否正确,元数据格式是否正确,字节码是否正确,符号引用是不是ok的
		准备 Preparation
			对我们的静态变量分配内存空间,并且完成一些默认初始化
		解析 Resolution
			虚拟机将会把常量池中的符号引用转换为直接引用
	初始化 initialization
		到初始化阶段才会真正的去初始化编译的静态成员代码,比如静态方法,静态代码块这些,完成显示(指定)初始化
		JVM负责对进行初始化, 这里主要指静态成员

类加载后内存布局情况
	方法区: 元数据:类的字节码二进制数据 (将字节码文件以二进制的形式保存起来)
	堆区: 类的Class对象 (引用方法区中类的字节码二进制数据) (其实是一个数据结构, 也是程序访问的入口)

元数据: 类的字节码二进制数据

类加载阶段中, 加载Loading、连接LinKing这两个阶段是由jvm虚拟机控制的, 我们是无法控制的, 初始化initialization是由我们程序员去控制的

加载阶段

JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中, 并生成一个代表该类的java.lang.Class对象

连接阶段-验证

1.目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身的安全
2.包括: 文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
3.可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施, 缩短虚拟机类加载的时间

连接阶段-准备

JVM会在该阶段对静态变量, 分配内存并默认初始化(对应数据类型的默认初始值, 如0、OL、null、false等), 这些变量所使用的内存都将在方法区中进行分配

class A {
    //属性-成员变量-字段
    //分析类加载的连接阶段-准备 属性是如何处理的
    //1. n1 是实例属性, 不是静态变量, 因此在准备阶段, 是不会分配内存
    //2. n2 是静态变量, 分配内存 n2 是默认初始化 0 , 而不是20
    //3. n3 是static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
    public int n1 = 10;
    public static int n2 = 20;
    public static final n3 = 30;
}

连接阶段-解析

虚拟机将常量池内传符号引用替换为直接引用的过程

举例:
A类 -> B类
在编译的过程中, A类和B类还没有真正的加载到内存里面去, 没有分配空间,
这个时候它们是以符号的方式, 假设A类符号是1, B类符号是2, A类引用到了B类, 就是1引用2, 符号引用(相对引用)
一旦到了真正的类加载了, A类和B类就会被加载到内存里面去了, 就会有A类和B类的内存地址了,
就会改变成地址之间的引用, 这就是直接引用 (绝对引用)

初始化阶段

1.到初始化阶段, 才真正开始执行类中定义的java程序代码, 此阶段是执行<clinit>()方法的过程
2.<clinit>()方法是由编译器按语句在源文件中出现的顺序, 依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句, 并进行合并
3.虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步, 如果多个线程同时去初始化一个类, 那么只会有一个线程去执行这个类的<clinit>()方法, 其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕[debug源码]

public class ClassLoad {
    public static void main(String[] args) {
        //1. 加载B类, 并生成 B的class对象
        //2. 连接 num = 0
        //3. 初始化阶段
        //    依次自动手机类中所有的静态变量的赋值动作和静态代码块中的语句, 并合并
        /*
            clinit() {
                System.out.println("B 静态代码块被执行");
                num = 300;
                num = 100;
            }
            合并: num = 100;
         */
        //new B(); //类加载
        System.out.println(B.num); //如果直接使用类的静态属性, 也会导致类的加载

        //看看类加载的时候, 是有同步机制控制
        /*
        protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
            //正因为有了这个机制, 才能保证某个类在内存中, 只有一份Class对象
            synchronized (getClassLoadingLock(name)) {
                //...
            }
        }
         */
    }
}

class B {
    static {
        System.out.println("B 静态代码块被执行");
        num = 300;
    }

    static int num = 100;

    public B() {
        System.out.println("B() 构造器被执行");
    }
}

通过反射获取类的结构信息

java.lang.Class类

1. getName:获取全类名
2. getSimpleName:获取简单类名
3. getFields:获取所有public修饰的属性,包含本类以及父类的
4. getDeclaredFields:获取本类中所有属性
5. getMethods:获取所有public修饰的方法,包含本类以及父类的
6. getDeclaredMethods:获取本类中所有方法
7. getConstructors:获取本类所有public修饰的构造器
8. getDeclaredConstructors:获取本类中所有构造器
9. getPackage:以Package形式返回包信息
10.getSuperClass:以Class形式返回父类信息
11.getlnterfaces:以Class[]形式返回接口信息
12.getAnnotations:以Annotation[]形式返回注解信息

java.lang.reflect.Field类

1. getModifiers: 以int形式返回修饰符
    说明: 默认修饰符是0, public是1, private是2, protected是4, static是8, final是16
2. getType: 以Class形式返回类型
3. getName: 返回属性名

java.lang.reflect.Method类

1. getModifiers: 以int形式返回修饰符
    说明: 默认修饰符是0, public是1, private是2, protected是4, static是8, final是16
2. getReturnType: 以Class形式获取 返回类型
3. getName: 返回方法名
4. getParameterTypes: 以Class[]返回参数类型数组

java.lang.reflect.Constructor类

1. getModifiers: 以int形式返回修饰符
2. getName: 返回构造器名 (全类名)
3. getParameterTypes: 以Class[]返回参数类型数组

反射爆破

反射爆破创建实例对象

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //1. 先获取到User类的Class对象
        Class<?> userClass = Class.forName("com.test.User");
        //2. 通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);
        //3. 通过public的有参构造器创建实例
        //3.1 先得到对应的构造器
        Constructor<?> constructor = userClass.getConstructor(String.class);
        //3.2 创建实例, 并传入参数
        Object user = constructor.newInstance("张三");
        System.out.println(user);
        //4. 通过非public的有参构造器创建实例
        //4.1 得到private的构造器对象
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(String.class, int.class);
        //4.2 创建实例
        //爆破 [暴力破解] , 使用反射可以访问private构造器/方法/属性, 反射面前, 都是纸老虎
        constructor1.setAccessible(true);
        Object user2 = constructor1.newInstance("李四", 100);
        System.out.println(user2);
    }
}

class User {
    private String name = "名字";
    private int age = 10;

    public User() {
    }

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

    private User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

反射爆破操作属性

import java.lang.reflect.Field;

public class ReflectAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //1. 得到Student类对应的 Class对象
        Class<?> stuClass = Class.forName("com.test.Student");
        //2. 创建对象
        Object o = stuClass.newInstance(); //o 的运行类型就是Student
        System.out.println(o.getClass()); //class com.test.Student
        //3. 使用反射得到 age 属性对象
        Field age = stuClass.getField("age");
        age.set(o, 88); //通过反射来操作属性
        System.out.println(o);
        System.out.println(age.get(o)); //返回age属性的值

        //4. 使用反射操作name 属性
        Field name = stuClass.getDeclaredField("name");
        //对 name 进行爆破, 可以操作 private 属性
        name.setAccessible(true);
//        name.set(o, "张三");
        name.set(null, "李四"); //因为name是static属性, 因此 o 也可以写为 null
        System.out.println(o);
        System.out.println(name.get(o)); //获取属性值
        System.out.println(name.get(null)); //获取属性值, 要求name是static
    }
}

class Student {
    private static String name;
    public int age;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

反射爆破操作方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectAccessMethod {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //1. 得到Boos类对应的Class对象
        Class<?> boosClass = Class.forName("com.test.Boos");
        //2. 创建对象
        Object o = boosClass.newInstance();
        //3. 调用public的hi方法
        //3.1 得到hi方法对象
        //Method hi = boosClass.getMethod("hi", String.class); //OK
        Method hi = boosClass.getDeclaredMethod("hi", String.class);//OK
        //3.2 调用
        hi.invoke(o, "~~~");

        //4 调用private static 方法
        //4.1 得到 say 方法对象
        Method say = boosClass.getDeclaredMethod("say", int.class, String.class, char.class);
        //4.2 因为say方法是private, 所以需要爆破
        say.setAccessible(true);
        System.out.println(say.invoke(o, 100, "张三", '男'));
        //4.3 因为say方法是static的, 还可以这样调用 , 可以传 null
        System.out.println(say.invoke(null, 200, "李四", '女'));

        //5. 在反射中, 如果方法有返回值, 统一返回Object , 但是他在运行类型和方法定义的返回类型一致
        Object reVal = say.invoke(null, 200, "王五", '男');
        System.out.println("reVal 的运行类型: " + reVal.getClass()); //class java.lang.String
    }
}

class Boos {
    private String name;
    public int age;

    public Boos() {
    }

    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }

    public void hi(String s) {
        System.out.println("hi " + s);
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值