day_8/23(浅谈反射理念)

发布于:2023-01-06 ⋅ 阅读:(389) ⋅ 点赞:(0)

目录

前言

一、反射的介绍

二、使用反射的步骤

         1、获取字节码对象

         1.1什么是字节码对象

         1.2获取字节码对象的的三种方式

         2、根据字节码文件获取构造方法、成员方法、属性

         2.1获取构造方法(Constructor类类型)

         2.2获取成员方法(Method类类型)

         2.3获取属性(Field类类型)

         3、使用构造方法、成员方法、属性

         3.1使用构造方法

         3.2使用成员方法

         3.3使用成员变量

总结


前言

         不知道你有没有想过这样一个问题,我们在使用IDEA集成开发工具编写代码的时候,idea可以提示你,你当前使用的类里面包含什么方法、什么属性, 不管是不是我们自己写的类,它都可以提示,那么这个功能它是如何做到的呢?答案就是利用了反射机制,今天就浅浅学习一下反射吧.。

一、反射的介绍

         反射是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。 反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。

二、使用反射的步骤

         1、获取字节码对象

         1.1什么是字节码对象

        字节码对象即Class类对象(这里的Class是一个类,并不是我们定义类的时候使用class关键字,这是值得注意的地方),每一个类的的.class文件就是一个字节码对象, .class文件我们知道,是我们编写的代码经过编译之后生成的文件,这个文件包含了源码里面的类的所有信息,既然如此,通过字节码对象就可以获取到类的所有信息.。

         1.2获取字节码对象的的三种方式

        类名.class,这种方式很简单,不过需要注意,这种方式需要导包,即你要让虚拟机能找到你这个类,找不到就会报错

        对象名.getClass(),这也是一种方式,但是我有一个疑问,既然有对象了,它的方法直接调用就可以了啊

        Class.forName("包名加类名"),这种方式应该是最好的一种,不管在哪执行这句代码,在不在同一个包下,都是没有问题的。

这里需要注意的是,每个类的字节码文件都只有一个,所以三种方式获取的Class对象是同一个,请看代码演示:

public class MainTest {
    public static void main(String[] args) throws ClassNotFoundException {
//        获取字节码对象
        //类名.class获取,MainTest类和TestClass类在同一个包下面
        Class<TestClass> aClass = TestClass.class;
        //对象名.getClass()获取,需要先新建一个对象
        TestClass testClass = new TestClass();
        Class<? extends TestClass> aClass1 = testClass.getClass();
        //Class.forName("包名+类名")获取
        Class<?> aClass2 = Class.forName("com.fs.test823.TestClass");

        //==比较的时对象的地址,如果不同的引用指向的是同一个地址处的对象,
        //则说明它们引用的对象是同一个
        System.out.println("aClass和aClass1是同一个对象:"+(aClass == aClass1));
        System.out.println("aClass和aClass2是同一个对象:"+(aClass == aClass2));
        System.out.println("aClass1和aClass2是同一个对象:"+(aClass1 == aClass2));

    }
}

 运行结果:

         2、根据字节码文件获取构造方法、成员方法、属性

         2.1获取构造方法(Constructor类类型)

         public Constructor<T> getConstructor(Class<?>... parameterTypes)该方法需要传入参数类型的字节码对象,可以没有, 可以是一个,也可以是多个,根据构造函数的参数个数来即可,返回的是一个Constructor对象。

         public Constructor <?>[] getConstructors() 该方法不需要参数,返回的是一个Constructor类型的数组。

         public Constructor <T> getDeclaredConstructor(Class<?>... parameterTypes)该方法的需要的参数和返回值与第一个方法一致。

         public Constructor <?>[] getDeclaredConstructors() 该方法和第二个方法的返回值一致 值得注意的,第一二个方法只能获取public修饰的构造方法,而第三四个方法可以获取所有的构造方法。

         2.2获取成员方法(Method类类型)

         与获取构造方法类似,获取成员方法也有四个方法分别为

         public Method getMethod(String name, Class<?>... parameterTypes) 此方法与getConstructor方法类似,返回一个Method对象, 区别在于,该方法第一个参数是你需要获得的成员方法的方法名称(String类型)。

         public Method[] getMethods() 类似于getConstructors方法,返回的是一个Method类型的数组。

        public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

         public Method[] getDeclaredMethods() 同样的,前面两个方法可以获取到的都是public修饰的成员方法,后两个方法可以获取所有成员方法。

         2.3获取属性(Field类类型)

         获取属性也是一样的,四个方法,分别是:

         public Field[] getField(String name)

         public Field[] getFields()

         public Field[] getDeclaredField(String name)

         public Field[] getDeclaredFields() 和前面类似,不过获取单个属性的时候只需要传入属性名称即可。

         3、使用构造方法、成员方法、属性

         新建一个TestClass类,MainTest类里面编写测试代码,获取TestClass类里面内容,TestClass里的内容如下:

public class TestClass {
    public String str;
    private int i;
    public TestClass() {
    }

    public TestClass(String str, int i) {
        this.str = str;
        this.i = i;
    }

    private TestClass(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "str="+str+"\ti="+i;
    }

    public void test(){
        System.out.println("我是public修饰test()方法");
    }

    private void test1(){
        System.out.println("我是private修饰test1()方法");
    }
    
    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }
}

         3.1使用构造方法

         构造方法的使用需要用到newInstance方法,看代码演示

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class MainTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//        获取字节码对象
        //类名.class获取,MainTest类和TestClass类在同一个包下面
        Class<TestClass> aClass = TestClass.class;

        //获取无参构造方法
        Constructor<TestClass> constructor1 = aClass.getConstructor();
        //获取全参构造方法
        Constructor<TestClass> constructor2 = aClass.getConstructor(String.class,int.class);
        //获取private修饰的构造方法
        Constructor<TestClass> constructor3 = aClass.getDeclaredConstructor(String.class);

        //使用全参构造
        TestClass testClass = constructor2.newInstance("dog", 123); //相当于TestClass testClass = new TestClass("dog", 123);
        //重写了toString方法
        System.out.println(testClass);

        //使用私有单参构造
        //在使用私有的方法或属性时要先设置暴力反射,否则会报IllegalAccessException异常
        constructor3.setAccessible(true);
        TestClass testClass1 = constructor3.newInstance("fish");
        System.out.println(testClass1);
    }
}

 运行结果:

 成功获取构造方法,并创建对象,同时给属性赋值,这里值得注意的地方就是暴力反射,如果没有constructor3.setAccessible(true);这句代码,则会抛出IllegalAccessException异常,在使用私有成员方法和使用属性的时候也是这样的。

         3.2使用成员方法

         成员方法的使用需要用到的是invoke方法,看代码演示

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MainTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//        获取字节码对象
        //类名.class获取,MainTest类和TestClass类在同一个包下面
        Class<TestClass> aClass = TestClass.class;

        //获取公有方法
        Method test = aClass.getMethod("test");
        //获取私有方法
        Method test1 = aClass.getDeclaredMethod("test1");

        //使用公有方法
        //这里与使用构造方法不一样,invoke的第一个参数要是执行方法所在类的对象
        //使用aClass.newInstance()可以创建aClass所代表的类的对象,不过该方法是依赖于无参构造的,如果类没有无参构造则会报InstantiationException异常
        test.invoke(aClass.newInstance());
        //使用私有方法
        //同样的,使用私有方法前需要设置暴力反射
        test1.setAccessible(true);
        test1.invoke(aClass.newInstance());
    }
}

运行结果 

 两个方法都成功运行。

         3.3使用成员变量

         属性的使用就略有区别,分了数据类型,获取属性的值的方法有:

         Object get(Object obj)

         int getInt(Object obj)

         long getLong(Object obj)

         boolean getBoolean(Object ob)

         double getDouble(Object obj)

         设置属性的值的方法有:

         void set(Object obj, Object value)

         void setInt(Object obj, int i)

         void setLong(Object obj, long l)

         void setBoolean(Object obj, boolean z)

         void setDouble(Object obj, double d)

         这里TestClass里面我就放了一个String类型的数据和int类型的数据,所以只需要使用get和getInt方法就可以获取属性的值了,设置值同理,看代码演示:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class MainTest {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//        获取字节码对象
        //类名.class获取,MainTest类和TestClass类在同一个包下面
        Class<TestClass> aClass = TestClass.class;

        //创建一个testClass对象,后面的方法需要用到,因为属性str和i都是非静态的,它们依赖对象而存在
        TestClass tc = new TestClass();
        //获取公有属性
        Field str = aClass.getField("str");
        //获取私有属性
        Field i = aClass.getDeclaredField("i");
        //使用公有属性
        System.out.println("给str赋值前str的值为:"+str.get(tc));
        str.set(tc,"cat");
        System.out.println("给str赋值后str的值为:"+str.get(tc));
        //使用私有属性
        //设置暴力反射
        i.setAccessible(true);
        System.out.println("给i赋值前i的值为:"+i.getInt(tc));
        i.set(tc,147);
        System.out.println("给i赋值后i的值为:"+i.getInt(tc));

    }
}

运行结果: 

总结

        本篇文章浅浅介绍了一下反射,并且简单的使用了一下,其反射用的多的地方应该是在框架里面,那时应该会结合昨天的文章所讲述的属性集和配置文件来使用,那样会大大的提高程序的灵活性,让使用者也更加方便,还没接触框架,就暂且不赘述了吧。

        文章到此结束,感谢您的阅读,祝您生活愉快!

本文含有隐藏内容,请 开通VIP 后查看