有趣又刺激的操作--Java反射

目录

 

 

一、引言

二、反射认知概念

1.反射是什么?

2.反射的使用前提是什么?

3.反射的作用是什么?

4.反射有没有缺点?

三、反射codes部分开始

1.反射之根本——Class 

2.目标类下的三大详细信息

3.对三大详细信息的总结

四、扩展部分

1.反射的其它使用:

2.扩展知识


 

一、引言

  • 在Java的学习中,大家总是顺向的去分析codes(代码),如果没有对某一个code已经了解过了,就很难逆向解析,那么,反射就应运而生了。
  • 在学习反射(reflection)之前,大家需要去多看看API,主要看Class,Object,Exception,还有Java.until.reflect.*;包
  • 此外,在Java反射中会有一些jvm的知识,有兴趣小伙伴可以看看《深入Java虚拟机》这本书。
  • PS:红色标记表示十分重要,蓝色表示需要注意的细节

二、反射认知概念

1.反射是什么?

  • 指程序可以访问、检测和修改它本身状态或行为的一种能力,简单来说就是 一种逆向解析本身code。
  • java类中的各种成分被反射映射成一个个的Java类。

2.反射的使用前提是什么?

  • 必须得到代表的字节码的Class,Class类用于表示.class文件(字节码

class文件是编译器编译之后供虚拟机解释执行的二进制字节码文件,不只是java。故Class类是反射的根本

3.反射的作用是什么?

  • 增加了codes的灵活性
  • 可以通过反射是程序代码访问在jvm中的类的内部信息。(主要是成员变量,方法,构造方法相关信息)
  • 得到类的信息之后,根据类的信息去做一些特定的操作

4.反射有没有缺点?

  • 反射绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
  • 主要用在对灵活性和扩展性要求很高的系统框架上。随意使用会模糊程序内部结构逻辑。

PS:好啦,介绍了一些迷迷糊糊的东西,不过除了标出来的之外,可以过后再来理解。接下来进入正题!

三、反射codes部分开始

1.反射之根本——Class 

  • Class介绍

Class在API中有如下两个要点: 第一,位于java.lang.Object包下。第二,实现了SerializableAnnotatedElementGenericDeclarationType接口,其中AnnotatedElement后面会写。

  • 获取Class类对象(字节码)的三种方式:第一种方式最为重要,十分重要
package test2;

public class testClass {
    public static void main(String[] args) throws Exception {//此处throws文末写,不会造成理解障碍
        //方法一,从类的全名中获取
        //forName("所属包名.类名");
        Class ca=Class.forName("test2.WordCodes");//通常都是使用的该种方式

        //方法二,从已有对象中获取
        WordCodes wc=new WordCodes();   //已经有了WordCodes对象,就不需要用反射了,多此一举
        Class ca2=wc.getClass();
        //方法三,从已有的类中获取
        Class ca3=WordCodes.class;      //需要带入WordCodes所在的包,依赖性太强
    }
}
class WordCodes{
    //新建一个类,只是为了演示,故没写然后属性,方法。
}

2.目标类下的三大详细信息

首先,创造一个已经存在的类,用作为目标类,具备以下属性、方法:PS:请多留意一下他们的访问权限及方法的返回类型

package package1;

class testM{
    //成员变量
    public int number1 = 10;
    private String na1 = "hello"  ;
    private int number2;
    private String na2;
    //构造方法
    public   testM(){
        System.out.println("公有无参构造方法");
    }
    private    testM(int  age){
        System.out.println("私有含参构造方法"+age);
    }
    public testM(String na2){
        this.na2=na2;
       System.out.println("公有含参构造"+na2);
    }
    //成员方法
    public void methods1(){
        System.out.println("公有无参的普通方法");
    }
    public void methods1(boolean b){
        System.out.println("公有有参的普通方法+"+b);
    }
    private void methods2(){
        System.out.println("私有无参的普通方法");
    }
    private void methods2(String s){
        System.out.println("私有有参的普通方法 "+s);
    }
}

 

  • 构造函数的获取与使用
package package1;

import java.lang.reflect.Constructor;

public class SecondRe{

    public static void main(String[] args) throws Exception {

        Class c1= Class.forName("package1.testM");          //由于前面说了,该方法获取C最常用lass,故以此为示范
        System.out.println("-------------获取所有构造方法------------");
        Constructor[] C_All=c1.getDeclaredConstructors();   //getDeclaredConstructors为复数形式,表示有多个,且以数组形式报存
        for(Constructor C_All_Print : C_All)                //打印保存的所有构造方法的信息
            System.out.println(C_All_Print);
        System.out.println("-------------获取公有无参构造方法,实例化时自动调用-----------");
        Constructor C = c1.getConstructor();                //注意用的是getConstructor,没有's'
        System.out.println(C);
        Object  CC = C.newInstance();                       //该句实例化了获取信息的对象。因为此处Class类把获取的信息一个个映射为了类
        System.out.println("-------------获取私有含参构造方法并调用---------------");
        Constructor con = c1.getDeclaredConstructor(String.class);  //获取class对象
        System.out.println(con);
        con.setAccessible(true);                    //设置访问权限
        Object  obj=con.newInstance("nan");         //为含参构造设置相应的参数值,并且实例化对象调用

    }
}
//对于Object CC = c1.getConstructor();句,  如果不需要接收该对象,可以直接调用c1.getConstructor();

 结果如下:

分析介绍:(调用关系)

  • 请注意,该操作使用了Class(根本),Constructor(操控构造方法的对象),Object(java.lang包下最大的类)
  • Constructor   extends--->Executable   extends--->AccessibleObject  extends--->Object
  • Class extends--->Object
  • Class类用来获取目标类的所有信息;Constructor以对象或者数组的身份去操控构造方法(接收了Class对象保存信息的一部分);Object类作为它们直接或间接的父类,相当于一个基类,创建的引用变量可以接收他们的对象;

PS:对newInstance的深入理解可以点这里直达

  • 成员变量的获取与调用
 package package1;

import java.lang.reflect.Field;

public class SecondRe{

    public static void main(String[] args) throws Exception {
        Class c1= Class.forName("package1.testM");    
        System.out.println("-----------所有公有获取变量-------------");
        Field[] f1 = c1.getFields();
        for(Field ff:f1){System.out.println(ff);}

        System.out.println("-----------公有变量调用-------------");
        Field ff1 = c1.getField("number1");
        System.out.println(ff1);
        Object obj1=c1.newInstance();       //产生testM对象
       // ff1.set(obj1,9);                  //为字段设置值,若该字段不想修改,也可以省去此句
        System.out.println(ff1.get(obj1));  //可得到对象中名为number的值,注意,使用了Object的引用变量,get();方法是Filed类中的方法,格式为get(Object obj)

        System.out.println("-----------获取私有变量-------------");
        Field f2 = c1.getDeclaredField("na1");
        System.out.println(f2);
        System.out.println(f2.getName());
        Object obj2= c1.newInstance();//产生testM对象
        f2.setAccessible(true);//设置访问权限
        //f2.set(obj2,"he");   // //为字段设置值,也可以省去此句
        System.out.println(f2.get(obj2));
}
}

 结果如下:

分析如下:

  • 该程序的调用方式与构造的方式相似,故不在详解
  • 对get(Object obj); 方法 API: Returns the value of the field represented by this Field, on the specified object. 表示返回该指定对象字段中的字段值
  • 同理,getName();方法 API:返回当前对象的名字。
  • set(obj,value),为obj字段赋值,setAccessible();为当前对象设置访问权限,true为许可,false为禁止;
  • 普通成员方法的获取与调用
  • package package1;
    
    import java.lang.reflect.Method;
    
    public class SecondRe{
    
        public static void main(String[] args) throws Exception {
            Class c1= Class.forName("package1.testM");       
    
            System.out.println("-----------获取所有公有方法------------");
            Method[] M1=c1.getDeclaredMethods();
            for(Method MM:M1){ System.out.println(MM); }
    
            System.out.println("-----------获取并调用公有方法------------");
            Method MM1=c1.getMethod("methods1",boolean.class);
            System.out.println(MM1);
           Object ObMM1 = c1.newInstance();
            //Object o=c1.getConstructor().newInstance();  //两种方式一样
            MM1.invoke(ObMM1,true);
            System.out.println(MM1.getName());
    
    
            System.out.println("-----------获取并调用私有方法------------");
            Method MM2 = c1.getDeclaredMethod("methods2",String.class);
            Object OBMM2 = c1.newInstance();
            MM2.setAccessible(true);
            MM2.invoke(OBMM2,"+私有噢+");
        }
    }
    结果如下:

分析如下:

  • 获取与调用的方式与上述差不多,就是换了一个类来接收方法对象
  • invoke(Object obj,Object... args) 调用具有指定参数的对象上的该方法

3.对三大详细信息的总结

  • 主要使用了Method,Constructor,Field这三个类。使用这三个类的对象或者数组可以保存三大信息
  • 该三大类的操作方法,结构十分相似,可以举一反三
  • 对私有信息进行操作之前,给予对象访问权限,可暴力访问(setAccessible(true)),否则会抛出IllegalAccessException异常
  • 三大类除了已列出的方法,自身还有许多方法,详情看看API文档,合适取用。
  • 对于main函数的反射也相同。
package package1;

import java.lang.reflect.Method;

public class SecondRe{

    public static void main(String[] args) throws Exception {
    Class c1= Class.forName("package1.testM");       
  Method methodMain = clazz.getMethod("main", String[].class);
  methodMain.invoke(null, (Object)new String[]{"a","b","c"});
    }
}

 

四、扩展部分

1.反射的其它使用:

  • 通过反射运行配置文件的内容
  • 通过反射越过泛型检查
  • 内省与反射

2.扩展知识

  • Class类被参数化,例如Class<Employee>的类型是Employee.class。它将已经抽象的概念更加复杂化了,在大多数实际问题中可以忽略类型参数,而使用原始的Class类。 
  • 异常的捕获与处理,因为反射经常会出现异常,例如空方法等,故需要做好安全问题
  • 以上我们已返回值为void进行的,其余返回值类型,相似处理,类比使用就行。

PS:若有问题,欢迎指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值