欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-21 10:33:50
反射机制了解反射机制,首先要知道反射机制的作用:通过java语言中的反射机制可以操作字节码文件。反射机制比new的方式更加灵活,利用反射机制可以在不改变java源代码的基础之上,做到不同对象的实例化。(符合OCP开闭原则:对扩展开放,对修改关闭)而new只能创建固定的一种对象,每一次修改都要对源文件进行重新编译。此外, 高级框架的底层实现原理都采用了反射机制。反射机制相关的类: java.lang.reflect.*java.lang.Class :代表整个字节码,代表整个类;java....

反射机制

了解反射机制,首先要知道反射机制的作用:通过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