Java 反射深入浅出

Java 反射深入浅出📈

反射的概述:📑

Java Reflection(反射) 被视为动态语言的关键,Java并不是动态语言,但因为反射Java可以被称为准动态语言

反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。反射是框架的灵魂

  • 我们都知道Java 程序在加载完之后,在堆内存的方法区中就产生了一个Class类型的对象一个类只有一个Class对象,这个对象包含了整个类的结构信息,可以通过这个对象看到类的结构。
  • 这个对象就像 照镜子 一样,反射 出类的信息。

动态语言

是一类在运行时可以改变其结构的语言:

例如新的函数、对象、甚至代码可以 被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运 行时代码可以根据某些条件改变自身结构。

主要动态语言: Object-C、C#、JavaScript、PHP、Python、Erlang

静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。

反射的入门案例:

上面说了这么多其实还是有点模模糊糊,归根结底,反射也就是Java中的一组API,常用的类有:

  • Java.lang.Class:代表一个类的字节码文件。注意并不是小写的class 关键字而是一个类,可以理解为是类的类。
  • Java.lang.reflect.Field:代表类的成员变量
  • Java.lang.reflect.Method:代表类中的方法
  • Java.lang.reflect.Constructor:代表类的构造器

环境搭建:

在这里插入图片描述
定义 Person

//创建一个人的类
public class Person {
    //姓名
    public String name;
    //年龄: 因为有点人的年龄是保密的,
    private int age;
    //无参公共构造函数
    public Person(){   }
    //有参私有构造函数
    private Person(String name, int age) { this.name = name; this.age = age; }
    //定义方法
    //说话表达,人都可以说话表达这个是公开说出来的
    public void speak(){ System.out.println(name+"表达说出自己的话");    }
    //心想思考,这个过程是内心活动是私有的
    private void think(){ System.out.println(name+"在内心在思考,晚上吃什么^^");    }
    //省略封装的toString/get/set方法...
}    

Deom实例:

创建Person的类对象,并调用属性方法,正常的Java操作:

Java创建对象:

//Java创建对象并使用:
@Test
public void common(){
    //创建对象
    Person person = new Person();
    //属性赋值并输出...
    person.setName("张三");
    person.speak();
    System.out.println("我叫:"+person.name+"今年:"+person.getAge());    //私有的属性被封装仅可通过公共get/set方法操作.
    //正常情况Java在类的外部不可以调用私有属性、方法、构造
}

在这里插入图片描述

Java反射创建对象:

//通过反射机制创建对象并使用:
@Test
public void reflection() throws Exception{
    //1.反射创建对象
    //通过Person类.class 获取一个Class对象,并通过getConstructor()获取其Public构造对象创建对象.
    Class<Person> personClass = Person.class;
    Constructor<Person> constructor = personClass.getConstructor();      //方法需要捕获/抛出异常:NoSuchMethodException
    Person person = constructor.newInstance();                           //这里的参数需要和上面getConstructor() 定义时一只不然会抛出IllegalAccessException类型转换异常.
    person.setName("张三");
    System.out.println("我叫:"+person.name+"今年:"+person.getAge());     //反射创建的对象并调用属性方法,因为Java的封装性任然不能调用私有的方法;

    //2.通过反射调用类的Public属性,并更改对象的name属性
    Field nameField = personClass.getDeclaredField("name");
    nameField.set(person,"李四");
    System.out.println("我叫:"+person.name);

    //3.通过反射调用类的Public方法
    Method speak = personClass.getDeclaredMethod("speak");
    speak.invoke(person);

    //-------------目前使用反射做到和反射钱一样的操作,但是反射更加强大它可以调用类的Private私有构造/方法/属性
    System.out.println("使用反射调用私有构造/属性/方法");
    //1.通过反射可以调用私有构造器
    Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class, int.class);
    declaredConstructor.setAccessible(true);
    Person person1 = declaredConstructor.newInstance("王五", 10);
    System.out.println("我叫:"+person1.name+"今年:"+person1.getAge());    //通过反射调用私有的构造完成了对象实例化操作.
    //2.通过反射调用私有的属性
    Field ageField = personClass.getDeclaredField("age");
    ageField.setAccessible(true);
    ageField.set(person1,20);
    System.out.println("我叫:"+person1.name+"今年:"+person1.getAge());   //通过反射调用私有的属性并重新赋值.
    //3.通过反射调用私有的方法
    Method thinkMethod = personClass.getDeclaredMethod("think");    	//通过反射调用私有的方法
    thinkMethod.setAccessible(true);
    thinkMethod.invoke(person1);                                        //invoke(需要被调用方法类的对象);
}

在这里插入图片描述

好的,通过上面的基础方法,大概了解了反射的基本使用: 反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。

反射核心 Class类

Java.lang.Class类:

Class类是一个特殊类本质也是一个类,它用于表示JVM运行时类或接口的信息

Class 对象只能由系统建立对象,一个加载的类在 JVM 中只会有一个Class实例,Class对象对应的是一个加载到JVM中的一个.class文件

每个类的实例都会记得自己是由哪个 Class 实例所生成,通过Class可以完整地得到一个类中的所有被加载的结构,Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象。

Java类加载简单过程:

这里只是简单了解一个大概的过程,其内部的原理更加复杂…

在这里插入图片描述

创建Class类的对象四种方法

  • 调用运行时类的 .class属性

  • 通过运行时类的对象,调用.getClass()方法;

  • 使用类加载器,ClassLoader进行创建Class对象

  • 通过Class类的静态方法 forName(“全类名”); 的形式进行调用。✨最常用

  • 通过Class类的静态方法 forName(“全类名”); 的形式进行调用。✨最常用

//Class 对象创建的几种方式:使用Person类举例
public class CreateClass {
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一:调用运行时类的 .class属性.
        Class<Person> class1 = Person.class;

        //方式二;通过运行时类的对象,调用.getClass()方法;
        Person person = new Person();
        Class<? extends Person> class2 = person.getClass();         //此种方法并不常用因为已经获取到了类的对象就可以完成大部分的操作了没必要多此一举了.

        //方式三: 通过Class类的静态方法 forName(“全类名”); 的形式进行调用
        //方法需要声明异常 ClassNotFoundException,也是最常用的方法,因为很多时候,我们都是不能确定要创建什么类的时候而这种方法可以通过传参自定义创建的对象,使程序具有动态性。
        Class class3 = Class.forName("com.wsm.begin.Person");

        //方式四:使用类加载器,ClassLoader进行创建Class对象
        //这种方法有点了离谱先通过Class对象获取 ClassLoader类加载器在去获取其他 Class对象;
        ClassLoader classLoader = CreateClass.class.getClassLoader();
        Class<?> class4 = classLoader.loadClass("com.wsm.begin.Person");

        //上面说每个类在加载到JVM中时候只有一个对应的Class,所以这四种创建Person Class对象,的对象值其实都是用的同一块内存空间地址.
        System.out.println(class1==class2 && class3==class4);   //结果是:True
    }
}

哪些类型可以有Class对象?

Java中,基本上任何对象属性都存在 Class对象,基于面向对象的原则万事万物皆是对象。

外部类、内部类、静态内部类、interface接口、数组、enum枚举、@annotation注解、基本数据类型、void…都存在Class对象。

//Java中那些类型存在Class的对象.
//Java中万事万物皆对象,只要可以是对象就都有Class对象.
@Test
public void allClass(){
    Class c1 = Object.class;        //Object类中存在 .getClass(); 所以所有的方法都可以通过改方法获取对象.
    Class c2 = Comparable.class;    //接口
    Class c3 = String[].class;      //数组
    Class c4 = int[][].class;       //多维数组
    Class c5 = ElementType.class;   //枚举
    Class c6 = Override.class;      //注解
    Class c7 = int.class;           //基本数据类型
    Class c8 = void.class;          //void
    Class c9 = Class.class;         //Class类本身也是底层一个Class文件.


    //注意:只要元素类型与维度一样,就是同一个Class 因为本质上在JVM都是同一个CLass文件所以结果: True
    int[] a = new int[10];
    int[] b = new int[100];
    Class c10 = a.getClass();
    Class c11 = b.getClass();
    System.out.println(c10 == c11);
}

Class类的常用方法:

方法名功能说明
static Class forName(String name)返回指定类名 name 的 Class 对象
Object newInstance()调用空参构造函数,返回该Class对象的一个实例
getName()返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称
Class getSuperClass()返回当前Class对象的父类的Class对象
Class [] getInterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Class getSuperclass()返回表示此Class所表示的实体的超类的Class
Constructor[] getConstructors()返回一个包含某些Constructor对象的数组
Field[] getDeclaredFields()返回Field对象的一个数组
Method getMethod(String name,Class … paramTypes)返回一个Method对象,此对象的形参类型为paramType

获取运行时类的运行状态:

环境准备

定义一个子类,继承父类,实现接口,私有|共有的构造函数,私有|共有属性、方法、注解…

自定义一个注解类:MyAnnotation

//自定义注解类
@Retention(RetentionPolicy.RUNTIME)					//一种元注解: 标识注解的执行生命周期,只有是运行时的注解才可以被反射使用.
public @interface MyAnnotation {
    //这是自定义注解中,属性的声明方式: 数据类型 参数名;
    String value() default "wsm";                   /** 如果只有一个参数成员,建议使用参数名为value */
}

生物类Creature

public class Creature<T> implements Serializable { //实现序列化的接口
    //共有属性: 性别
    public String sex;
    //私有属性:年龄
    private int age;

    //公共|私有方法
    public void eat(){ System.out.println("吃饭了..."); }
    private void slepp(){ System.out.println("吃饱了,睡觉..."); }

    //Java默认类具有一个无参的构造...
    //省略封装的toString/get/set方法...
}

人类Person

public class Person extends Creature<String> implements Comparable{     //注意这里的父类带泛型<String>
    //共有属性:姓名
    public String name;
    //私有属性:学历
    private String education;

    //公共|私有构造
    public Person (){ System.out.println("无参构造执行..."); }
    private Person(String name, String education) { this.name = name; this.education = education; }

    //公共|私有方法
    //说话表达,人都可以说话表达这个是公开说出来的
    public void speak(){ System.out.println(name+"表达说出自己的话");    }
    //心想思考,这个过程是内心活动是私有的,方法具有:注解 访问修饰符 返回值 声明的异常...都可以通过反射获取.
    @MyAnnotation
    private String think() throws RuntimeException,IllegalAccessError { return  "在内心在思考,晚上吃什么^^";   }
    //重写Comparable Java比较器的方法.
    @Override
    public int compareTo(Object o) { return 0; }
    //省略封装的toString/get/set方法...
}

反射操作运行时类:

Class对象的newInstance()方法👍

使用反射最常用创建对象的方法 newInstance();,该方法本质是调用,类的无参构造进行创建对象。所以需要一些硬性要求:

  • 类必须有一个无参数的构造器。
  • 类的构造器的访问权限需要足够。
//反射操作运行时类对象Test1
@Test
public void fsTest1() throws Exception{
    //使用Class类的静态方法forName(”全类名“); 获取对应的Class对象
    Class<Person> classP = (Class<Person>) Class.forName("com.wsm.reflection.study.Person");   //方法需要抛出异常因为可能存在全类名不存在的类.
    Person person = classP.newInstance();    //方法需要抛出异常因为可能存在类的构造函数不存在的情况.
}

获取运行时类的结构:

获取运行时类的结构:父类,实现的接口,获取类的全类名,接口调用构造完成实例化

  • getSuperclass(); 获取父类的Class对象
  • getInterfaces(); 获取类实现的所有接口,反击一个Class[]
  • getTypeName(); 获取Class的全类名
  • getConstructor(...);|getDeclaredConstructor(...); (…)表示一个动态长度的参数数组,
    • 分别表示获取当前Class的仅Public的构造函数。并调用 newInstance(...);进行初始化操作。
    • getModifiers();返回的值是int类型:什么都不加是0 , public 是1 ,private 是 2 ,protected 是 4,static 是 8 ,final 是 16、
    • getParameterTypes(); 返回参数列表的数组
    • getName();获取方法的名称;
  • getGenericSuperclass(); 获取带泛型的父类,如果父类没有泛型则获取不到 Type类型
    • ParameterizedType 泛型类型的对象,getActualTypeArguments(); 获取泛型对象的数组。
  • setAccessible(boolean); Method和Field、Constructor对象都有setAccessible()方法;
    • setAccessible启动和禁用访问安全检查的开关,参数值为true则指示反射的对象在使用时应该取消Java语言访问检查即可以使用Private的属性方法构造
//获取运行时类的结构:父类,实现的接口,获取类的全类名,接口调用构造完成实例化.
@Test
public void runClassStructure() throws Exception {
    Class<Person> classP = (Class<Person>) Class.forName("com.wsm.reflection.study.Person");
    //获取其父类Class对象
    Class<? super Person> superclass = classP.getSuperclass();
    //获取接口
    Class<?>[] classPInterfaces = classP.getInterfaces();       //返回一个数组因为一个类可以实现多个接口;
    //获取子类|父类|接口类的全类名:
    System.out.println("子类全类名:"+classP.getTypeName());
    System.out.println("父类全类名:"+superclass.getTypeName());
    System.out.println("接口类全类名:"+classPInterfaces[0].getTypeName());  //因为子类仅有一个接口就直接[0],当然可以考虑不存在的情况...
    
    //获取带泛型的父类的泛型:
    //getGenericSuperclass(); 如果父类没有泛型则不会获取;
    Type genericSuperclass = classP.getGenericSuperclass();
    ParameterizedType paramtype = (ParameterizedType) genericSuperclass;
    System.out.println("获取带泛型的父类:"+genericSuperclass);
    System.out.println("获取带泛型的父类的泛型的类型:"+paramtype.getActualTypeArguments()[0]);   //返回一个数组,因为类的泛型可以是多个比如:MAP<k,v>
    
    //获取子类的Public构造方法创建对象
    //正常情况下构建对象直接使用 newInstance(); 即可, getConstructor():获取该类的Public的构造方法,并根据参数进行实例化;
    Constructor<Person> constructor = classP.getConstructor();
    Person person = constructor.newInstance();  //这里的newInstance方法的参数要和 classP.getConstructor() 一致不然会报错;
    System.out.println(person);

    //获取子类的Public和Private构造方法创建对象
    Constructor<Person> constructor1 = classP.getDeclaredConstructor(String.class, String.class);
    constructor1.setAccessible(true);   //如果调用private的方法|构造需要将启动和禁用访问安全检查的开关设为 True
    Person person1 = constructor1.newInstance("wsm", "大专");
    System.out.println(person1);
    //获取构造函数的修饰符 方法名称 参数列表(因为这里知道有两个参数就写死了,方法返回的是一个数组)
    System.out.println("Person的有参构造的访问修饰符:"+constructor1.getModifiers()+",方法名称:"+constructor1.getName()+",参数类型列表:"+constructor1.getParameterTypes()[0]+","+constructor1.getParameterTypes()[1]);
}

获取运行时类的方法Method:

  • getMethods(); 获取子类和父类Public的方法。

  • getDeclaredMethods 获取子类Public和Private的方法。

  • getMethod();|getDeclaredMethod(); 根据方法名指定获取仅共有包含父类|仅当前类但包含共有私有的方法。

    • setAccessible(boolean) 获取私有的方法需要,设置true开通访问权限。

    • Object invoke(Object obj,Object... args); 相当于代替obj对象调用指定的方法的方法,返回值Object类型,若原方法无返回值,此时返回null

      若原方法若为static静态方法,此时形参Object obj可为null

      若原方法形参列表为空,则Object[] args为null,可以不填

      若原方法声明为private,则需要在调用此invoke()方法前,显式调用 方法对象的setAccessible(true)方法,将可访问private的方法

获取方法的具体信息:

  • getModifiers(); 获取方法权限修饰符
  • getReturnType(); 获取方法的返回值
  • getParameterTypes(); 获取方法的参数列表,返回一个数组;
  • getExceptionTypes(); 获取方法抛出的异常,返回一个异常数组;
//获取运行时类的结构方法:
@Test
public void runClassStructureMethod() throws Exception {
    Class<Person> classP = (Class<Person>) Class.forName("com.wsm.reflection.study.Person");
    System.out.println("获取子类父类的所有Public方法,包含Object类");
    Method[] methods = classP.getMethods();
    for (Method method : methods) { System.out.println(method); }

    System.out.println("获取当前Class Public和Private方法,这个不会包含父类");  //如果想要父类的私有方法可以通过 getSuperclass() 先获取父类Class
    Method[] declaredMethods = classP.getDeclaredMethods();
    for (Method method : declaredMethods) { System.out.println(method); }

    //上面获取方法都是返回一个数组,反射中也存在单独获取某一个类的操作:getMethod("指定方法名");|getDeclaredMethod("指定方法名"); 和上面大概相同Public和Private的区别;
    Person person = classP.newInstance();
    Method thinkMethod = classP.getDeclaredMethod("think");
    thinkMethod.setAccessible(true);
    System.out.println("调用方法 invoke(执行方法的对象,...方法的多参属性...) 如果方法存在返回值则也通过 invoke()方法返回;");
    Object invoke = thinkMethod.invoke(person);
    System.out.println("Person的think方法返回值:"+invoke);
    //获取方法的具体信息:
    System.out.println("取得权限修饰符: "+thinkMethod.getModifiers());
    System.out.println("取得方法的返回值: "+thinkMethod.getReturnType());
    System.out.println("取得全部的参数: "+thinkMethod.getParameterTypes()+"返回一个参数数组地址值");
    System.out.println("取得方法抛出的异常信息: "+thinkMethod.getExceptionTypes()[0]+","+thinkMethod.getExceptionTypes()[1]+"返回一个参数数组地址值");

    //扩展:获取注解
    //getAnnotations(); 获取改方法的所有RUNTIME注解数组[],包含父类的注解,因为这个是方法所以不方便展示;
    //getDeclaredAnnotations(); 仅获取子类的注解;
    Annotation[] annotations = thinkMethod.getAnnotations();
    System.out.println(annotations[0]);
}

获取运行时类的属性Field:

这里的方法和类和上面也都类似就不详细介绍了: Field类的常用方法;

  • Object get(Object obj); 取得指定对象obj上此Field的属性内容
  • set(Object obj,Object value); 设置指定对象obj上此Field的属性内容 obj指要修改的对象 vlaue指要修改的值;
//获取运行时类的结构属性:
@Test
public void runClassStructureField() throws Exception {
    Class<Person> classP = (Class<Person>) Class.forName("com.wsm.reflection.study.Person");
    System.out.println("获取子类父类的所有Public属性,包含Object类");
    Field[] fields = classP.getFields();
    for (Field field : fields) { System.out.println(field); }

    System.out.println("获取当前Class Public和Private属性,这个不会包含父类");  //如果想要父类的私有属性可以通过 getSuperclass() 先获取父类Class
    Field[] declaredField = classP.getDeclaredFields();
    for (Field field : declaredField) { System.out.println(field); }

    //上面获取方法都是返回一个数组,反射中也存在单独获取某一个类的操作:getFields("指定属性名");|getDeclaredFields("指定属性名"); 和上面大概相同Public和Private的区别;
    Person person = classP.newInstance();
    Field field = classP.getDeclaredField("education");
    field.setAccessible(true);
    //设置person的属性
    field.set(person,"本科");
    System.out.println("person的学历是:"+field.get(person));
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.慈祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值