深入剖析JAVA的反射机制

定义

在Java 的运行时环境中,对于任意的一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用任意的方法和属性,这种动态的获取类的信息以及动态的调用对象方法的功能,称之为反射(注意是运行状态,非编译)。

Java反射提供的主要功能:
  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
Java反射机制实现类:
  • Field类:代表类的成员变量
  • Method类:代表类的方法
  • Constructor类:代表类的构造方法
  • Array类:提供动态的创建数组
    • 以上四个类都位于java.lang.reflect 包中
  • Class类:代表一个类 , 位于java.lang 包中
反射的使用

在JAVA中,无论一个类生成多少个对象,这些对象都会对应同一个Class对象 。要想使用反射,首先需要获得待处理的类或者对象所对应的Class对象。该Class对象是在类被加载之后,由系统自动生成的。获取该Class对象有三种方式:

  1. 使用Class类的静态方法forName: Class.forName("java.lang.String") ,参数必须添加完整的路径,包括包名。
  2. 使用类的.class 语法: String.class
  3. 使用对象的getClass()方法: String s = "reflect" ;Class<?> clazz = s.getClass()

以上便是获取Class对象的三种方式,要使用反射,必须先获取Class对象,一旦获取了Class对象之后,就可以调用Class对象的方法来获得对象和该类的真实信息。

反射相关API ,使用反射生成并操作对象

Class对象可以获得带操作类里的方法(Method),构造器(constructor),Field 。要操作方法需要获得Mehtod对象,要操作构造器需要获得Constructor对象,要操作属性需要获得Field对象。

下面将会结合具体的实例,来说明生成Class对象的三种方式是如何使用的:

反射初体验,通过第一种方式,获取Class对象,并打印java.lang.String的所有声明方法:

Class<?> aClass = Class.forName("java.lang.String");  //获取String类的Class对象
Method[] methdos = aClass.getDeclaredMethods(); //获取所有的声明方法
for(Method m : methdos)  //循环遍历Method数组
   System.out.println(m);   //打印方法名称,包含了private 方法

运行结果:

部分运行结果截图

通过反射调用某个类的方法

public class InvokeTest {

    public int add(int a , int b){
        return a +b ;
    }
    public String echo(String message){
        return "Hello :" +message ;
    }

    public static void main(String[] args) throws Exception {
        //通过new的方式调用
        //InvokeTest invokeTest = new InvokeTest();
        //System.out.println(invokeTest.add(1 , 2));
        //System.out.println(invokeTest.echo("Tom"));

        //通过反射调用方法
        //通过内置语法,获取Class对象
        Class<InvokeTest> invokeTestClass = InvokeTest.class;
        //创建Class对象表示的类的实例 ,调用Class对象的newInstance()方法
        InvokeTest invokeTest = invokeTestClass.newInstance();
        
        //获取待操作类的Method对象。传入方法名字,和方法的参数类型 
        Method add = invokeTestClass.getDeclaredMethod("add", new Class[]{int.class, int.class});
        //使用这种方式也是可以得,因为getDeclaredMethod()第二个参数是一个可变参数
        //Method add = InvokeTestClass.getDeclaredMethod("add" , int.class , int.class) 
        Method echo = invokeTestClass.getDeclaredMethod("echo", new Class[]{String.class});
        
        //调用Method对象的invoke方法,表示含义为,调用invokeTest对象的add/echo方法,并传出参数值
        Object addResult = add.invoke(invokeTest, new Object[]{1, 2});
        Object echoResult = echo.invoke(invokeTest, new Object[]{"Tome"});
        
        //打印输出的结果
        System.out.println((Integer) addResult); 
        System.out.println((String) echoResult);  
        
        //运行结果
        //3
        //Hello :Tome
    }
}

通过反射调用某个类的构造方法 ,创建对象

public class ConstructorTest {
    public static void main(String[] args) throws Exception {
        User user = new User() ;
        //使用第三种获取Class对象的方式。使用getClass()方法
        Class<?> userClass = user.getClass();
        //调用无参构造方法,创建对象实例,反射无法得知返回的类型,需要强制类型转换
        Constructor<?> constructor = userClass.getConstructor();
        User user1 = (User)constructor.newInstance();
        //上面两行代码可以替换为下面一行代码
        //User u = (User)userClass.newInstance();
        user1.setAge(18);
        user1.setName("zhangsan");
        System.out.println(user1.getName() +"," + user1.getAge()); //zhangsan,18
        System.out.println("--------------------------");
        //调用有参构造方法,下面两行方法中的参数要一一对应
        Constructor<?> constructor1 = userClass.getConstructor(new Class[]{String.class, int.class});
        User user2 = (User)constructor1.newInstance(new Object[]{"lisi", 30});
        System.out.println(user2.getName() +"," +user2.getAge());
    }
}

class User{
    private String name ;
    private int age ;

    public User(){}

    public User(String name , int age ){
        this.name = name ;
        this.age = age ;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

通过反射生成对象有两种方式:

1.使用Class对象的newInstance()方法,这种方法要求对象的对应类有无参构造方法
2.先使用Class对象获得Constructor()对象,在调用Constructor对象的newInstance()方法。这种方式可以调用无参的构造方法,也可以调用有参的构造方法。但是Constructor()和newInstance()中的参数要一一对应。可参考上例。

通过反射操作类的属性

public class FieldTest {
    public static void main(String[] args) throws  Exception {
        //获取Class对象,并生成实例对象
        Class<?> personClass = Person.class;
        Object p = personClass.newInstance();
        //通过Class对象的getDeclaredField()方法,获取类的field
        Field name = personClass.getDeclaredField("name");
        Field age = personClass.getDeclaredField("age");
        //name字段为private 修饰,使用setAccessible()方法,取消访问权限检查
        name.setAccessible(true);
        //调用set()方法,设置p对象的值
        name.set(p , "zhangsan");
        age.set(p , 18);
        //打印值
        System.out.println(p); //name: zhangsan, age: 18 
    }
}

class Person{
    private String name ;
    public  int age ;

    @Override
    public String toString() {
        return  "name: "+name +", age: "+age ;
    }
}

通过setAccessible()方法,可以通过反射访问类中的私有属性和私有方法,通常不建议这样使用,因为它打破了封装的特性。但是在一些特定情况中,还是需要使用这种方式的。

通过反射操作数组

public class ArrayTest {

    public static void main(String[] args) {
        //创建一个类型为String, 长度为10 的数组 
        Object o = Array.newInstance(String.class, 10);
        
        //为数组赋值
        Array.set(o , 2 ,"hello");
        Array.set(o , 5 ,"world");
        
        //获取数组的值,并打印
        System.out.println(Array.get(o , 2));  //hello
        System.out.println(Array.get(o , 5)); //world
    }
}

上面简单介绍了几个常用的方法,旨在说明反射对方法,构造方法,字段,数组中的调用方式,除了上面的这些,还可以获取父类,接口,包 ,全部的方法声明,全部的field声明等与类相关的全部信息。想要了解更多内容,请自行查阅API。

通过反射生成动态代理

先看代码,稍后再解释动态代理的实现过程,这个地方可能有点难理解,多看几遍就可以了。

/*定义接口*/
public interface Subject {
    public void sayHello(String name);
}

/*定义接口的实现类,也就是真实的代理调用对象*/
public class RealSubject implements Subject{
    public void sayHello(String name ) {
        System.out.println("hello : " + name );
    }
}

/*定义动态代理类*/
public class DynamicProxy implements InvocationHandler{

    private Object obj = null ; //此处定义了Object类型,表示可以代理任何对象
    public DynamicProxy(Object obj){
        this.obj = obj ;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        //调用obj对象的method方法,并传入参数args,本例中相当于调用obj对象的sayHello()方法,并传入Tom 参数
        method.invoke(obj , args) ;
        after();
        return null ; //Subject接口中方法没有返回值,返回Null 
    }

    //此方法表示代理的额外操作。
    private void before(){
        System.out.println("before proxy");
    }
    //此方法表示代理的额外操作
    private void after(){
        System.out.println("after proxy");
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {

        //需要代理的真实对象
        RealSubject realSubject = new RealSubject() ;
        //创建代理类,并将真实的代理对象,传入构造方法中
        InvocationHandler handler = new DynamicProxy(realSubject);

        //运行时动态生成的代理实例,实现了RelSubject类所实现的接口,所以可以强制转换为Subject类型,第一个参数是类加载器,第二个参数是代理类实现的接口,第三个参数是处理实际工作的handler实例
        Subject subject = (Subject)Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
        //调用接口中声明的方法 ,流程跳转到handler的invoke()方法中执行。
        subject.sayHello("Tom");
    }
}

总结:

Java的动态代理类位于java.lang.reflect包下,涉及到两个类:
interface InvocationHandler: 该接口中只定义了一个方法-invoke(),使用动态代理类时,必须实现这个接口。
Porxy:该类即为动态代理类,常用newProxyInstance()来生成代理实例,它不会做什么实质性的工作,实质性的工作是由与之关联的InvocationHandle来处理的。也就是说生成的代理对象都有一个与之关联的InvocationHandler对象。

动态代理的使用步骤:

  • 创建被代理类的接口和实现类,因为动态代理是基于接口的。这也是动态代理的一个小小缺憾。
  • 创建实现InvocationHandler接口的类,并实现invoke()方法
  • 通过Proxy的静态方法newProxyInstance(ClassLoader loader ,Class[] interfaces ,InvocationHandler handler) 创建一个代理实例。
  • 通过上面创建的代理实例,调用方法

至此,End!



少年听雨歌楼上,红烛昏罗帐。  
壮年听雨客舟中,江阔云低,断雁叫西风。
感谢支持!
                                        ---起个名忒难

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 一、概述 Java反射机制定义 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法...
    CoderZS阅读 1,630评论 0 26
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • 一、概述 1、Java反射机制(Java-Reflect): 在运行状态中,对于任意一个类,都能够知道这个类中的所...
    年少懵懂丶流年梦阅读 4,376评论 0 5
  • 带节点进度条的实现方法不止一个,但是如果要实现图中这种效果的,初步看好像还不简单。进度条的形状不规则、背景是渐变颜...
    NanBox阅读 4,609评论 3 35
  • 事实证明,二年级玩奥数是可以的,请看下图: 为什么孩子会如此兴奋,因为我们这里是俱乐部,不是集训营! 在这里老师像...
    何立泵阅读 242评论 0 2