Bootstrap

Java进阶第十章——反射机制

反射机制

1.反射机制概述

  • 反射机制相关的类:

    代表字节码文件,代表一个类型:java.lang.Class

    代表字节码中方法字节码,代表类中方法:java.lang.reflect.Method

    代表字节码中构造方法字节码,代表类中构造方法:java.lang.reflect.Constructor

    代表字节码中属性字节码,代表类中成员变量:java.lang.reflect.Field

//java.lang.Class
public class User{
    //java.lang.reflect.Field
    int no;
    //java.lang.reflect.Constructor
    public User(){ }
    //java.lang.reflect.Method
    public void setNo(int no){ this.no = no; }
}

2.反射类Class

  • 要操作一个类字节码,首先需要获取这个类的字节码。获取Class三种方式:

    第一种:Class.forName()

    这是一种静态方法、方法参数是一个字符串、字符串是一个完整类名、完整类名必须带有包名。

    public class ReflectTest {
        public static void main(String[] args) {
            //Class.forName()方法
            try {
                Class c1 = Class.forName("java.lang.String");
                Class c2 = Class.forName("java.util.Date");
                Class c3 = Class.forName("java.lang.Integer");
                Class c4 = Class.forName("java.lang.System");
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    第二种:java中的任何对象中的getClass()方法

    public class ReflectTest {
        public static void main(String[] args) {
            //java中所有对象都有一个方法:getClass()
            String s = "abc";
            Class x = s.getClass();  //x代表String.class字节码文件,x代表String类型。
        }
    }
    

    第三种:java语音中任何一种类型,包括基本数据类型,都有.Class属性

    public class ReflectTest {
        public static void main(String[] args) {
            Class z = String.class;
            Class k = Date.class;
            Class f = int.class;
            Class e = double.class;
        }
    }
    
  • 通过反射机制获取Class,通过Class.newInstance()实例化对象。

public class ReflectTest {
    public static void main(String[] args) {
        try {
            //通过反射机制获取Class,通过Class实例化对象
            Class c = Class.forName("reflect.User");
            //newInstance()会调用类的无参构造方法完成对象创建。即实例化对象。
            //必须保证无参构造必须存在。
            Object obj = c.newInstance();
            System.out.println(obj);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
public class User {
    public User() {
        System.out.println("无参构造方法");
    }
    //如果这里不写无参构造方法,直接写有参构造方法,无参构造方法就没了,会使newInstance()报错
}
  • java代码写一遍,在不改变java源代码的基础上,可以做到不同对象的实例化。
//验证反射机制的灵活性
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        
        //这种方式是写死的,只能创建User类型对象
        User user = new User();
        
        //以下方式是灵活的,代码不需要改动,只需要修改配置文件就能创建不同实例对象。
        //通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("D:\\study\\study5\\study5\\classinfo.properties");
        //创建属性类对象Map
        Properties pro =new Properties();  //key和value都是String
        //加载
        pro.load(reader);
        //关闭流
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");

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

创建的classinfo.properties文件

className=reflect.User
#测试其他类:
#className=java.util.Date
#className=java.lang.String
  • 以后学习的高级框架如Spring、SpringMVC、MyBatis……底层实现原理都采用了反射机制。
  • 在调用Class.forName会直接执行会导致类加载,类加载时会执行静态代码块。
  • 当只希望一个类静态代码块执行,其他代码不执行。可以直接调用Class.forName(“完整类名”);
public class ReflectTest {
    public static void main(String[] args) {
        try {
            //这个直接执行会导致类加载,类加载时会执行静态代码块。
            Class.forName("reflect.MyClass");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

    }
}
class MyClass{
    //静态代码块在加载时执行,并且执行一次
    static {
        System.out.println("MyClass静态代码块执行了");
    }
}
  • 获取类的根路径(src)下的某个文件的绝对路径:Thread.currentThread().getContextClassLoader().getResource(“从根目录开始,到目标文件的路径”).getPath()
public class AboutPath {
    public static void main(String[] args) throws Exception {
        //通过IO流读取classinfo.properties文件
        //以下这种方法如果代码移植了找不到这个文件
        //FileReader reader = new FileReader("D:\\study\\study5\\study5\\classinfo.properties");

        //通用的路径,即使代码换位置了还能读到这个文件
        //注:这个文件必须在类路径下,即src下。src是类的根路径。

        //当前线程对象Thread.currentThread()
        //线程对象的方法,获取到当前线程的类加载器对象getContextClassLoader()
        //类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源getResource()
        //使用这里获取文件的绝对路径
        //获取src/classinfo.properties文件
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo.properties").getPath();
        //getResource("")这里填根路径开始到获取的资源的路径
        System.out.println(path);
        
        //获取src/bean/db.properties文件
        String path2 = Thread.currentThread().getContextClassLoader()
                .getResource("bean/db.properties").getPath();
        //getResource("")这里填根路径开始到获取的资源的路径
        System.out.println(path2);
    }
}
  • 以流的形式直接返回
public class IoPropertiesTest {
    public static void main(String[] args) throws Exception{
        //先拿到流的路径,以流形式返回
        //String path = Thread.currentThread().getContextClassLoader().getResource("classinfo.properties").getPath();
        //FileReader reader = new FileReader(path);

        //直接以流的形式返回
        InputStream reader = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("classinfo.properties");
        
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);
    }
}
  • java.util包下提供了一个资源绑定器,便于获取属性配置文件中内容。属性配置文件xxx.properties必须放到类路径下。
public class ResourceBundleTest {
    public static void main(String[] args) {
        //资源绑定器,只能绑定xxx.properties文件,且必须在类路径下。
        //在写路径时,路径后面的扩展名不能写
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");

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

3.反射属性Field

  • 获取Field属性

    Field[] getFields():获取所有带public修饰符的Field

    Field[] getDeclaredFields():获取所有的Field

    int getModifiers():获取修饰符,用int返回

    Modifier.toString(int):将int类型转换为修饰符名称

    Class<> getType():获取Field属性的类型

    String getName():获取Field属性的名字

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

}

package reflect;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
//反射Student类中所有Field
public class ReflectTest {
    public static void main(String[] args) throws Exception{
        //获取整个类
        Class studentClass = Class.forName("bean.Student");

        //完整类名
        System.out.println("完整类名:" + studentClass.getName());//完整类名:bean.Student
        //简类名
        System.out.println("简类:" + studentClass.getSimpleName());//简类:Student

        //获取类中所有的public修饰的Field
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length);//1
        System.out.println(fields[0]);  // public int bean.Student.no
       

        //获取所有的Field
        Field[] fs = studentClass.getDeclaredFields();
        System.out.println(fs.length);  //4
        for (Field field:
             fs) {
            //获取修饰符
            //field.getModifiers()返回的是int类型
            //通过Modifier.toString()转换为修饰符
            System.out.printf(Modifier.toString(field.getModifiers()) + " ");
            //获取属性类型
            System.out.printf(field.getType().getSimpleName() + "   ");
            //获取属性名字
            System.out.printf(field.getName());
            System.out.println();
            
            //public int   no
			//private String   name
			//protected int   age
 			//boolean   sex
        }
    }
}
  • 反编译Field
//通过反编译机制,反编译一个类的属性Field
public class ReflectTest {
    public static void main(String[] args) throws Exception{

        //创建用来拼接的字符串
        StringBuilder s = new StringBuilder();

        //获取整个类
        //还可以拿到java.lang.String等
        Class studentClass = Class.forName("bean.Student");

        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
        Field[] fields = studentClass.getDeclaredFields();
        for (Field field:
             fields) {
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            s.append(field.getType().getSimpleName());
            s.append(" ");
            s.append(field.getName());
            s.append("\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
  • 通过反射机制访问对象属性
//通过反射机制访问java对象的属性
public class ReflectTest {
    public static void main(String[] args) throws Exception{

        //不使用反射机制,访问一个对象的属性
        Student s = new Student();
        //属性赋值
        s.no=1111;//要给一个属性赋值需要三要素:对象、属性、值
        //读取属性值
        System.out.println(s.no);

        //获取整个类
        Class studentClass = Class.forName("bean.Student");
        Object obj = studentClass.newInstance(); //底层调用无参构造方法创建student对象

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

        //要给一个属性赋值需要三要素:对象、属性、值
        //给obj对象(Student)对象的no属性赋值
        noField.set(obj,22222); //给obj对象的no属性赋值22222

        //读取属性值
        System.out.println(noField.get(obj));

        //访问私有属性
        Field nameField = studentClass.getDeclaredField("name");
        //打破封装
        nameField.setAccessible(true);
        nameField.set(obj,"coffee");
        System.out.println(nameField.get(obj));
    }
}

4.可变长参数

  • 可变长参数:int… args(类型… )

    可变长参数的参数个数是:0~N个都可以

    可变长参数在参数列表中必须在最后一个位置上,并且可变长度参数只能有1个。

    可变长参数可以当做一个数组来看待。

public class ArgsTest {
    public static void main(String[] args) {
        m();
        m(10);
        m(10,20);
        m2(10,"abc");
        m3("ice","coffee");
        String[] sts = {"a","b","c"};
        m3(sts);
        m3(new String[]{"ice","coffee"});
    }
    public static void m(int... args){
        System.out.println("m方法执行了");
    }
    //可变长参数在参数列表只能放在最后一个位置上,且只有一个
    public static void m2(int a ,String... args){
        System.out.println("m2方法执行了");
    }
    //可以将可变长参数作为数组来看
    public static void m3(String... args){
        //args有length属性
        for (int i = 0; i < args.length; i++) {
            System.out.println(args[i]);
        }
    }
}

5.反射方法Method

  • 反编译获取方法类型、名称等
public class ReflectTest {
    public static void main(String[] args) throws Exception{
        //获取类
        Class userServiceClass = Class.forName("service.UserService");
        //获取所有的Method
        Method[] methods = userServiceClass.getDeclaredMethods();
        System.out.println(methods.length);  //2

        //遍历Method
        for (Method method:
             methods) {
            //获取修饰符列表
            System.out.printf(Modifier.toString(method.getModifiers()) + " ");
            //获取方法类型
            System.out.printf(method.getReturnType().getSimpleName() + " ");
            //获取方法名称
            System.out.printf(method.getName());
            //获取方法参数列表
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameterType:
                    parameterTypes) {
                System.out.printf(" "+parameterType.getSimpleName());
            }
            System.out.println();
        }
    }
}
  • 通过反射机制调用方法
package service;
public class UserService {
    /**
     * 登录方法
     * @param name 用户名
     * @param password 密码
     * @return true表示登录成功,false表示登录失败
     */
    public boolean login(String name,String password){
        if("admin".equals(name) && "123".equals(password)){
            return true;
        }
        return false;
    }
    public void logout(){
        System.out.println("系统已经安全退出");
    }
}

package reflect;
import service.UserService;
import java.lang.reflect.Method;
public class ReflectTest {
    public static void main(String[] args)  throws Exception {
        //不使用反射机制,调用方法
        //调用方法要素:对象、方法名、实参列表、返回值
        UserService userService = new UserService();
        boolean loginSuccess = userService.login("admin","123");
        System.out.println(loginSuccess? "登录成功" : "登录失败");

        //使用反射机制调用对象的方法
        Class userserviceClass = Class.forName("service.UserService");
        //创建对象
        Object obj = userserviceClass.newInstance();
        //获取Method
        Method loginMethjod = userserviceClass.getDeclaredMethod("login",String.class,String.class);
        //调用方法要素:对象、方法名、实参列表、返回值
        Object retValue = loginMethjod.invoke(obj,"admin","123");
        System.out.println(retValue);
    }
}

6.反射构造方法Constructor

  • 反编译构造方法
//定义一个多个构造方法的类
public class Vip {
    int no;
    String name;
    String birth;
    boolean sex;
    public Vip() { }

    public Vip(int no) { this.no = no;  }

    public Vip(int no, String name) {
        this.no = no;
        this.name = name;
    }
    public Vip(int no, String name, String birth) {
        this.no = no;
        this.name = name;
        this.birth = birth;
    }
    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Vip{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", birth='" + birth + '\'' +
                ", sex=" + sex +
                '}';
    }
}
//反编译构造方法
public class ReflectTest {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("bean.Vip");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        //拼接构造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for (Constructor constructor:
             constructors) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            //拼接参数
            Class[] paramterTypeds = constructor.getParameterTypes();
            for (Class parameterType:
                 paramterTypeds) {
                s.append(parameterType.getSimpleName());
                s.append(",");

            }
            if (paramterTypeds.length>0){
                s.deleteCharAt(s.length()-1);
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}
  • 通过反射机制创建对象
public class ReflectTest11 {
    public static void main(String[] args) throws Exception{
        //不使用反射机制创建对象
        Vip v1 = new Vip();
        Vip v2 = new Vip(110,"coffee","2001-06-21",true);

        //使用反射机制创建对象
        Class c = Class.forName("bean.Vip");
        //调用无参数构造方法
        Object obj = c.newInstance();
        //调用有参构造方法
        Constructor con =  c.getDeclaredConstructor(int.class,String.class,String.class,boolean.class);
        //调用构造方法new对象
        Object newObj = con.newInstance(110,"ice","2001-06-21",true);
        System.out.println(newObj);  //Vip{no=110, name='ice', birth='2001-06-21', sex=true}
    }
}

7.获取父类

public class ReflectTest12 {
    public static void main(String[] args) throws Exception{
        //String举例
        Class stringClass = Class.forName("java.lang.String");
        //获取String的父类
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName());  //java.lang.Object

        //获取String类实现所有接口
        Class[] interfaces = stringClass.getInterfaces();
        for (Class in : interfaces
                ) {
            System.out.println(in.getName());
            /**
             * java.io.Serializable
             * java.lang.Comparable
             * java.lang.CharSequence
             */
            
        }
    }
}

——本章节为个人学习笔记。学习视频为动力节点Java零基础教程视频:动力节点—JAVA零基础教程视频

;