java基础-反射

文章介绍了Java中的反射机制,包括如何通过反射获取类的内部信息,如属性、构造器和方法。通过一个配置文件读取类名和方法名的案例展示了反射的应用。还详细讲述了类加载的五个阶段,以及获取Class对象的四种方法。此外,讨论了准备阶段的内存分配情况和类加载的时机。最后,提供了获取类结构,如类名、属性、方法和构造器信息的方法。

反射的概念

反射机制通过API取得类的内部信息(包括变量,构造器,方法等),在类完成加载后,会生成Class类型的对象,该对象包含了类的完整结构信息,犹如一面镜子,透过镜子可以看到类的结构,因此称为反射。

反射的引入案例

要求从配置文件中读取并获取类的信息

配置文件:

classPathName=reflectionBeginner.Cat //类名是Cat
method=hi //方法名为hi

Cat类:

public class Cat {
    //属性
    public String name="cat";
    public int age=0;
    //构造器
    public Cat(){}
    public Cat(String name){
        this.name=name;
    }
    //方法
    public void hi(){
        System.out.println("hello "+name);
    }
    @Override
    public String toString() {
        return "WayToGetClass.Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

通过反射读取配置文件中的信息,创建类的对象并调用类中的方法
1.使用Properties读取并加载配置文件,得到类名和方法名

     //使用Properties读取配置文件
      Properties properties = new Properties();
      //加载配置文件文件
      properties.load(new FileInputStream("src/reflectionBeginner/re.properties"));
      //类
      String classPathName = properties.get("classPathName").toString();
      //方法
      String methodName = properties.get("method").toString();

2.通过Class.forName()得到对象,并创建对象实例,调用方法

     // 调用方法
      Class cls = Class.forName(classPathName);//通过类名得到对象
      Object o = cls.newInstance(); //对象实例
      Method method = cls.getMethod(methodName);//得到方法对象
      method.invoke(o);//调用方法 方法.invoke(对象)

3.得到类中的属性

      //返回name属性值
      Field nameField = cls.getField("name");
      System.out.println(nameField.get(o));//属性.get(对象名)
      
      //返回所有属性
      Field[] fields = cls.getFields();
      for(Field field:fields){
          System.out.println(field.getName());
      }
  1. 返回对象实例信息和构造器
    //对象实例信息
    Cat cat=(Cat)cls.newInstance();
    System.out.println("对象实例信息:"+cat); //toString方法

    //返回无参构造器
    Constructor constructor = cls.getConstructor();
    System.out.println(constructor);
    //返回有参构造器
    Constructor constructor1 = cls.getConstructor(String.class);
    System.out.println(constructor1);

获取Class对象的四种方法

    //1.通过Class.forName(pathName)
    String filePath="WayToGetClass.Cat";
    Class cls1 = Class.forName(filePath);

    //2.通过类名.Class
    Class cls2 = Cat.class;

    //3.通过对象.getClass()
    Cat cat = new Cat();
    Class cls3 = cat.getClass();

    //4.通过类加载器得到class对象
    //1)先得到类加载器
    ClassLoader classLoader = cat.getClass().getClassLoader();
    //2)加载得到对象
    Class cls4 = classLoader.loadClass(filePath);

类加载的五个阶段

在这里插入图片描述

准备阶段的内存分配情况

class A{
    //1.a是实例属性,不会在准备阶段分配内存
    public int a=10;
   
    //2.b是静态变量,默认初始化分配内存,但不会赋值
    public static int b=10;
    
    //3.c是常量,完成分配内存和赋值
    public static final int c=10;
}

加载方式

静态加载:编译时完成加载,如果没有该类会报错
动态加载:运行时才加载,如果运行时为使用该类,则不会报错

类加载的四个时机

  1. new一个对象时
  2. 子类被加载,父类也被加载
  3. 调用static成员时
  4. 通过反射时

类加载后的内存布局

将方法区的二进制字节码数据转移到堆中得到class对象

获取类结构的方法

ShowClassStructure.java中有两个类

//父类
class A{
	//属性
    public String numbers;
    //方法
    public void sayOK(){}
    //构造器
    public A(){}
    private A(String numbers){
        this.numbers=numbers;
    }
}

//子类
class Person extends A{
    //属性
    public String name;
    private int age;
    String job;
    protected double salary;
    //方法
    public void sayHi(){}
    private void sayNo(){}
    void sayYes(){}
    protected void sayNothing(){}
    //构造器
    public Person(){}
    private Person(String name){
        this.name=name;
    }

}

首先得到类的对象

 //得到类对象
Class personCls = Class.forName("ShowClassStructure.Person");

类名

 	//.getName()得到全类名
   System.out.println(personCls.getName());
   System.out.println(personCls.getSimpleName());//简单类名

属性

   //得到所有公有属性,本类及父类
    Field[] fields = personCls.getFields();
    for (Field field : fields) {
        System.out.println("本类和父类的公有属性= "+field.getName());
    }
    
    //得到所有公有属性,及修饰值及类型
    for (Field field : fields) {
        System.out.println("本类和父类的公有属性= "+field.getName()
                +" 属性的修饰符值= "+ field.getModifiers()
                +" 属性的类型= "+field.getType());
    }
    
    //得到本类所有属性
    Field[] declaredFields = personCls.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println("本类的所有属性="+declaredField.getName());
    }

方法

   //得到所有公有方法
    Method[] methods = personCls.getMethods();
    for (Method method : methods) {
        System.out.println("所有公有方法= "+method.getName());
    }
    //得到本类所有方法
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
        System.out.println("本类所有方法= "+declaredMethod.getName());
    }
    //得到本类所有方法及修饰值及类型
    for (Method declaredMethod : declaredMethods) {
        System.out.println("本类所有方法= "+declaredMethod.getName()
                +" 修饰值= "+declaredMethod.getModifiers()
                +" 返回类型= "+declaredMethod.getReturnType());
    }

构造器

  //得到本类公有构造器
    Constructor[] constructors = personCls.getConstructors();
    for (Constructor constructor : constructors) {
        System.out.println("本类公有构造器= "+constructor.getName());
    }
    //本类所有构造器
    Constructor[] declaredConstructors = personCls.getDeclaredConstructors();
    for (Constructor declaredConstructor : declaredConstructors) {
        System.out.println("本类所有构造器= "+declaredConstructor.getName());
    }

父类信息

   //父类信息
    Class superclass = personCls.getSuperclass();
    System.out.println("父类信息="+superclass.getSimpleName());

反射爆破

通过反射爆破可以访问私有构造器/属性/方法

User类:

class User{
    //私有属性
    private String name;
    private int age;
    //私有含参构造器
    private User(String name,int age){
        this.name=name;
        this.age=age;
    }
    //私有方法
    private void sayHello(String name){
        System.out.println("hello "+name);
    }
    @Override
    public String toString() {
        return "ReflectionBreaking.User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

1.得到类的对象

 //得到类的对象
  Class cls = Class.forName("ReflectionBreaking.User");

2.访问构造器

  //通过爆破访问私有构造器
   Constructor declaredConstructor = cls.getDeclaredConstructor(String.class, int.class);
   declaredConstructor.setAccessible(true); //允许访问私有构造器
   Object o = declaredConstructor.newInstance("fwj", 19);//创建实例

3.访问属性

    //通过爆破访问私有属性
    Field name = cls.getDeclaredField("name"); //获取属性对象
    Field age = cls.getDeclaredField("age");
    name.setAccessible(true);//允许访问
    age.setAccessible(true);
    name.set(o,"feng"); //设置属性
    age.set(o,20);
    System.out.println(o);
    System.out.println(name.get(o)); //返回属性
    System.out.println(age.get(o));

4.访问方法

   //通过爆破访问私有方法
    Method  declaredMethod = cls.getDeclaredMethod("sayHello",String.class);
    declaredMethod.setAccessible(true);
    declaredMethod.invoke(o,"fwj"); //传参并调用方法
<think>嗯,用户之前问过关于`int`和`integer`的区别,现在转向Java基础中的反射。可能用户正在学习Java,之前的问题涉及基本数据类型和包装类,现在深入到了更高级的主题。我需要先确定用户对Java基础掌握程度,反射通常是面向对象之后的内容,所以用户可能已经有一定的Java基础。 用户的需求是了解Java反射基础知识,可能需要知道反射是什么、为什么用它、以及如何使用。深层需求可能包括实际应用场景或常见问题,比如性能影响或者安全性考虑。需要结构清晰,分点解释,帮助用户逐步理解。 首先,反射的定义,必须用简单明了的语言,避免术语过多。然后核心类,比如Class、Constructor、Method、Field,这些是反射的关键类,需要举例说明。应用场景部分,用户可能想知道反射的实际用途,比如框架开发或动态加载,要举常见的例子如Spring的IoC。 接下来要讲实现步骤,获取Class对象的三种方式,实例化对象的方法,访问字段和调用方法。每个步骤都需要代码示例,但用户可能对代码的细节不太清楚,所以需要解释每个步骤的作用。例如,`Class.forName`可能会抛出异常,需要提及异常处理,但可能不需要深入,保持基础知识的介绍。 优缺点部分需要平衡,用户可能想知道为什么有时候不推荐使用反射,所以性能和安全问题是重点。最后,实际案例,比如动态加载类,或者简单的框架应用,帮助用户将理论联系实际。 还要注意用户可能存在的误区,比如滥用反射导致代码难以维护,或者性能问题。需要提醒在必要时使用,并遵循最佳实践。此外,检查是否需要提到Java版本的变化对反射的影响,比如模块化系统带来的访问限制,但可能用户刚入门,暂时不需要深入。 最后总结,强调反射的灵活性和代价,帮助用户权衡使用场景。整体结构需要逻辑清晰,从定义到实现再到应用,逐步深入,确保用户能够跟随思路,逐步掌握反射基础知识。</think>好的!以下是关于 **Java反射(Reflection)** 的基础知识介绍,以逐步分析的方式展开: --- ### 一、反射的定义 **反射(Reflection)** 是Java提供的一种**运行时动态机制**,允许程序在**不预先知道类结构**的情况下,通过`Class`对象获取类的信息(如字段、方法、构造器等),并操作这些成员。简单来说:**通过反射,程序可以“观察”和修改自身的行为**。 --- ### 二、反射的核心类 Java反射通过以下核心类实现: 1. **`Class`**:表示类或接口的元数据。 2. **`Constructor`**:表示类的构造方法。 3. **`Method`**:表示类的方法。 4. **`Field`**:表示类的字段(属性)。 5. **`Modifier`**:解析成员的修饰符(如`public`、`private`)。 --- ### 三、反射的核心用途 1. **动态获取类信息** 例如:获取类名、父类、接口、注解等。 ```java Class<?> clazz = String.class; System.out.println("类名:" + clazz.getName()); // 输出:java.lang.String ``` 2. **动态创建对象** 即使没有显式定义构造方法,也可以通过反射实例化对象。 ```java Class<?> clazz = Class.forName("java.util.ArrayList"); Object list = clazz.newInstance(); // 创建ArrayList实例(已过时,推荐用构造器) ``` 3. **动态调用方法/访问字段** 可以绕过访问权限(如调用私有方法)。 ```java Method method = clazz.getDeclaredMethod("privateMethod"); method.setAccessible(true); // 突破private限制 method.invoke(obj); ``` 4. **实现通用框架** 如Spring的依赖注入(IoC)、MyBatis的ORM映射等。 --- ### 四、反射的实现步骤(以调用方法为例) 1. **获取`Class`对象** 三种方式: ```java Class<?> clazz1 = Class.forName("全限定类名"); // 通过类路径加载 Class<?> clazz2 = Object.class; // 通过类字面量 Class<?> clazz3 = obj.getClass(); // 通过对象实例 ``` 2. **获取方法或字段** ```java Method method = clazz.getDeclaredMethod("方法名", 参数类型.class); // 包括私有方法 Field field = clazz.getDeclaredField("字段名"); // 包括私有字段 ``` 3. **设置访问权限(可选)** ```java method.setAccessible(true); // 突破private限制 ``` 4. **执行操作** ```java Object result = method.invoke(对象实例, 参数值); // 调用方法 field.set(对象实例, 值); // 修改字段值 ``` --- ### 五、反射的优缺点 | **优点** | **缺点** | |------------------------------|-----------------------------------| | 动态性高(如插件化开发) | 性能较差(比直接调用慢约10-100倍)| | 可访问私有成员(灵活性强) | 代码可读性降低 | | 通用性高(框架开发必备) | 绕过类型安全检查,可能引发异常 | --- ### 六、实际应用案例 1. **动态加载类**(如插件机制): ```java Class<?> pluginClass = Class.forName("com.example.Plugin"); Object plugin = pluginClass.newInstance(); Method runMethod = pluginClass.getMethod("run"); runMethod.invoke(plugin); ``` 2. **Spring框架的依赖注入**: ```java // 通过反射创建Bean并注入属性 Class<?> beanClass = Class.forName("com.example.UserService"); Object bean = beanClass.newInstance(); Field daoField = beanClass.getDeclaredField("userDao"); daoField.setAccessible(true); daoField.set(bean, new UserDaoImpl()); // 注入依赖 ``` --- ### 七、注意事项 1. **性能优化**:避免在频繁调用的代码中使用反射。 2. **安全限制**:Java 9+的模块系统可能限制反射访问(需在`module-info.java`中声明`opens`)。 3. **代码健壮性**:反射调用需处理大量异常(如`ClassNotFoundException`、`NoSuchMethodException`)。 --- ### 总结 反射Java中强大的动态编程工具,**核心价值是灵活性**,但需谨慎使用。掌握反射后,可以深入理解框架设计原理(如Spring、Hibernate),但日常开发中应优先考虑直接调用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值