学习笔记丨初学Java的反射机制!

2022-07-31,,,,

反射机制

了解反射机制,首先要知道反射机制的作用:通过java语言中的反射机制可以操作字节码文件

反射机制比new的方式更加灵活,利用反射机制可以在不改变java源代码的基础之上,做到不同对象的实例化。(符合OCP开闭原则:对扩展开放,对修改关闭
而new只能创建固定的一种对象,每一次修改都要对源文件进行重新编译。

此外, 高级框架的底层实现原理都采用了反射机制。

反射机制相关的类: java.lang.reflect.*

  • java.lang.Class :代表整个字节码,代表整个类;
  • java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法;
  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码,代表类中的构造方法;
  • java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的属性。

反射机制的灵活性验证

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
/*
利用反射机制可以通过修改配置文件的参数,在不改变java源代码的基础之上,做到不不同对象的实例化,这就是反射机制灵活性的体现。
*/
public class ReflectTest03 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {

        FileReader reader = new FileReader("D:\\WorkPlace\\java\\chapter23\\src\\com\\ptu\\javase\\reflect\\classInfo.properties");

        Properties pro = new Properties();

        pro.load(reader);

        reader.close();

        String className = pro.getProperty("className");
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

获取类的字节码(Class)

获取类名:getName() / getSimpleName

  • 第一种方式:Class c = Class.forName(“完整类名带包名”);
  • 第二种方式:Class c = 引用.getClass();
  • 第三种方式:利用Java中任何一种类型(包括基本数据类型)都有的.class属性
public class ReflectTest01 {
    public static void main(String[] args) {
        /*
        Class.forName()
            1. 静态方法
            2. 参数是字符串
            3. 字符串需要的是一个完整类名
            4. 完整类名必须带有包名,java.lang也不能省略
         */
        Class c1 = null;
        try {
            // c1代表String.class文件
            c1 = Class.forName("java.lang.String"); 
            // c2代表Date.class文件
            Class c2 = Class.forName("java.util.Date"); 
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // java中任何一个对象都有一个getClass
        String s = "abc";
        Class x = s.getClass();
        System.out.println(x == c1); // true

        // 第三种方式:java中任何一种类型,包括基本数据类型,都有.class属性
        Class z = String.class;
        Class y = int.class;
        Class c = Integer.class;
        System.out.println(z); // class java.lang.String
        System.out.println(y); // int
        System.out.println(c); // class java.lang.Integer
    }
}

关于Class.forName()

Class.forName()这个方法的执行会导致:类加载。如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(“完整类名”)。

提示:后面JDBC会使用到这个技术。

public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            // Class.forName()这个方法的执行会导致:类加载。
            Class.forName("chapter23.src.com.ptu.javase.reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass{
    // 静态代码块在类加载的时候一定会执行,并且只执行一次
    static {
        System.out.println("这是一个静态代码块的内容!");
    }
}

通过字节码文件创建对象(newInstance)

newInstance() 这个方法是通过调用类的无参构造,完成对象的创建,所以必须保证类中存在无参构造方法!

public class ReflectTest02 {
    public static void main(String[] args) {
        try {
            // 通过反射机制获取Class,通过Class来实例化对象
            Class c = Class.forName("chapter23.src.com.ptu.javase.reflect.bean.User"); 
            // c就代表User类型
            // newInstance() 这个方法会调用User类的无参构造,完成对象的创建
            // 所以必须保证无参构造是存在的!
            Object obj = c.newInstance();

            System.out.println(obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

public class User {
    public User() {
        System.out.println("这是一个无参构造!");
    }
}

通过字节码文件获取对象属性(Field)

获取思路:先获取类(Class) —> 利用Class的方法获取Field(getField/getDeclaredField)(getField/getDeclaredField方法的参数中可以写属性的名字,根据属性的名称来获取Field,例子见“通过字节码文件修改对象属性”)

注意:在获取属性的修饰符列表时,使用getModifiers()返回的是修饰符的代号,即返回值为int类型,可以使用Modifier.toString()将代号转为字符串,然后打印输出即可。

获取类名的方法:getType()

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectTest05 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取整个类
        Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
        String className = studentClass.getName();
        String simpleName = studentClass.getSimpleName();
        System.out.println("类名:" + className + ";简单类名:" + simpleName); // 类名:chapter23.src.com.ptu.javase.reflect.Student;简单类名:Student

        // 获取类中所有的 【public修饰的】 Field
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length); // 测试结果:数组中只有一个元素
        // 取出这个field
        Field f = fields[0];

        // 取出field的名字
        String fieldName = f.getName();
        System.out.println(fieldName); // no

        // 获取所有的Field
        Field[] fs = studentClass.getDeclaredFields();
        System.out.println(fs.length); // 4

        System.out.println("----------------------------");
        // 遍历
        for (Field field:fs){
            // 获取属性的修饰符列表
            int i = field.getModifiers(); // 返回的修饰符是一个数字,每个数字是修饰符的代号
            System.out.println(i);
            // 可以将这个代号转化为字符串
            String modifierString = Modifier.toString(i);
            System.out.println(modifierString);
            // 获取类名
            Class fieldType = field.getType(); // 返回一个Class
            String fieldName1 = fieldType.getName(); // 通过Class getName,这里也可以getSimpleName
            System.out.println(fieldName1);
            // 获取属性的名字
            System.out.println(field.getName());
        }

    }
}

这里还有一个例子,是利用反射机制,反编译一个类的属性Field

// 通过反射机制,反编译一个类的属性Field
public class ThreadTest06 {
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();
        Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");

        Field[] fields = studentClass.getDeclaredFields();
        for (Field f:fields){
            s.append("\t");
            s.append(Modifier.toString(f.getModifiers()));
            s.append(" ");
            s.append(f.getType().getSimpleName() + " ");
            s.append(f.getName());
            s.append(";\n");
        }

        s.append("}");

        System.out.println(s);
    }
}

通过字节码文件修改对象属性(Field)

/*
通过反射机制获取、修改对象的属性值
 */
public class ReflectTest07 {
    public static void main(String[] args) throws Exception {

        // 不使用反射机制创建对象
        Student s = new Student();
        // 不使用反射机制给对象的属性赋值
        s.no = 0012;

        // 使用反射机制创建对象
        Class studentClass = Class.forName("chapter23.src.com.ptu.javase.reflect.Student");
        Object obj = studentClass.newInstance();

        // 获取no属性(根据属性的名称来获取Field)
        Field noField = studentClass.getDeclaredField("no");

        // 使用反射机制给obj对象的no属性赋值
        noField.set(obj,0041);

        // 读取属性的值
        noField.get(obj);

        // 还是不能直接访问私有属性
        Field nameField = Class.forName("chapter23.src.com.ptu.javase.reflect.Student").getDeclaredField("name");
        // 访问私有属性,需要打破私有属性的封装
        nameField.setAccessible(true); // 缺点:打破封装可能给不法分子留下机会
        nameField.set(obj,"Tom");
        System.out.println(nameField.get(obj)); 
        // 直接访问私有属性会报错:cannot access a member of class with modifiers "private"
    }
    
// 反射属性Field
public class Student {

    // Field翻译为字段,其实就是属性/成员
    // 4个Field分别采用不同控制权限修饰
    private int age;
    public int no;
    protected boolean sex;
    private String name;
    public static final double MATH_PI = 3.1415;

}

通过字节码文件获取类的方法(Method)

获取方法的思路:获取Class —> 通过Class获取Method(getDeclaredMethods)

注意:获取方法的修饰符列表、返回值类型、方法名、参数列表分别对应Method类的getModifiers() 、getReturnType() 、getName() 、getParameterTypes()。(其实就是英文名字对应上而已)

public class ReflectTest08 {
    public static void main(String[] args) throws Exception{
        Class userClass = Class.forName("chapter23.src.com.ptu.javase.reflect.UserService");

        Method[] methods = userClass.getDeclaredMethods();
        for (Method m : methods){
            // 获取修饰符列表
            System.out.println(Modifier.toString(m.getModifiers()));
            // 获取方法的返回值类型
            System.out.println(m.getReturnType());
            // 获取方法名
            System.out.println(m.getName());
            // 获取方法的参数列表
            Class[] parameterType = m.getParameterTypes();
            for (Class c:parameterType){
                System.out.println(c.getSimpleName());
            }
        }
    }
}

通过字节码文件调用对象的方法

调用方法的四要素:返回值类型,引用,方法名,实参

思路:创建对象 —> 获取对象的方法(参照上一步如何获取Method)—> 调用Method中的invoke方法

public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        Class userServiceClass = Class.forName("chapter23.src.com.ptu.javase.reflect.UserService");
        // 第一步:先创建对象
        Object obj = userServiceClass.newInstance();
        // 第二步:获取对象的方法
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);

        // 调用方法----四要素:返回值类型,引用,方法名,实参
        Object retValue = loginMethod.invoke(obj,"admin","1234");
        System.out.println(retValue);
    }
}

通过字节码文件获取类的父类和已实现接口

获取类的父类思路: 获取类的Class —> 调用Class的getSuperClass()即可获取类的父类

获取已实现的接口思路:获取类的Class —> 调用Class的getInterfaces() (返回值为Class[] )

public class ThreadTest10 {
    public static void main(String[] args) throws Exception{
        Class stringClass = Class.forName("java.lang.String");

        // 获取String的父类
        Class superClass = stringClass.getSuperclass();
        String superName = superClass.getName();
        System.out.println(superName);

        // 获取类实现的接口
        Class[] interfaces = stringClass.getInterfaces();
        for (Class in:interfaces){
            System.out.println(in.getName());
        }
    }
}

补充知识

可变长度参数

可变长度参数——格式:int… args
1. 可变长度参数要求的参数个数是:0-N个
2. 可变长度参数必须在参数列表的最后一个,且只能有一个
3. 可变长度参数可以当做是一个数组,具有数组的属性

public class ArgsTest {
    public static void main(String[] args) {
        m(1,2,3);
        System.out.println("------------------------------");

        // 也可以传一个数组
        int[] array = {1,2,3,5};
        m(array);
    }

    public static void m(int... args){
        System.out.println("我要把我的参数列表遍历出来:");
        for (int i = 0; i < args.length; i++) {
            System.out.println(i);
        }
    }
}

Java中的文件路径问题

在使用Java的反射机制时,如果都填文件/包的相对路径,会有一个缺点是:移植性差,因为在IDEA中相对路径的默认当前路径是Project的根,假设这个代码离开了IDEA,换到了其它位置,到时的那个路径不是Project的根,此时这个路径就无效了。

FileReader reader = new FileReader("相对路径");  

而我们需要的是:即使代码换位置了,这样编写仍然是通用的,方法如下:

import java.io.FileNotFoundException;
import java.io.FileReader;

/*
关于文件路径的问题
 */
public class AboutPath {
    public static void main(String[] args) throws FileNotFoundException {
        // 前提:这个文件必须在类路径下。(即:src是类的根路径,凡是在src目录下的文件,就是在类路径下)
        /*
        getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象
        getResource是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
        getResource("")可以获取当期的根目录
         */
        String path = Thread.currentThread().getContextClassLoader().getResource("chapter23/src/Info.properties").getPath();
        System.out.println(path);
    }
}

资源绑定器

java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容,这种方式只能绑定属性配置文件(.properties),且属性配置文件必须放到类路径下,同时,写路径时不允许写入文件扩展名。

public class ResourceBundleTest {
    public static void main(String[] args) {
        // 资源绑定器,只能绑定.properties文件,并且这个文件必须在类路径下
        // 而且,写路径的时候,不能写路径后面的扩展名
        ResourceBundle bundle = ResourceBundle.getBundle("chapter23/src/Info");

        String className = bundle.getString("className");
        System.out.println(className);
    }
}

本文地址:https://blog.csdn.net/qq_45250341/article/details/107690293

《学习笔记丨初学Java的反射机制!.doc》

下载本文的Word格式文档,以方便收藏与打印。