Java反射:框架设计的灵魂

1.1 反射概述

反射是一种机制/功能,利用该机制/功能可以在程序运行过程中对类进行解剖并操作类中的构造方法,成员方法,成员属性。

1.1.1 反射的使用场景

  1. 开发工具中写代码时的提示

    开发工具之所能够把该对象的方法和属性展示出来就使用利用了反射机制对该对象所有类进行了解剖获取到了类中的所有方法和属性信息

  2. 各种框架的设计【SSM=Spring+SpringMVC+MyBatis】

框架:半成品软件。可以在框架的基础上进行软件开发,简化编码

1.1.2 反射的好处

  1. 可以在程序运行过程中,操作这些对象。
  2. 可以解耦,提高程序的可扩展性。

1.2 类的加载

我们要使用反射,那么就得研究类的对象是如何产生,一个class字节码文件是如何加载到内存中的,这是使用反射机制解剖类的前提。

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

1. 加载

  1. 就是指将class文件读入内存,并为之创建一个Class对象。
  2. 任何类被使用时系统都会建立一个Class对象

2. 连接

  1. 验证是否有正确的内部结构,并和其他类协调一致
  2. 准备负责为类的静态成员分配内存,并设置默认初始化值
  3. 解析将类的二进制数据中的符号引用替换为直接引用

3. 初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量【静态变量】进行初始化。
在 Java 类中对类变量指定初始值有两种方式:
​ ① 声明类变量时指定初始值;
​ ② 使用静态代码块为类变量指定初始值。

当一个类完成加载后就会产生一个该类型的Class对象,我们要使用反射技术,就得使用该Class对象。如何获取该类型的Class对象呢?请看下节内容。

1.3 获取Class对象的方式

获取class对象作用应用场景
Class.forName(“全类名”)通过指定的字符串路径获取多用于配置文件,将类名定义在配置文件中。读取文件,加载类
类名.class通过类名的属性class获取多用于参数的传递
对象.getClass()通过对象的getClass()方法获取多用于对象的获取字节码的方式

注意:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

@Test
public void testClass() throws ClassNotFoundException {
    //1.Class.forName("全类名")
  	//全类名路径:含有包名的全类名
    Class<?> cls1 = Class.forName("com.itheima.demo01Reflect.User");
    //2.类名.class
    Class<User> cls2 = User.class;
    //3.对象.getClass()
    Class<? extends User> cls3 = new User().getClass();

    //类只加载一次,内存中只会存在一份User 文件
    System.out.println(cls1 == cls2); // true
    System.out.println(cls1 == cls3); // true
}

1.4 获取Class对象信息

1.4.1 获取类名信息

我们已经获取了Class对象了,接下来就介绍几个Class类中常用的方法了。

  1. String getSimpleName(); 获得简单类名,只是类名,没有包
  2. String getName(); 获取完整类名,包含包名+类名
  3. T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
@Test
public void testClass2() throws IllegalAccessException, InstantiationException {
    //获取User类的Class对象
    Class<User> cls = User.class;
    //1. String getSimpleName(); 获得简单类名,只是类名,没有包
    String simpleName = cls.getSimpleName();
    System.out.println("simpleName = " + simpleName); //User
    //2. String getName(); 获取完整类名,包含包名+类名
    String name = cls.getName();
    System.out.println("name = " + name);// com.itheima.demo01Reflect.User
}

一开始在阐述反射概念的时候,我们说到利用反射可以在程序运行过程中对类进行解剖并操作里面的成员。而一般常操作的成员:构造方法,成员方法,成员属性,那么接下来看看怎么利用反射来操作这些成员以及操作这些成员能干什么,先来看看怎么操作构造方法。

1.4.2 获取类中构造器

要通过反射操作类的构造方法,我们需要先知道一个Constructor类。Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。

Constructor类概述

Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。

[外链图片转存失败(img-RsIwko2J-1566217928466)(assets/1556092262925.png)]

Class中获取Constructor的方法
  1. Constructor[] getConstructors()

    获取所有的public修饰的构造方法

  2. Constructor getConstructor(Class... parameterTypes)

    根据参数类型获取构造方法对象,只能获得public修饰的构造方法。如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。

    参数是可变参数,调用此方法时,可以不写参数,获取的空参构造可以写参数,给定的参数必须是Class对象
    比如:
    参数	类名(String name,int age)
    调用此方法: String.class,int.class 
    

    例如,一已知存在User类如下:

    public class User {
        int age 
        String name;
    
        public User(){}
    
        public User(String name,int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    1. 获取所有public构造方法

    2. 获取public修饰的空参构造方法对象

    3. 获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象

      @Test
      public void testConstructor() throws NoSuchMethodException {
          Class<User> cls = User.class;
          //1. 获取所有的public构造方法
          Constructor<?>[] cons = cls.getConstructors();
          for (Constructor<?> con : cons) {
              System.out.println("con = " + con);
          }
          System.out.println("========");
          //2. 获取public修饰的空参构造方法对象
          Constructor<User> con1 = cls.getConstructor();
          System.out.println("con1 = " + con1);
          System.out.println("========");
          //3. 获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
          Constructor<User> con2 = cls.getConstructor(String.class, int.class);
          System.out.println("con2 = " + con2);
      }
      
Constructor类中常用方法
  1. T newInstance(Object… initargs) 根据指定参数创建对象。
  2. T newInstance() 空参构造方法创建对象。
@Test
public void test3() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Class<User> cls = User.class;

    //1. 获取public修饰的空参构造方法对象
    Constructor<User> con1 = cls.getConstructor();
    User u1 = con1.newInstance();
    System.out.println("name="+u1.name+":age="+u1.age);

    System.out.println("========");
    //2. 获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
    Constructor<User> con2 = cls.getConstructor(String.class, int.class);
    User u2 = con2.newInstance("小强", 10);
    System.out.println("name="+u2.name+":age="+u2.age);

}

1.4.3 获取类中方法

Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。

Class类获取Method相关方法
1. Method[] getMethods()
获取所有的public修饰的成员方法,包括父类中

2. Method[] getDeclaredMethods()
获取当前类中所有的方法,包含私有的,不包括父类中

3. Method getMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,只能是获取public修饰的

4. Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,包括private修饰的

Method类中常用方法
1. Object invoke(Object obj, Object... args)
根据参数args调用对象obj的该成员方法
如果obj=null,则表示该方法是静态方法

2. void setAccessible(true)
暴力反射,设置为可以直接调用私有修饰的成员方法【对于私有方法使用前需要调用该方法】

编写代码演示

存在User类:

User.java
/**
 * 用户 JavaBean
 */
public class User {
    //项目开发中,字段和属性名相同
    private int id;//编号
    private String name;//姓名
    private double sal;//薪水

    public User(){}
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSal() {
        return sal;
    }
    public void setSal(double sal) {
        this.sal = sal;
    }
}

/**
 * 获取Class对象的Method信息
 */
public class Demo04 {

    /**
     * 演示Method[] getMethods()
     * JUnit单元测试
     */
    @Test
    public void test1() {
        Class clazz = User.class;
        Method[] ms = clazz.getMethods();
        for(Method m:ms){
            System.out.println(m);
        }
    }

    /**
     * 演示Method[] getDeclaredMethods()
     */
    @Test
    public void test2(){
        Class clazz = User.class;
        Method[] ms = clazz.getDeclaredMethods();
        for(Method m:ms){
            System.out.println(m);
        }
    }

    /**
     * 演示Method getMethod("方法名", 方法的参数类型... 类型)
     */
    @Test
    public void test3() throws Exception{
        Class clazz = User.class;
        //参数一:方法名
        //参数二:方法名中参数的Class类型,如果无的话,书写null
        Method m = clazz.getMethod("set",null);
        System.out.println(m);
    }

    /**
     * 演示Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
     */
    @Test
    public void test4() throws Exception{
        Class clazz = User.class;
        Object obj = clazz.newInstance();
        Method m = clazz.getDeclaredMethod("show",null);
        System.out.println(m);
    }

    /**
     * 演示Object invoke(Object obj, Object... args)
     */
    @Test
    public void test5() throws Exception{
        Class clazz = User.class;
        Object obj = clazz.newInstance();
        //参数一:方法名
        //参数二:方法名中参数的Class类型,如果无的话,书写null
        Method m = clazz.getMethod("set",null);
                //调用方法
        //参数一:对象
        //参数二:实际参数,如果无的话,书写null
        m.invoke(obj,null);
    }

    /**
     * 演示void setAccessible(true)
     */
    @Test
    public void test6()throws Exception{
        Class clazz = User.class;
        Object obj = clazz.newInstance();
        Method m = clazz.getDeclaredMethod("show",null);
        //暴力反射
        m.setAccessible(true);
        m.invoke(obj,null);
    }

1.4.4 获取类中属性

Field类概述

Field是属性类,类中的每一个属性都是Field的对象,通过Field对象可以给对应的属性赋值和取值。

Class类中与Field相关方法
1. Field[] getFields()
获取所有的包括父类中public修饰的属性对象,返回数组

2. Field[] getDeclaredFields()
获取所有本类属性对象,包括private修饰的,返回数组

3. Field getField(String name)
根据属性名获得属性对象,只能获取public修饰的

4. Field getDeclaredField(String name)
根据属性名获得属性对象,包括private修饰的
Filed类中的方法
1. Object get(Object obj) 
  返回指定对象上此 Field 表示的字段的值。 
  
2. void set(Object obj, Object value) 
  将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 
3. void setAccessible(true);
	暴力反射,设置为可以直接访问私有类型的属性

例如:已知类User,通过反射的方式获取属性

public class User {

    //项目开发中,字段和属性名相同

    private int id;//编号
    private String name;//姓名
    private double sal;//薪水

    public User(){}
		//省略 getter/setter方法
}
/**
 * 获取Class对象的Field信息
 */
public class Demo05 {

    /**
     * 演示Field[] getFields()
     */
    @Test
    public void test1() throws Exception{
        Class clazz = User.class;
        Field[] fs = clazz.getFields();
        for (Field f : fs) {
            System.out.println(f);
        }
    }

    /**
     * 演示Field[] getDeclaredFields()
     */
    @Test
    public void test2()throws Exception{
        Class clazz = User.class;
        Field[] fs = clazz.getDeclaredFields();
        for (Field f : fs) {
            System.out.println(f);
        }
    }

    /**
     * 演示Field getField(String name)
     */
    @Test
    public void test3()throws Exception{
        Class clazz = User.class;
        //获取某个Field对象
        //参数一:字段名
        Field f = clazz.getField("id");
        System.out.println(f);
    }

    /**
     * 演示Field getDeclaredField(String name)
     */
    @Test
    public void test4()throws Exception{
        Class clazz = User.class;
        Field f = clazz.getDeclaredField("sal");
        System.out.println(f);
    }

    /**
     * 演示set/get()
     */
    @Test
    public void test5()throws Exception{
        Class clazz = User.class;
        Object obj = clazz.newInstance();
        //获取某个Field对象
        //参数一:字段名
        Field f = clazz.getField("id");
        //为id字段设置值
        //参数一:对象
        //参数二:实际值
        f.setInt(obj,2018);
        //从id字段中获取值
        System.out.println(f.getInt(obj));
    }

    /**
     * 演示void setAccessible(true)
     */
    @Test
    public void test6()throws Exception{
        Class clazz = User.class;
        Object obj = clazz.newInstance();
        //f指向私有成员变量
        Field f = clazz.getDeclaredField("sal");
        //暴力反射
        f.setAccessible(true);
        f.setDouble(obj,5555.55);
        System.out.println(f.getDouble(obj));

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值