反射

反射

一定要自动动手实践一遍,才能有更深的一些认识。看完这些打算斗胆去看看框架试试,心想应该没有那么难了叭?

参考链接🔗:https://blog.csdn.net/sinat_38259539/article/details/71799078?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-9.nonecase

反射是框架设计的灵魂

使用前提:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

反射的概述

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。

反射就是把Java类中的各种成分映射成一个个的Java对象。一个类有:成员变量、方法、构造方法、包等信息。

使用

先写一个实体类,如HomeHouse(住宿类)

package com.snl.domain;

public class HomeHouse {
    private Integer id;
    private String name;
    private Boolean sale;
}

一、获取Class对象的三种方式

  1. Object ---> getClass(); ——因为所有类都继承Object类,返回一个对象的运行时类
  2. 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
  3. (常用)通过Class类的静态方法:Class.forName(String className)

Demo:

package com.snl.controller;

import com.snl.domain.HomeHouse;

public class TestController {

    /**
     * 获取Class对象
     * 
     * 注意:在运行期间,一个类,只有一个Class对象产生。
     * 常用第三种:一个字符串可以传入也可写在配置文件中等多种方法。
     * 第一种:对象都有了还要反射干什么。
     * 第二种:需要导入类的包,依赖太强,不导包就抛编译错误。
     *
     * @param args
     */
    public static void main(String[] args) {
        // 方式1:Object --->  getClass();
        // new 产生一个HomeHouse对象,一个Class对象
        HomeHouse homeHouse = new HomeHouse();
        // 获取Class对象
        Class<? extends HomeHouse> houseClass1 = homeHouse.getClass();
        // 输出className:com.snl.domain.HomeHouse
        System.out.println(houseClass1.getName());

        // 方式2:任何数据类型都有一个“静态”的class属性
        Class<HomeHouse> houseClass2 = HomeHouse.class;
        // 在运行期间,一个类只有一个Class对象产生
        // 输出:true
        System.out.println(houseClass1 == houseClass2);

        // 方式3:通过Class类的静态方法:Class.forName(String className)
        try {
            Class<?> houseClass3 = Class.forName("com.snl.domain.HomeHouse");
            // 输出:true
            System.out.println(houseClass2 == houseClass3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

二、通过反射获取构造方法并使用

通过Class对象可以获取类中的:构造方法、成员变量、成员方法,并访问成员。

获取构造方法:

  1. 批量的方法

    // 所有"公有的"构造方法
    public Constructor[] getConstructors();
    // 获取所有的构造方法(包括私有、受保护、默认、公有)
    public Constructor[] getDeclaredConstructors();
    
  2. 获取单个的方法

    // 获取单个的"公有的"构造方法:
    public Constructor getConstructor(Class... parameterTypes);
    // 获取"某个构造方法"可以是私有的,或受保护、默认、公有;
    public Constructor getDeclaredConstructor(Class... parameterTypes);
    
  3. 调用构造方法

    Constructor-->newInstance(Object... initargs);
    创建一个构造方法的声明类的新实例对象,并为之调用。
    

Demo:

package com.snl.controller;

import com.snl.domain.HomeHouse;
import java.lang.reflect.Constructor;

public class TestController {
	public static void main(String[] args) throws Exception {
        // 加载Class对象
        Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");

        System.out.println("--获取所有公有构造方法--");
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("--所有构造方法(包括:私有、受保护、默认、公有)--");
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : declaredConstructors) {
            System.out.println(constructor);
        }

        System.out.println("--获取公有、无参的构造方法--");
        // 因为是无参构造方法,所以类型是一个null,不写也可以(注意:这里需要的是一个参数的类型)
        Constructor<?> constructor = clazz.getConstructor(null);
        System.out.println(constructor);

        //调用构造方法
        System.out.println("--调用构造方法--");
        Object obj = constructor.newInstance();
        System.out.println(obj);
        HomeHouse homeHouse = (HomeHouse) obj;
        System.out.println(homeHouse);

        System.out.println("--获取私有构造方法,并调用--");
        constructor = clazz.getDeclaredConstructor(Boolean.class);
        System.out.println(constructor);
        // 暴力访问,忽略掉访问修饰符
        constructor.setAccessible(true);
        Object o = constructor.newInstance(true);
        System.out.println(o);
    }
}

HomeHouse类

package com.snl.domain;

public class HomeHouse {
    // 无参构造方法
	public HomeHouse() {
        System.out.println("调用了公有、无参构造方法执行了");
    }

    // 多个参数构造方法
    public HomeHouse(Integer id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("调用了公有、有2参构造方法执行了");
        System.out.println("id" + id + "姓名:" + name);
    }

	// 一个参数构造方法
    public HomeHouse(String name) {
        this.name = name;
        System.out.println("调用了公有、有1参构造方法执行了");
        System.out.println("姓名:" + name);
    }

    // 受保护的构造方法
    protected HomeHouse(Integer id) {
        this.id = id;
        System.out.println("调用了受保护、有1参构造方法执行了");
        System.out.println("id:" + id);
    }

    // 私有构造方法
    private HomeHouse(Boolean sale) {
        this.sale = sale;
        System.out.println("调用了私有、有1参构造方法执行了");
        System.out.println("sale:" + sale);
    }
}

三、获取成员变量并调用

  1. 批量的
// 获取所有的“公有字段”
Field[] getFields();
// 获取所有字段,包括:私有、受保护、默认
Filed[] getDeclaredFields();
  1. 获取单个的

    // 获取某个"公有的"字段
    public Field getField(String fieldName);
    // 获取某个字段(可以是私有的)
    public Field getDeclaredField(String fieldName);
    
  2. 设置字段的值

    参数说明:

    obj:要设置的字段所在的对象

    value:值

    Field --> public void set(Object obj,Object value);
    

Demo:

package com.snl.controller;

import com.snl.domain.HomeHouse;
import java.lang.reflect.Field;

public class TestController {

	public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");

        System.out.println("--获取所有公有的字段--");
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        System.out.println("--获取所有的字段--");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }

        System.out.println("--获取私有字段name--");
        Field name = clazz.getDeclaredField("name");
        System.out.println(name);

        Object o = clazz.getConstructor().newInstance();
        // 暴力反射,解除私有限定
        name.setAccessible(true);
        name.set(o, "豪宅");
        // 验证
        HomeHouse homeHouse = (HomeHouse) o;
        System.out.println("验证名称:" + homeHouse.getName());
    }
}

四、获取成员方法并调用

  1. 批量的

    // 获取所有"公有方法"(包含了父类的方法也包含Object类)
    public Method[] getMethods();
    // 获取所有的成员方法,包括私有的(不包括继承的)
    public Method[] getDeclaredMethods();
    
  2. 获取单个的

    // 参数:
    // name : 方法名;
    // Class ... : 形参的Class类型对象(注意:是类型)
    public Method getMethod(String name,Class<?>... parameterTypes);
    public Method getDeclaredMethod(String name,Class<?>... parameterTypes);
    
  3. 调用方法

    // 参数说明:
    // obj:要调用方法的对象;
    // args:调用方式时所传递的实参
    Method --> public Object invoke(Object obj,Object... args);
    

Demo:

package com.snl.controller;

import com.snl.domain.HomeHouse;
import java.lang.reflect.Field;

public class TestController {

	public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
        
        System.out.println("--获取所有公有方法--");
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        
        System.out.println("--获取所有方法,包括私有的--");
        methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        System.out.println("--获取公有的show1()的方法--");
        Method m = clazz.getMethod("show1", String.class);
        System.out.println(m);
        // 实例化一个HomeHouse对象
        Object o = clazz.getConstructor().newInstance();
        m.invoke(o, "方块");

        System.out.println("--获取私有的show4方法--");
        m = clazz.getDeclaredMethod("show4", int.class);
        System.out.println(m);
        m.setAccessible(true);
        Object result = m.invoke(o, 20);
        System.out.println("返回值:" + result);
}

HomeHouse类

package com.snl.domain;

public class HomeHouse {
    // 成员方法
    public void show1(String s) {
        System.out.println("调用了 公有的、String参数的show1():s = " + s);
    }

    protected void show2() {
        System.out.println("调用了 受保护的、无参数的show2()");
    }

    void show3() {
        System.out.println("调用了 默认的、无参数的show3()");
    }

    private String show4(int i) {
        System.out.println("调用了 私有的,并且有返回值的,int参数的show4:i = " + i);
        return String.valueOf(i);
    }
}

五、反射main方法

Demo:

package com.snl.controller;

import java.lang.reflect.Method;

public class TestController {
	public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.snl.domain.HomeHouse");
        
		// 获取main方法
        // 第一个参数:方法名称
        // 第二个参数:方法形参的类型
        Method main = clazz.getMethod("main", String[].class);
        
        // 调用main方法
        // 第一个参数:对象类型,因为方法是static静态的,可传null
        // 第二个参数:String数组,jdk1.4时是数组,jdk1.5后是可变参数,这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
//        main.invoke(null, (Object)new String[]{"a", "b", "c"});
        main.invoke(null, new Object[]{new String[]{"a", "b", "c"}});
    }

HomeHouse

package com.snl.domain;

public class HomeHouse {
    public static void main(String[] args) {
        System.out.println("HomeHouse的main方法执行了");
    }
}

六、通过反射运行配置文件内容

利用反射和配置文件,可以使应用程序更新时,对源码无需进行任何修改。只需将新类发送给客户端,并修改配置文件即可。

Demo:

package com.snl.controller;

import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;

public class TestController {
	public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName(getValue("className"));
        Method method = clazz.getMethod(getValue("methodName"));
        method.invoke(clazz.getConstructor().newInstance());
    }

    /**
     * 接收key,在配置文件中获取相应的value
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static String getValue(String key) throws IOException {
        Properties properties = new Properties();
        FileReader fileReader = new FileReader("D:\\snl\\learnspace\\mytest\\src\\main\\java\\com\\snl\\config\\pro.txt");
        properties.load(fileReader);
        fileReader.close();
        return properties.getProperty(key);
    }

配置文件以txt为例(pro.txt):

className = com.snl.domain.HomeHouse
methodName = show

HomeHouse

package com.snl.domain;

public class HomeHouse {
    public void show(){
        System.out.println("is show()");
    }
}

通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的。

package com.snl.controller;

import java.lang.reflect.Method;
import java.util.ArrayList;

public class TestController {
	
    // 通过反射越过泛型检查
    // 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值
    public static void main(String[] args) throws Exception{
        ArrayList<String> stringList = new ArrayList<String>();
        stringList.add("aaa");
        stringList.add("bbb");

        Class listClass = stringList.getClass();
        Method m = listClass.getMethod("add", Object.class);
        m.invoke(stringList,100);

        for (Object s : stringList) {
            System.out.println(s);
        }
    }
}
posted @ 2020-06-30 16:30  shinl00  阅读(73)  评论(0编辑  收藏  举报