JavaSE进阶十一 反射机制一

1,反射机制

  • 反射机制有什么作用
    • 通过java语言中的反射机制可以操作字节码文件;有点类似于黑客,可以读和修改字节码文件。
    • 通过反射机制可以操作代码片段。(class文件)
  • 反射机制的相关类在哪个包下?
    • java.lang.reflect
  • 反射机制相关的重要类:
    • java.lang.Class 代表字节码文件 代表一个类型
    • java.lang.reflect.Method 代表字节码中的方法字节码
    • java.lang.reflect.Constructor 代表字节码中的构造方法字节码
    • java.lang.reflect.Field 代表字节码中的属性字节码
  • 获取一个类的字节码文件有三种方式
    • 1,Class c = Class.forName("完整的类名带包名")
    • 2,Class c = 引用/对象.getClass();
    • 3,Class c = 任何类型.class();
代码示例
import java.util.Date;

public class ReflectTest {
    public static void main(String[] args) {
        /*
        第一种方式:
        Class.forName();
            静态方法
            方法的参数是一个字符串
            字符串需要是一个完整的类名
            完整类名必须带有包名
         */
        Class c1 = null;
        try {
            c1 = Class.forName("java.lang.String"); // c1代表String.class字节码文件,或者说代表String类型
            Class c2 = Class.forName("java.lang.System");// c2代表Date类型
            Class c3 = Class.forName("java.util.Date");  // c3代表Integer类型
            Class c4 = Class.forName("java.lang.Integer");// c4代表System类型
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 第二种方式:java对象中都有一个方法:getClass()
        // 在方法区中,同一个类型字节码文件只有一个。
        String s = "abc";
        Class x = s.getClass();// x代表String.class字节码文件,或者说代表String类型。
        // 在方法区中,String类型字节码文件只有一个。
        System.out.println(c1 == x); // true 说明c1和x的内存地址是一样的

        // 第三种方式:java语言中任何一种类型,包括基本数据类型,都有.class属性。
        Class z = String.class;  // z代表String类型。
        Class k = Date.class;   // k代表Date类型。
        Class f = int.class;    // f代表int类型。
        Class e = double.class; // e代表double类型。

        System.out.println(x == z); // true 说明x和z的内存地址是一样的
    }
}

2,通过反射机制实例化对象

  • 获取到Class能做什么
    • 通过Class的newInstance方法来实例化对象。
    • 注意:newInstance内部调用的是无参构造方法,必须保证无参构造方法的存在。
  • Class.forName()方法的执行时发生了什么
    • Class.forName()方法的执行会导致类加载;类加载时,静态代码块执行。
    • 如果你只希望执行一个类的静态代码块,其他代码不执行可以使用它。
代码示例
public class ReflectTest01 {
    public static void main(String[] args) {

        try {
            // 通过反射机制获取Class,通过Class来实例化对象
            Class c = Class.forName("com.javaSE.reflects.User");
            // newInstance这个方法会调用User类的无参构造方法,完成对象的创建。
            // newInstance调用的是无参构造方法,必须保证无参构造方法的存在。
            Object obj = c.newInstance();
            System.out.println(obj);

            //  Class.forName()方法的执行会导致类加载
            Class.forName("com.javaSE.reflects.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

class User{
    public User() {
        System.out.println("User无参构造方法");
    }
    public User(String s) {
        System.out.println("User有参构造方法");
    }
}

class MyClass{
    // 静态代码块在类加载时执行,并且只执行一次。
    static {
        System.out.println("静态代码块执行了");
    }
}

3,验证反射机制的灵活性

java代码写一遍,在不改变java源代码的情况下,可以做到不同对象的实例化,非常灵活。(符合OCP开闭原则:对扩展开发,对修改关闭)

代码示例
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class ReflectTest02 {
    public static void main(String[] args) {
        // 1,创建一个名为ClassName的properties配置文件,把User类放到配置文件里:className=com.javase.reflects.User
        FileReader reader = null;
        try {
            // 通过IO流读取ClassName.properties文件
             reader = new FileReader("src/ClassName.properties");
            // 创建属性类对象
            Properties pro = new Properties();
            // 加载
            pro.load(reader);

            // 通过key获取value
            String cn = pro.getProperty("className");
            System.out.println(cn);

            // 通过反射机制实例化对象

            Class c = Class.forName(cn);
            Object obj = c.newInstance();
            System.out.println(obj);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            if (reader != null){
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4,研究一下文件路径问题

怎么获取一个文件的绝对路径,以下讲解的这种方式是通用的,前提:文件需要在类路径下。

代码示例
public class ReflectTest03 {
    public static void main(String[] args) throws Exception {
        // 这种方式路径缺点:移植性差,在IDEA中默认的当前路径是project的跟。
        // 这个代码假设离开了IDEA,换到了其他位置,可能当前路径就是project的跟了,这个时候路径就无效了。
        // FileReader reader = new FileReader("src/ClassName.properties");

        // 下面说一种比较通用的路径,即使代码位置更改了,仍然是有用的。
        // 注意:使用以下通用方式的前提是:这个文件必须在类路径下。
        // 什么是类路径?在src下的都是类路径下。src是类的跟路径

        /*
        解析以下代码:
            Thread.currentThread()  当前线程对象。
            getContextClassLoader()  是线程对象的方法,获取当前线程的类加载器对象。
            getResource()  [获取资源]这是类加载器的方法,当前线程的类加载器默认从类的根路径下加载资源。
         */
        String className = "ClassName.properties";
        // String className = "com/javaSE/reflects/ReflectTest.class";
        System.out.println(className);
        // 获取绝对路径
        String path = Thread.currentThread().getContextClassLoader().
                getResource(className).getPath();
        // 采用以上的代码可以拿到一个文件的绝对路径
        System.out.println(path);

        // 把绝对路径传给流
        // FileReader reader = new FileReader(path);

        // 直接以流的反射返回
        InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("ClassName.properties");

        // --------------------------------------------------------------------------------------------------------------------------

        // 创建属性对象
        Properties pro = new Properties();
        // 加载流
        pro.load(reader);
        // 关闭流
        reader.close();

        // 通过key值获取value
        String cl = pro.getProperty("className");
        System.out.println(cl);

        Class c2 = Class.forName(cl);
        c2.newInstance();
    }
}

5,资源绑定器(ResourceBundle)

  • java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
  • 使用这种方式的时候,属性配置文件xx.properties必须放到类路径下。
代码示例
import java.util.ResourceBundle;

public class ReflectTest04 {
    public static void main(String[] args) throws Exception {

        // 资源绑定器 只能绑定xx.properties文件 这个文件必须在类路径下 文件扩展名不用写
        ResourceBundle bundle = ResourceBundle.getBundle("ClassName");
        // 通过key值获取value
        String cl = bundle.getString("className");
        System.out.println(cl);
        // 获取字节码文件
        Class c2 = Class.forName(cl);
        c2.newInstance();
    }
}

关于JDK中自带的类加载器(了解)

  • 什么是类加载器

    • 专门负责加载类的命令/工具。ClassLoader
  • JDK中自带了3个类加载器

    • 启动类加载器
    • 扩展类加载器
    • 应用类加载器
  • 假设有这样一段代码:String s = "abc";

    • 代码再开始执行之前,会将所需要类全部加载到JVM当中,通过类加载器加载,看到以上代码类加载器会找到Sting.class文件进行加。

    • 怎么进行加载的?

      • 首先通过启动类加载器加载;
        • 启动类加载器专门加载:jdk目录下的/jre/lib/rt.jar
        • rt.jar中都是JDK中最核心的类库。
      • 如果通过启动加载器加载不到,就会通过扩展类加载器进行加载;
        • 扩展类加载器专门加载:jdk目录下的/jre/lib/ext/*.jar
      • 如果通过扩展类加载器也加载不到,就会通过应用类加载器进行加载。
        • 应用类加载器专门加载:classpath中的jar包。(class文件)
  • java中为了保证类加载的安全,使用了双亲委派机制

    • 优先从启动类加载器中加载,这个称为"父";"父"无法加载到,再从扩展类加载器加载,
      这个称为"母";双亲委派如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。

6,反射属性Field

6.1,通过反射机制获取一个对象的属性(Filed)

Field 翻译为字段,其实就是属性成员

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

public class ReflectTest06 {
    public static void main(String[] args) throws Exception {
        // 获取整个类
        Class student = Class.forName("com.javaSE.reflects.Student");
        System.out.println("完整类名:" + student.getName());
        System.out.println("简写类名:" + student.getSimpleName());

        // 获取类中所有public修饰的Field
        Field[] fields = student.getFields();
        System.out.println(fields.length);// 测试数组中只有2个元素
        // 取出整个Field
        Field f = fields[0];
        // 取出整个Field名字
        String fieldName = f.getName();
        System.out.println(fieldName);

        System.out.println("--------------------------------------------------------------------");

        // 获取类中所有的Field
        Field[] fs = student.getDeclaredFields();
        System.out.println(fs.length);// 测试数组中 有5个元素
        //  遍历
        for (Field field : fs){
            // 获取属性名字
            System.out.println(field.getName());

            // 获取属性类型
            Class fieldType = field.getType();
            System.out.println(field.getName() + "完整类名:" + fieldType.getName() + ";简写类名:" + fieldType.getSimpleName());

            // 获取属性修饰符列表
            int mods = field.getModifiers();// 返回的修饰符是一个数字,每个数组是修饰符的代号。
            // 将代号数字转换成修饰符
            String mos = Modifier.toString(mods);
            System.out.println(field.getName() + "修饰符代号:" + mods + ";修饰符是:" + mos);

        }

        System.out.println("-------------通过反射机制,反编译一个类的属性---------------------------------------------");
        // 通过反射机制,反编译一个类的属性Field
        fbyStudent();
    }

    /**
     * 通过反射机制,反编译一个类的属性
     * @throws ClassNotFoundException
     */
     static void fbyStudent() throws ClassNotFoundException {
         // 获取整个类
         Class student = Class.forName("com.javaSE.reflects.Student");
//         Class student = Class.forName("java.lang.Integer");
         // 创建一个拼接字符串对象
         StringBuilder sr = new StringBuilder();
         // 开始拼写
         sr.append(Modifier.toString(student.getModifiers()) + " class " + student.getSimpleName() + "{\n");
         Field[] fds = student.getDeclaredFields();
         for (Field field : fds){
             sr.append("\t");
             sr.append(Modifier.toString(field.getModifiers()));
             sr.append(" ");
             sr.append(field.getType().getSimpleName());
             sr.append(" ");
             sr.append(field.getName());
             sr.append(";\n");
         }
         sr.append("}");
         System.out.println(sr);
    }
}

// 反射属性Field
class Student{
    // Field 翻译为字段,其实就是属性成员
    // 4个Field,分别采用不同的访问控制权限修饰符
    public int no;
    private String name;
    protected int age;
    boolean sex;

    public static final double MIN_PI = 3.1415926;
    
}

6.2,通过反射机制操作一个对象的属性(Field)

给属性赋值set, 获取属性的值get。

public class ReflectTest07 {
    public static void main(String[] args) throws Exception {
        // 使用反射机制获取/修改一个对象属性的值
        Class cStudent = Class.forName("com.javaSE.reflects.Student");
        Object obj = cStudent.newInstance(); // obj就是Student对象。(底层调用无参构造方法)

        // 获取no属性 (根据属性的名称获取Field)
        Field noField = cStudent.getField("no");
        // 给obj对象的no属性赋值
        noField.set(obj,1234);
        // 读取no属性的值
        System.out.println(noField.get(obj));

        // 获取私有属性name
        Field nameField = cStudent.getDeclaredField("name");
        // 打破封装
        // 这样设置之后,在外部也可以访问private的
        nameField.setAccessible(true);
        // 给name属性赋值
        nameField.set(obj,"kitty");
        // 获取name属性的值
        System.out.println(nameField.get(obj));
    }
}

上篇:JavaSE进阶十 线程二
下篇:JavaSE进阶十一 反射机制二

你可能感兴趣的:(JavaSE进阶十一 反射机制一)