自学内容网 自学内容网

java之反射

Java的反射机制是指在程序运行时能够动态地获取类、方法、字段等信息,甚至可以在运行时创建对象、调用方法和访问属性。反射使得程序在编译时不需要了解具体的类信息,而可以在运行时进行操作,提供了更大的灵活性和动态性。比如:通过配置 properties 文件结合反射机制,可以实现动态修改配置而无需改变源代码。这样的设计模式在实际开发中非常常见,比如用来配置数据库连接信息、应用程序的运行参数等。反射机制可以通过读取配置文件,动态加载类、设置属性或调用方法,从而实现灵活的配置和行为控制。

如何获取Class对象?

  • 第一种获取类的方式:通过Class.forName("类的全限定名")来获取类的Class对象,注意需要正确提供完整的类名,否则会抛出ClassNotFoundException
  • 第二种方式是通过对象的getClass()方法来获取该对象的Class实例。
  • 第三种方式是通过基本数据类型或引用类型直接使用.class属性来获取类的信息。
public class ReflectTest01 {
    public static void main(String[] args) throws ClassNotFoundException {
        // stringClass 代表着 String 类型。
        // stringClass 就代表硬盘上的 String.class 文件。
        Class stringClass = Class.forName("java.lang.String");

        // 获取 User 类型
        Class userClass = Class.forName("com.powernode.javase.reflect.User");//类的全限定名

        String s1 = "动力节点";
        Class stringClass2 = s1.getClass();

        // 某种类型的字节码文件在内存当中只有一份。
        // stringClass 和 stringClass2 都代表了一种类型:String 类型
        System.out.println(stringClass == stringClass2); // true

        User user = new User("zhangsan", 20);
        Class userClass2 = user.getClass();
        System.out.println(userClass2 == userClass); // true

        // intClass 代表的就是基本数据类型 int 类型
        Class intClass = int.class;
        Class doubleClass = double.class;
    }
}

如何通过反射机制获取类的Class对象,并验证获取到的Class对象是否相等。

如何通过反射机制使用newInstance()方法来实例化类的对象。

public class ReflectTest02 {
    public static void main(String[] args) throws Exception {
        // 获取到Class类型的实例化之后,可以实例化对象
        // 通过反射机制实例化对象
        Class userClass = Class.forName("com.powernode.javase.reflect.User"); // userClass 代表的就是 User 类型。

        // 通过userClass来实例化User类型的对象
        // 底层实现原理是:调用了User类的无参数构造方法完成了对象的实例化。
        // 要使用这个方法实例化对象的话,必须保证这个类中存在无参数构造方法。如果没有无参数构造方法,则出现异常:java.lang.InstantiationException
        User user = (User) userClass.newInstance();

        System.out.println(user);

        User user2 = (User) userClass.newInstance();
        System.out.println(user == user2);  // false
    }
}

需要注意的是,使用newInstance()方法时,类中必须有无参构造方法,否则会抛出InstantiationException

如何通过读取配置文件来获取类名,并通过反射机制实例化对象 

 配置文件:

通常情况下,Java项目中的配置文件存储在.properties文件中

className=com.powernode.javase.reflect.Vip

className=com.powernode.javase.reflect.Vip 这行代码的意思是,className 的值是字符串 com.powernode.javase.reflect.Vip,它指向一个名为 Vip 的类。  

java代码 

VIP类:

public class Vip {
    // Field
    public String name;

    private int age;

    protected String birth;

    boolean gender;

    public static String address = "北京海淀";

    public static final String GRADE = "金牌";
}
public class ReflectTest03 {
    public static void main(String[] args) throws Exception {
        // 资源绑定器
        ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.reflect.classInfo");

        // 通过key获取value
        String className = bundle.getString("className");

        // 通过反射机制实例化对象
        Class classObj = Class.forName(className);

        // 实例化
        Object obj = classObj.newInstance();

        System.out.println(obj);
    }
}

代码解释:

  1. 资源绑定器(ResourceBundle
    ResourceBundle用于读取指定的配置文件。在这段代码中,ResourceBundle.getBundle()读取了com.powernode.javase.reflect.classInfo文件。通常这种文件是一个.properties文件,里面以键值对的形式存储配置信息。

  2. 获取类名
    通过bundle.getString("className")从配置文件中读取键为className的值,这个值通常是一个类的完整路径。

  3. 反射实例化对象
    使用Class.forName(className)通过类的全限定名(字符串形式)获取该类的Class对象。然后,使用classObj.newInstance()来实例化这个类的对象。

  4. 输出对象
    打印实例化后的对象。实例化的对象是通过反射动态创建的,这展示了反射机制在运行时的动态性。

在代码中,bundle.getString("className") 将返回com.powernode.javase.reflect.Vip字符串,随后通过 Class.forName(className) 动态加载这个 Vip 类。这样就可以通过反射机制创建 Vip 类的实例。

通过反射机制获取类的属性(字段)

public class ReflectTest04 {
    public static void main(String[] args) throws Exception {
        // 获取Vip类
        Class vipClass = Class.forName("com.powernode.javase.reflect.Vip");

        /*
        // 获取Vip类中所有 public 修饰的属性/字段
        Field[] fields = vipClass.getFields();
        System.out.println(fields.length);

        // 遍历属性
        for(Field field : fields){
            System.out.println(field.getName());
        }
        */

        // 获取Vip类中所有的属性/字段,包括私有的
        Field[] fields = vipClass.getDeclaredFields();

        for (Field field : fields) {
            // 获取属性名
            System.out.println(field.getName());

            // 获取属性类型
            Class fieldType = field.getType();
        }
    }
}
  • getFields()getDeclaredFields()

    • getFields():只返回当前类中由 public 修饰的字段,不能获取私有字段。
    • getDeclaredFields():返回当前类中所有声明的字段,包括 privateprotectedpublic 修饰的字段,但不包括继承的字段。
  • Field

    • getDeclaredFields():这行代码 Field[] fields = vipClass.getDeclaredFields(); 获取的是 Vip 类中所有声明的属性,包括 privateprotectedpublic 修饰的字段。
    • field.getName()
      这行代码遍历每个 Field 对象,并调用 getName() 方法获取字段的名称,输出每个字段的名字。

    • field.getType()
      这行代码 Class fieldType = field.getType(); 获取了字段的数据类型,比如 intString 等。

  • 遍历字段并打印信息

    • 通过遍历 Field[] 数组,可以打印每个字段的名称和类型,帮助开发者动态获取类的结构信息。

通过反射修改对象的属性可以借助 Field 类中的 setAccessible(true) 方法来访问和修改私有属性。以下是一个简单的步骤和示例代码,演示如何通过反射来修改 Vip 类的对象属性。

如果通过反射修改对象属性

步骤:

  1. 获取类的 Field 对象。
  2. 对私有属性调用 setAccessible(true),以便能够绕过访问修饰符限制。
  3. 使用 Field.set(Object obj, Object value) 方法修改目标对象的属性值。
import java.lang.reflect.Field;

public class ReflectModifyField {
    public static void main(String[] args) throws Exception {
        // 创建 Vip 对象
        Vip vip = new Vip();
        vip.name = "初始值";

        // 获取 Vip 类的 Class 对象
        Class<?> vipClass = vip.getClass();

        // 获取 public 属性 "name"
        Field nameField = vipClass.getField("name");

        // 修改 "name" 属性的值
        nameField.set(vip, "修改后的值");

        // 打印修改后的值
        System.out.println("修改后的 name: " + vip.name);

        // 获取 private 属性 "age"
        Field ageField = vipClass.getDeclaredField("age");

        // 允许访问 private 属性
        ageField.setAccessible(true);

        // 修改 "age" 属性的值
        ageField.set(vip, 30);

        // 打印修改后的值
        System.out.println("修改后的 age: " + ageField.get(vip));
    }
}

说明:

  1. 获取属性:通过 Class.getField("name") 来获取 public 修饰的 name 字段。对于私有字段(例如 age),使用 Class.getDeclaredField("age") 来获取。

  2. 修改属性:使用 Field.set(Object obj, Object value) 方法修改属性值,obj 是要修改属性的对象,value 是新值。

  3. 访问私有字段:使用 setAccessible(true) 来绕过 Java 的访问控制机制,从而可以访问和修改 private 修饰的字段。

如何通过反射机制调用方法 

public class UserService {

    /**
     * 登录系统的方法
     * @param username 用户名
     * @param password 密码
     * @return true 表示登录成功, false 表示失败
     */
    public boolean login(String username, String password) {
        return "admin".equals(username) && "123456".equals(password);
    }

    /**
     * 退出系统的方法
     */
    public void logout() {
        System.out.println("系统已安全退出!");
    }
}

1. 直接调用方法

UserService userService = new UserService();
boolean isSuccess = userService.login("admin", "123456");
System.out.println(isSuccess ? "登录成功" : "登录失败");

userService.logout();
  • 通过创建 UserService 对象,直接调用 loginlogout 方法。
  • 调用 login 方法时传递用户名和密码,通过返回值判断是否登录成功。
  • 使用 logout 方法打印系统退出信息。

2. 反射机制调用方法

getDeclaredMethod 主要特点:

  1. 获取声明的方法
    • getDeclaredMethod 会获取 当前类中声明的指定方法,包括 privateprotecteddefault(包访问权限)和 public 方法。它只会查找当前类中的方法,而不会查找继承自父类或接口的方法。
  2. 通过方法名称和参数类型来定位方法
    • 它通过方法名称和参数类型来确定具体的目标方法。
    • 方法的参数必须完全匹配,包括参数的数量和类型。
  3. getDeclaredMethod 返回的不是字符串,而是一个 Method 对象。这个 Method 对象代表了类中的某个具体方法,而不是方法名的字符串。

invoke函数:

invoke 是 Java 反射机制中用于 调用方法 的核心方法。它是 Method 类的一部分,通过反射可以在运行时动态调用类的某个方法,而不需要在编译时明确地硬编码调用它。

invoke 的声明:

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException

参数说明:

  1. Object obj

    • 表示你想要调用这个方法的 实例对象也就是说,方法需要在哪个对象上调用。
    • 如果这个方法是 static,则这个参数可以传 null
  2. Object... args

    • 方法调用时传递的实际参数。可以传递多个参数,顺序和类型要与方法的参数列表一致。
    • 如果方法没有参数,则可以不传或传递空数组。

返回值:

  • invoke 的返回值是一个 Object,代表被调用方法的返回值。
    • 如果被调用的方法返回 void,则 invoke 返回 null
    • 如果被调用的方法返回基本类型(如 intboolean 等),invoke 会自动将其封装为相应的包装类型(如 IntegerBoolean)。
  • 获取类对象:

    Class clazz = Class.forName("com.powernode.javase.reflect.UserService");
    

    Class.forName 方法通过类的全限定名来获取 Class 对象,这里传入的类名为 UserService

  • 获取方法对象:

    Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
    • 通过 getDeclaredMethod 方法获取 login 方法的对象,并指定该方法的参数类型为两个 String
  • 调用方法:

    Object retValue = loginMethod.invoke(userService, "admin", "123456");
    System.out.println(retValue);
    
    • 使用 invoke 方法调用 login,传入参数为 userService 实例以及 "admin""123456"
    • invoke 返回的值是一个 Object,因为 login 方法返回布尔值,最终打印 truefalse
  • 调用 logout 方法:

    Method logoutMethod = clazz.getDeclaredMethod("logout");
    logoutMethod.invoke(userService);
    
    • 同样通过反射机制调用 logout 方法,但 logout 方法没有参数,因此只需要获取方法对象并直接调用 invoke
public class ReflectTest10 {
    public static void main(String[] args) throws Exception {
        // 不使用反射机制怎么调用方法?
        UserService userService = new UserService();

        // 直接调用login方法
        boolean isSuccess = userService.login("admin", "123456");
        System.out.println(isSuccess ? "登录成功" : "登录失败");

        // 直接调用logout方法
        userService.logout();

        // 通过反射机制调用login方法
        Class clazz = Class.forName("com.powernode.javase.reflect.UserService");
        Method loginMethod = clazz.getDeclaredMethod("login", String.class, String.class);
        Object retValue = loginMethod.invoke(userService, "admin", "123456");
        System.out.println(retValue);

        // 通过反射机制调用logout方法
        Method logoutMethod = clazz.getDeclaredMethod("logout");
        logoutMethod.invoke(userService);
    }
}

如何通过反射机制来动态实例化类

order类:

public class Order {
    // 私有属性
    private String no;
    private double price;
    private String state;

    // 无参构造方法
    public Order() {}

    // 只有 no 参数的构造方法
    public Order(String no) {
        this.no = no;
    }

    // 有 no 和 price 参数的构造方法
    public Order(String no, double price) {
        this.no = no;
        this.price = price;
    }

    // 有 no、price 和 state 参数的构造方法
    public Order(String no, double price, String state) {
        this.no = no;
        this.price = price;
        this.state = state;
    }

    // 获取 no 属性的方法(getter)
    public String getNo() {
        return no;
    }

    // 设置 no 属性的方法(setter)
    public void setNo(String no) {
        this.no = no;
    }

    // 获取 price 属性的方法(getter)
    public double getPrice() {
        return price;
    }

    // 设置 price 属性的方法(setter)
    public void setPrice(double price) {
        this.price = price;
    }

    // 获取 state 属性的方法(getter)
    public String getState() {
        return state;
    }

    // 设置 state 属性的方法(setter)
    public void setState(String state) {
        this.state = state;
    }

    // 重写 toString() 方法,输出对象的字符串表示
    @Override
    public String toString() {
        return "Order{" +
                "no='" + no + '\'' +
                ", price=" + price +
                ", state='" + state + '\'' +
                '}';
    }
}

1. 直接创建对象(不使用反射)

这部分代码使用传统的方式直接实例化对象,不涉及反射机制:

Order order1 = new Order();
System.out.println(order1);

Order order2 = new Order("1111122222", 3650.5, "已完成");
System.out.println(order2);
解释:
  • order1 是通过无参构造函数创建的。
  • order2 使用了带参数的构造函数,传入了订单号、价格和状态。

2. 通过反射创建对象

这里通过反射来动态加载类,并使用构造函数来创建实例。代码的步骤分为两个部分,一个是使用无参构造方法,另一个是使用带参构造方法。

2.1 动态加载类
Class clazz = Class.forName("com.powernode.javase.reflect.Order");
解释:
  • Class.forName()方法用于根据类的全限定名(包名+类名)动态加载类。

3. 使用已弃用的newInstance()方法

这部分代码已经被注释掉,因为newInstance()方法在Java 9之后已被弃用,原因是它只能调用无参构造函数,且可能抛出反射异常:

// Object obj = clazz.newInstance();
// System.out.println(obj);

4. 通过反射调用无参构造方法

Constructor defaultCon = clazz.getDeclaredConstructor();
Object obj = defaultCon.newInstance();
System.out.println(obj);
解释:
  • clazz.getDeclaredConstructor():获取Order类的无参构造函数。
  • newInstance():调用该构造函数创建一个Order对象。

5. 通过反射调用带参数的构造方法 

Constructor threeArgsCon = clazz.getDeclaredConstructor(String.class, double.class, String.class);
Object obj1 = threeArgsCon.newInstance("555245422", 6985.0, "未完成");
System.out.println(obj1);
解释:
  • clazz.getDeclaredConstructor():获取一个带有三个参数的构造函数,这三个参数类型分别是String(订单号)、double(价格)、String(状态)。
  • newInstance():传入具体参数值来实例化对象。

详细解释:

  1. 动态获取类和构造函数
    • 通过Class.forName()获取类的Class对象。
    • 通过clazz.getDeclaredConstructor()获取类的构造函数,支持无参和带参的构造函数。
  2. 反射实例化对象
    • 无论是无参还是带参构造函数,都可以通过调用Constructor对象的newInstance()方法来实例化对象。
    • 带参构造函数需要传递相应的参数类型和值。
public class ReflectTest12 {
    public static void main(String[] args) throws Exception {
        // 不使用反射机制的情况下,怎么创建对象?
        Order order1 = new Order();
        System.out.println(order1);

        Order order2 = new Order("1111122222", 3650.5, "已完成");
        System.out.println(order2);

        // 通过反射机制来实例化对象
        Class clazz = Class.forName("com.powernode.javase.reflect.Order");

        // 获取Order的无参数构造方法
        Constructor defaultCon = clazz.getDeclaredConstructor();
        Object obj = defaultCon.newInstance();
        System.out.println(obj);

        // 获取三个参数的构造方法
        Constructor threeArgsCon = clazz.getDeclaredConstructor(String.class, double.class, String.class);
        Object obj1 = threeArgsCon.newInstance("555245422", 6985.0, "未完成");
        System.out.println(obj1);
    }
}

反射机制的使用场景:

  1. 动态加载类:当类名在运行时才确定时,反射可以用来动态加载类并操作类中的属性、方法或构造函数。
  2. 调用私有构造函数或方法:使用反射可以访问类的私有成员,尽管这通常用于测试或调试场景。
  3. 框架或库的底层实现:许多框架(如Spring)使用反射来实现依赖注入和动态代理。

注意事项:

  • 性能开销:反射相比于直接调用会有一定的性能开销,不建议频繁使用。
  • 安全问题:反射可以绕过类的访问控制,从而访问私有成员,使用时需谨慎。

原文地址:https://blog.csdn.net/gege_0606/article/details/142573420

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!