Loading

Java学习笔记-反射机制

通过反射机制可以操作 .class 字节码文件
反射机制,让代码具有通用性,可变化的内容都是写到配置文件当中
将来只需要修改配置文件,创建的对象不一样,调用的方法也不一样
但是java代码不需要做任何的改动

反射机制相关类:

java.lang.Class:代表字节码文件

java.lang.reflect.*;
java.lang.reflect.Method : 代表字节码中方法的字节码
java.lang.reflect.Constructor:代表构造方法的字节码
java.lang.reflect.Field:代表属性字节码

获取Class的三种方式

  • Class c = Class.forName("完整类名");
  • Class c = 对象.getClass();
  • Class c = 类名.class;

重要方法:

  • public T newInstance() :通过Class对象调用,实例化对象

示例方法:

public class ReflectTest01 {
    public static void main(String[] args) throws Exception {
        
        // Class.forName()
        //1. 静态方法
        //2. 方法的参数是一个字符串,字符串需要的是一个完整类名
        Class c1 = Class.forName("java.lang.String");
        Class c2 = Class.forName("java.util.Date");
        
        //Object中的方法: getClass()
        String s = "abc";
        Class x = s.getClass();
        System.out.println(c1 == x);
        
        // 第三种方式:静态属性
        Class z = String.class;
        Class k = Date.class;
        
        //重要方法:public T newInstance()
        //功能:这个方法对应类的无参数构造方法,完成对象的创建
        Object obj = c1.newInstance();
        System.out.println(obj);

    }
}

如果只想让一个类的”静态代码块“执行

public class ReflectTest04 {
    public static void main(String[] args) {
        try{
            //执行的时候,类会被加载到方法区中,而类加载时静态代码块会执行
            //通过这种方式,可以只让静态代码块执行
            Class.forName("Reflect.User");
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

class User{
    static {
        System.out.println("静态代码块执行");
    }
}

文件路径问题

相对路径:不同编译器的相对路径不同
绝对路径:不同电脑可能存放的也不同,不同的系统的绝对路径也不同

比较通用的方式:即使代码换了位置也能找到

  • 使用前提:这个文件必须在类路径下
  • 类路径:D:/biancheng/java/file/chapter19/out/production/chapter23/Reflect/CreatObject/properties
  • 可以看到类路径是out/production目录下的,编译器中的"src"代指这个目录
  • 在编译器中新建的文件是保存在这个目录下的,但是.java源文件不是保存在个目录下的

示例程序

public class AboutPath {
    public static void main(String[] args) {
        
        // Thread.currentThread()当前线程对象
        // getContextClassLoader() 是线程对象的方法,可以获取到当前对象的类加载器
        // getResource() 类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
            
        //获取一个文件的绝对目录,该文件必须放在类路径下
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("Reflect//CreatObject//properties").getPath();
        System.out.println(path);     //path是该文件的绝对路径

        //直接以一个输入流的形式返回
        InputStream in = Thread.currentThread().getContextClassLoader()
                      .getResourceAsStream("Reflect/CreatObject/properties")
    }
}

通过读取配置文件来创建对象

通过上述方式读取文件:

示例程序:

//Reflect/CreatObject/properties文件
className=Reflect.ReflectTest01
public class CreateObj {
    public static void main(String[] args) throws Exception {

        //升级:以前直接写路径的方式都需要修改成这样的方式获取路径,提高程序的可移植性
        // String path = Thread.currentThread().getContextClassLoader().getResource("Reflect//CreatObject//properties").getPath();
        // FileReader fr = new FileReader(path);

        InputStream fr = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("Reflect//CreatObject//properties");
        //创建属性类对象Map
        Properties pro = new Properties();
        //加载
        pro.load(fr);
        //关闭流
        fr.close();

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

        //通过反射机制实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

通过资源绑定器读取.properties配置文件

  • java.util包下提供了一个资源绑定器,便于获得属性配置文件中的内容
  • 使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下
  • 只能绑定在xxx.properties文件
  • 并且在写路径的时候,路径后面的扩展名不能写

示例程序:

//Reflect/classinfo.properties配置文件
className=Reflect.ReflectTest01
import java.util.ResourceBundle;

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

        ResourceBundle bundle = ResourceBundle.getBundle("Reflect//classinfo");

        String className = bundle.getString("className");

        Class c = Class.forName(className);

        Object obj = c.newInstance();

        System.out.println(obj);
    }
}

总结:

  • 通过此方式在任何环境下都能读取文件,只要此文件在类目录下
  • 只需要修改配置文件就能创建不同的类,不需要修改任何java源代码
  • 这样编写的程序很灵活,符合OCP原则

(重点)通过反射机制读取/设置类的属性

常用方法:

  • public Class<?> getType() 获取数据类型
    • (Class.java)public String getSimpleName() 返回数据类型的名字
  • public int getModifiers() 获取权限标识符,返回int类型,每一个标识符对应一个数字
    • (MModifier.java)public static String toString(int mod)

示例程序 -读取属性:

package Reflect;

public class Student {
    private String name;
    int age;
    protected double grade;
    public String id;
    public static final double PI = 3.1015;

    public Student() {
    }

    public Student(String name, int age, double grade, String id) {
        this.name = name;
        this.age = age;
        this.grade = grade;
        this.id = id;
    }

    public void show(){
        System.out.println("无参数show方法执行");
    }

    public void show(int x,String s){
        System.out.println("有参数show方法执行");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", grade=" + grade +
                ", id='" + id + '\'' +
                '}';
    }
}
public class ReflectTest05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取整个类(完整类名)
        Class c = Class.forName("Reflect.Student");
        
        String name = c.getName();     //获取完整类名
        String simpleName = c.getSimpleName();


        //获取类中所有的public修饰的Field(属性)
        Field[] fields = c.getFields();

        System.out.println(fields.length);  //1
        System.out.println(fields[0]);      //public java.lang.String Student.id

        //=======================================
        Field[] fs = c.getDeclaredFields();
        for(Field f : fs){
            Class t = f.getType();                           
            System.out.print(t.getSimpleName() + "    ");    
            System.out.print(f.getModifiers() + "    ");     
            System.out.print(Modifier.toString(f.getModifiers()) + "    ");   
            System.out.println(f.getName());
        }
    }
}

示例程序 -反编译Field

public class ReflectTest06 {
    public static void main(String[] args) throws ClassNotFoundException {
        //拼接字符串
        StringBuilder s = new StringBuilder();

        Class c = Class.forName("Reflect.Student");

        s.append(Modifier.toString(c.getModifiers())
                        + " class " + c.getSimpleName() + " {\n");

        Field[] fs = c.getDeclaredFields();
        for(Field f : fs){
            s.append("\t");
            //获取权限
            s.append(Modifier.toString(f.getModifiers()));
            s.append(" ");
            //获取类型
            s.append(f.getType().getSimpleName());
            s.append(" ");
            //获取名字
            s.append(f.getName());
            s.append(";\n");
        }
        s.append("}");

        System.out.println(s);
    }
}

示例程序 -修改属性

  • Field getDeclaredField(String name) 根据属性名获取属性,private也可以获取
  • void setAccessible(boolean flag) 打破封装,使得在外部也可以修改private修饰的属性值
  • void set(Object obj, Object value) 设置属性值,三要素:对象、属性、值
  • Object get(Object obj) 获取属性的值,两要素:对象、属性
public class ReflectTest07 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("Reflect.Student");
        Object obj = c.newInstance();

        //获取属性 -根据名称
        Field f = c.getDeclaredField("name");

        //打破封装,
        f.setAccessible(true);

        //对象:obj   属性:f   值:"zhangsan"
        f.set(obj,"zhangsan");
        
        System.out.println(f.get(obj));
    }
}

通过反射机制调用方法

可变长度参数

  • 语法:类型... args

    1. 可变长度参数要求的参数个数:0~N
    1. 可变长度参数必须在参数列表的最后一个位置上,且只能有一个
    1. 可变长度参数可以当作一个数组来看待
public class ArgsTest {
    public static void main(String[] args) {
        m(2,"ad","ap");

        String[] s = new String[]{"aa","bb","cc"};
        m(3,s);
    }

    public static void m(int a, String...args){
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}

调用普通方法

  • Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取目标方法的对象, 参数:方法名,方法参数列表
  • public Object invoke(Object obj, Object... args) 调用目标方法,四要素:方法对象、对象、实参、返回值(执行方法的返回值)

示例程序

public class ReflectTest08 {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("Reflect.Student");

        Object obj = c.newInstance();
        
        //方法名:show
        Method m = c.getDeclaredMethod("show",int.class, String.class);

        //方法:m   对象:obj    实参:args...   返回值:retValue
        Object retValue = m.invoke(obj,12,"34");

        System.out.println(retValue);
    }
}

调用有参数构造方法

  • Constructor getDeclaredConstructor(Class<?>... parameterTypes) 获取构造方法的构造器
  • T newInstance(Object ... initargs) 调用构造方法,返回实例化后的对象
public class ReflectTest09 {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("Reflect.Student");

        //调用无参数构造方法(要保证无参数构造方法存在)
        Object obj1 = c.newInstance();

        //调用无参数构造方法
        //第一步:获取此有参数构造方法
        Constructor con = c.getDeclaredConstructor(String.class, int.class, double.class, String.class);
        //第二步:调用构造方法new对象
        Object obj2 = con.newInstance("zhangsan",12,34,"1111");
        System.out.println(obj2);
    }
}

获取类的父类或者接口

示例程序:

public class ReflectTest10 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("java.lang.String");

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

        //获取String类实现的接口
        Class[] is = c.getInterfaces();
        for(Class i : is){
            System.out.println(i.getName());
        }
    }
}
posted @ 2020-06-05 14:42  Krocz  阅读(151)  评论(0编辑  收藏  举报