2021-10-11 每日一记--反射

反射

参考文章Java基础之—反射(非常重要)

反射是框架设计的灵魂虽然时至今日我还没学到框架,但我还是顺藤摸瓜发现了反射,决定好好学习一下。


一,反射的概述

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

想要解剖一个类,必须要获取到该类的字节码文件对象。使用Class类中的方法可以解剖类中的方法,属性。

解剖

Class类的get方法可以将一个类的成员变量、方法、构造方法、包等信息,映射为对象返回;

类由属性以及方法构成

如图是类的正常加载过程:

img

当我们用new 创建一个类的实例对象时 java虚拟机Jvm就会将类的字节码文件加载到内存中同时自动产生改类的Class对象 注意:一个类只产生一个Class对象img

通过上图可知

Class类没有公有的构造方法,Class对象时在加载类时java虚拟机以及通过类加载器中的defineClass方法自动构造的。


实现反射的关键就在于Class对象的获取

1.获取Class对象的三种方式

  • Object --> getClass();

返回一个对象的运行时类

  • 任何数据类型(包括基本数据类型)都有一个"静态"的class属性

  • 通过Class类的静态方法:static Class<?> forName(String name)(常用)

String name 代表着类的名字(真实路径、完整名字,包括包名)。

先创建一个***Student***类

package DEVIL.反射;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-19:22
 */
public class Student {
    public static void main(String[] args) {
        System.out.println("main方法执行了");
    }
    //成员变量
    public String name;
    private int age;
    protected char st;
    String num;
    //公有方法 用于输出字段
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", st=" + st +
                ", num='" + num + '\'' +
                '}';
    }

    //默认的有参的构造方法
    Student(String str){
        System.out.println(str+"调用了默认的构造方法");
    }

    //公有的无参的构造方法
    public Student(){
        System.out.println("调用了无参的公用构造方法");
    }

    //公有的有参的构造方法
    public Student(char str){
        System.out.println(str+"调用有参的公用构造方法");
    }

    //公有的有多个参的构造方法
    public Student(String str, int age){
        System.out.println(str+"今年"+age+"岁了");
    }

    //受保护的有参的构造方法
    protected Student(boolean b){
        System.out.println("今天是个好天气是"+b+"的");
    }

    //私有的构造方法
    private Student(int age){
        System.out.println("今年"+age+"岁了");
    }

    public void show1(String name){
        System.out.println("调用了公有的,参数类型为String的方法 name = "+name);
    }
    public void show2(){
        System.out.println("调用了公有的,无参的方法");
    }
    private void show3(int age){
        System.out.println("调用了私有的,参数类型为int的方法 age = "+age);
    }
    void show4(char st){
        System.out.println("调用了默认的,参数类型为char的方法 st = "+st);
    }
    protected void show5(String num){
        System.out.println("调用了受保护的,参数类型为String的方法 num = "+num);
    }
}

演示上述三种方法的使用:

package DEVIL.反射;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-18:53
 */
/*
获取Class对象的三种方式:
1.Object ->getClass();
2.任何数据类型(包括基本数据类型)都有一个相应的class静态属性;
3.通过Class的静态方法static Class<?>	forName(String name);
 */
public class E_01 {
    public static void main(String[] args) {
        //getClass获取Class对象
        Student stu = new Student();
        Class stuClass1 = stu.getClass();
        System.out.println(stuClass1.getName());
        //第二种方法获取Class对象
        Class stuClass2 = Student.class;
        System.out.println(stuClass2.getName());
        //第三种方法获取Class对象
        try {
            Class stuClass3 = Class.forName("DEVIL.反射.Student");//注意forName中的字符串必须是真实的路径(带包名、类名)
            System.out.println(stuClass3==stuClass2);//判断是否获取的同一个Class对象
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注:在运行期间,一个类只有一个Class对象产生

第一中方法是在事先已经创建对象的情况获取Class对象,个人觉得多此一举,都已经有了实例对象了,反射就显得没有意义了。

第二种方法需要导入类的包(指在文件的头部加上类的包的路径)依赖性太强了,不导包就抛编译错误

第三种是最为合理也是最常用的一种,传入的字符串为类的真实路径


2.通过反射获取构造方法并使用

1.获取构造方法:
(1)获取批量的构造方法:
public Constructor[] getConstructors(): 获取所有的公有的构造方法;
public Constructor[] getDeclaredConstructors(): 获取所有的构造方法(包括私有、受保护和默认);
(2)获取单个指定的构造方法:
public Constructor[] getConstructor(Class… parameterTypes):获取单个的指定参数的公有的构造方法;
public Constructor[] getDeclaredConstructor(Class… parameterTypes):获取单个的指定参数的构造方法(可以是私有、受保护和默认);

       调用构造方法:
       Constructor-->newInstance(Object... initargs);
package DEVIL.反射;

import java.lang.reflect.Constructor;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-19:42
 */
/*
通过Class对象可以获取对应类中的:构造方法、成员变量、成员方法和访问成员;
1.获取构造方法:
        (1)获取批量的构造方法:
            public Constructor[] getConstructors(): 获取所有的公有的构造方法;
            public Constructor[] getDeclaredConstructors(): 获取所有的构造方法(包括私有、受保护和默认);
        (2)获取单个指定的构造方法:
            public Constructor[] getConstructor(Class... parameterTypes):获取单个的指定参数的公有的构造方法;
            public Constructor[] getDeclaredConstructor(Class... parameterTypes):获取单个的指定参数的构造方法(可以是私有、受保护和默认);

            调用构造方法:
            Constructor-->newInstance(Object... initargs);
 */
public class E_02 {
    public static void main(String[] args) throws Exception{
        //首先先加载获取Class对象
        Class<?> stuClass = Class.forName("DEVIL.反射.Student");

        //获取所有公有的构造方法
        System.out.println("================获取所有公有的构造方法=================");
        Constructor<?>[] constructors1= stuClass.getConstructors();
        for (Constructor<?> constructor : constructors1) {
            System.out.println(constructor);
        }
        System.out.println("================获取所有的构造方法=================");
        Constructor<?>[] constructors2 = stuClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors2) {
            System.out.println(constructor);
        }
        System.out.println("================获取的单个指定的公有的构造方法=================");
        Constructor<?> constructor1 = stuClass.getConstructor(char.class);//此处参数填原方法中参数类型的Class对象即可也可填null
        //null则代表无参
        System.out.println(constructor1);
        //调用构造方法
        Student stu = (Student) constructor1.newInstance('丁');
        //因为此处的调用构造方法默认返回Object实例对象因此在这里使用时强制类型转换为Student;
        System.out.println("================获取的单个指定的构造方法=================");
        Constructor<?> constructor2 = stuClass.getDeclaredConstructor(int.class);
        System.out.println(constructor2);
        constructor2.setAccessible(true);//暴力访问 忽略掉访问标识符
        //调用构造方法
        Student st = (Student) constructor2.newInstance(19);
    }
}

控制台输出

获取所有公有的构造方法=
public DEVIL.反射.Student(java.lang.String,int)
public DEVIL.反射.Student(char)
public DEVIL.反射.Student()
获取所有的构造方法=
public DEVIL.反射.Student(java.lang.String,int)
public DEVIL.反射.Student(char)
public DEVIL.反射.Student()
protected DEVIL.反射.Student(boolean)
private DEVIL.反射.Student(int)
DEVIL.反射.Student(java.lang.String)
获取的单个指定的公有的构造方法=
public DEVIL.反射.Student(char)
丁调用有参的公用构造方法
获取的单个指定的构造方法=
private DEVIL.反射.Student(int)
今年19岁了


3.通过反射获取成员字段并设置值

获取成员变量并调用:
1.获取批量的
1).Field[] getFields():获取所有的公有字段
2).Field[] getDeclaredFields():获取所有字段(私有、受保护、默认和公有)
2.获取单个的:
1).public Field getField(String fieldName):获取指定的单个公有的字段;
2).public Field getDeclaredField(String fieldName):获取指定的单个字段(私有、受保护、默认和公有);

 设置字段的值:
	Field --> public void set(Object obj,Object value):
				参数说明:
				1.obj:要设置的字段所在的对象;
				2.value:要为字段设置的值;
package DEVIL.反射;

import java.lang.reflect.Field;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-20:50
 */
/*
获取成员变量并调用:
   1.获取批量的
 		1).Field[] getFields():获取所有的公有字段
 		2).Field[] getDeclaredFields():获取所有字段(私有、受保护、默认和公有)
   2.获取单个的:
 		1).public Field getField(String fieldName):获取指定的单个公有的字段;
 		2).public Field getDeclaredField(String fieldName):获取指定的单个字段(私有、受保护、默认和公有);

 	 设置字段的值:
		Field --> public void set(Object obj,Object value):
					参数说明:
 					1.obj:要设置的字段所在的对象;
  					2.value:要为字段设置的值;
 */
public class E_03 {
    public static void main(String[] args) throws Exception {
        //首先先获取Class对象
        Class<?> stuClass = Class.forName("DEVIL.反射.Student");
        //获取所有共有字段
        System.out.println("======================获取所有公有字段=======================");
        Field[] fields1 = stuClass.getFields();
        for (Field field : fields1) {
            System.out.println(field);
        }
        //获取所有字段
        System.out.println("======================获取所有字段=======================");
        Field[] fields2 = stuClass.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
        }
        //获取指定的单个公有字段
        System.out.println("======================获取指定的单个公有字段=======================");
        Field field1 = stuClass.getField("name");
        System.out.println(field1);
        //获取一个对象
        Student stu = (Student) stuClass.getConstructor().newInstance();
        //设置字段的值
        field1.set(stu,"丁杨维");
        //验证设置是否成功
        System.out.println("姓名:"+stu.name);
        //获取指定的单个字段
        System.out.println("======================获取指定的单个字段=======================");
        Field field2 = stuClass.getDeclaredField("age");
        System.out.println(field2);
        //设置字段的值
        field2.setAccessible(true);//暴力访问(忽略掉访问表示符)
        field2.set(stu,19);
        //验证设置是否成功
        System.out.println(stu.toString());
    }
}

控制台输出

获取所有公有字段=
public java.lang.String DEVIL.反射.Student.name
获取所有字段=
public java.lang.String DEVIL.反射.Student.name
private int DEVIL.反射.Student.age
protected char DEVIL.反射.Student.st
java.lang.String DEVIL.反射.Student.num
获取指定的单个公有字段=
public java.lang.String DEVIL.反射.Student.name
调用了无参的公用构造方法
姓名:丁杨维
获取指定的单个字段=
private int DEVIL.反射.Student.age
Student{name=‘丁杨维’, age=19, st= , num=‘null’}


4.通过反射获取成员方法并调用

1.获取批量的:
public Method[] getMethods():获取所有公有方法;(包含了父类的方法也包含Object类)
public Method[] getDeclaredMethods():获取所有的成员方法,(包括私有的,不包括继承的)
2.获取单个指定的:
public Method getMethod(String name,Class<?>… parameterTypes):
参数:
name : 方法名;
Class … : 形参的Class类型对象
public Method getDeclaredMethod(String name,Class<?>… parameterTypes)

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

package DEVIL.反射;

import java.lang.reflect.Method;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-21:21
 */
/*
获取成员方法并调用:

  1.获取批量的:
  		public Method[] getMethods():获取所有公有方法;(包含了父类的方法也包含Object类)
  		public Method[] getDeclaredMethods():获取所有的成员方法,(包括私有的,不包括继承的)
  2.获取单个指定的:
  		public Method getMethod(String name,Class<?>... parameterTypes):
  					参数:
  						name : 方法名;
  						Class ... : 形参的Class类型对象
  		public Method getDeclaredMethod(String name,Class<?>... parameterTypes)

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

 */
public class E_04 {
    public static void main(String[] args)throws Exception {
        //首先先获取Class对象
        Class<?> stuClass = Class.forName("DEVIL.反射.Student");
        //获取所有公有方法;(包含了父类的方法也包含Object类)
        System.out.println("====================获取所有的公有方法=====================");
        Method[] methods1 = stuClass.getMethods();//获取的方法包含了父类的方法也包含Object类
        for (Method method : methods1) {
            System.out.println(method);
        }
        //获取所有的成员方法,(包括私有的,不包括继承的)
        System.out.println("====================获取所有的成员方法=====================");
        Method[] methods2 = stuClass.getDeclaredMethods();//获取的方法不包含继承的方法
        for (Method method : methods2) {
            System.out.println(method);
        }
        //获取单个指定的公有成员方法
        System.out.println("====================获取单个指定的公有成员方法=====================");
        Method method1 = stuClass.getMethod("show1",String.class);
        //获取一个对象
        Student stu = (Student) stuClass.getConstructor().newInstance();
        //调用方法
        method1.invoke(stu,"丁杨维");
        //获取单个指定的成员方法
        System.out.println("====================获取单个指定的成员方法=====================");
        Method method2 = stuClass.getDeclaredMethod("show3",int.class);
        //调用方法
        method2.setAccessible(true);
        method2.invoke(stu,19);//需要两个参数(一个是要调用的方法的对象,一个是方法参数的值)
    }
}

控制台输出

获取所有的公有方法=
public void DEVIL.反射.Student.show2()
public void DEVIL.反射.Student.show1(java.lang.String)
public static void DEVIL.反射.Student.main(java.lang.String[])
public java.lang.String DEVIL.反射.Student.toString()
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
获取所有的成员方法=
public void DEVIL.反射.Student.show2()
private void DEVIL.反射.Student.show3(int)
void DEVIL.反射.Student.show4(char)
public void DEVIL.反射.Student.show1(java.lang.String)
protected void DEVIL.反射.Student.show5(java.lang.String)
public static void DEVIL.反射.Student.main(java.lang.String[])
public java.lang.String DEVIL.反射.Student.toString()
获取单个指定的公有成员方法=
调用了无参的公用构造方法
调用了公有的,参数类型为String的方法 name = 丁杨维
获取单个指定的成员方法=
调用了私有的,参数类型为int的方法 age = 19


5.通过反射获取main方法并调用

package DEVIL.反射;

import java.lang.reflect.Method;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-21:45
 */
public class E_05 {
    public static void main(String[] args) {
        try {
            //首先获得Class对象
            Class<?> stuClass = Class.forName("DEVIL.反射.Student");
            //获取main方法
            //调用main方法
            Method field =  stuClass.getDeclaredMethod("main", String[].class);
            field.invoke(null,(Object)new String[]{"a","b","c"});//方式一
            //field.invoke(null,new Object[]{new String[]{"a","b","c"}}); 方式二
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

main方法

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

控制台输出

main方法执行了


6.通过反射运行配置文件内容

Student1

package DEVIL.反射;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-21:58
 */
public class Student1 {
    public void show(){
        System.out.println("执行了show方法");
    }
}

配置文件pro.txt

className = DEVIL.反射.Student1
methodName = show

主类

package DEVIL.反射;

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

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-22:04
 */
public class E_06 {
    public static void main(String[] args) throws Exception {
        //通过Class.forName(String name)和getValue(String key)获取一个Class对象;
        Class<?> stuClass = Class.forName(getValue("className"));
        //获取show方法
        Method method = stuClass.getDeclaredMethod(getValue("methodName"));
        //调用show方法
        method.invoke(stuClass.getConstructor().newInstance());
    }
    //此方法接受一个Key 在配置文件中获取相应的value
    public static String getValue(String key) throws Exception{
        Properties pro = new Properties();//获取配置文件的对象
        FileReader in = new FileReader("E:\\Java\\JavaStudy02\\src\\DEVIL\\反射\\pro.txt");//创建输入流
        pro.load(in);//将流加载到配置文件中
        in.close();
        return pro.getProperty(key);//返回关键字对应的值
    }
}

控制台

执行了show方法

若是加入一个新的类,无需修改程序,只需将配置文件修改即可。

Student2

package DEVIL.反射;

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-22:28
 */
public class Student2 {
    public void show2(){
        System.out.println("牛子执行了");
    }
}

配置文件pro.txt 修改为

className = DEVIL.反射.Student2
methodName = show2

控制台

牛子执行了

可以发现于与预期一致,个人认为这样的性质十分巧妙,符合java的开发原则;


7.通过反射越过泛型检查

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

仔细观察下面程序

package DEVIL.反射;

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

/**
 * @auther Devil(丁杨维)
 * @create 2021-10-11-22:32
 */
//反射可以越过泛型检查
public class E_07 {
    public static void main(String[] args) throws Exception{
        ArrayList<String> myList = new ArrayList<>();//创建一个ArrayList对象
        myList.add("ad g");//向其中添加值
        myList.add("fad");
        //先获取myList的Class对象
        Class<?> clz = myList.getClass();
        Method method = clz.getDeclaredMethod("add",Object.class);
        //调用add方法
        method.invoke(myList,123);//插入与原本定义的泛型类型不同的数据
        method.invoke(myList,true);
        Iterator<?> iter = myList.iterator();
        for (Object obj : myList) {//输出并验证 方法一 一般for循环遍历输出
            System.out.println(obj);
        }
        /*
        while(iter.hasNext()){//方法二 迭代器遍历
            System.out.println(iter.next());
        }

         */
    }
}
//结果与预期一致越过了泛型检查

发现了吗 我们一开始定义为的ArrayList 在通过反射获得add方法并调用后输入了非**“String”**类型的数据 但程序却没有报错,着就是通过反射越过泛型检查。

–end–

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值