java反射机制的简单理解

初学java的时候,看到反射这一部分时完全是丈二的和尚摸不着头脑,工作两年后再看反射,突然就有一种柳暗花明的感觉。
这里记录一下我对反射的理解,可能不够详细,但是基本理解应该是没问题的。

反射有什么用?

可能你还不知道什么是反射,但是你一定用到过反射。
比如spring的IOC就是通过反射机制实现的,struts2配置action也用到了反射,在配置数据库驱动的时候也都用到了反射。

什么是反射?

定义:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
其实定义并不是用来记的,而是用来帮助我们理解的。
这一长串的定义,其实只有两个关键点:
1. 运行状态中,动态调用。
2. 对任一实体类都能获取并调用该类的方法和属性。
对于第一点,涉及到ClassLoader,这里不展开讲,我们只简单说一下,平时我们Object object = new Object();的时候,是静态加载,在JVM运行时,就已经把new的类的Class加载了,在new的时候,才分配内存空间。所以,这里说的动态调用是指程序运行的时候去创建一个没有被ClassLoader加载的类。
对于第二点,我们能获取该类的所有属性和方法,包括被private修饰的。

如何通过反射创建一个类的实例

首先创建一个类

package com.zhangyaoyu;

public class Dog {
    private String name;

    public void eat() {
        System.out.println("吃骨头");
    }
}

我们怎么不使用new关键字来创建一个Dog实例呢?

import com.zhangyaoyu.Dog;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Object object = cls.newInstance();
        Dog dog = (Dog) object;
        dog.eat();
    }
}

这种方法就是反射。
首先使用Class.forName()创建一个class,里面的参数必须跟完整的包名。这里要补充一点,所有的类都是Class,包括Obejct也是Class。
之后通过newInstance(),返回一个Object,再转换为Dog即可。
如果在运行时,找不到”com.zhangyaoyu.Dog”这个类,则会报java.lang.ClassNotFoundException,要注意,这里说的运行时是指程序运行到Class.forName(“com.zhangyaoyu.Dog”);这句话的时候。
那么,如果我们使用Dog dog = new Dog();会怎么样呢?第一种情况,如果没有Dog这个类,在编译的时候就会报错,告诉你找不到Dog。第二种情况,编译时有Dog类,并且编译完成了,之后把Dog.class删掉,那么在程序启动的时候,就会报错java.lang.NoClassDefFoundError。

通过有参构造函数创建实例

上面的例子中,Dog类的构造函数是默认无参的,很多时候我们都需要带参的去创建一个类。修改一下之前的Dog类。

package com.zhangyaoyu;

public class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + "喜欢吃骨头");
    }
}

这时Dog类就没有无参的构造函数了,在创建Dog的时候,必须有一个String类型的参数。

import com.zhangyaoyu.Dog;

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

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Constructor constructor = cls.getConstructor(String.class);
        Object object = constructor.newInstance("旺财");
        Dog dog = (Dog) object;
        dog.eat();
    }
}

这时,我们就需要对应的构造函数,去newInstance。

获取方法或属性列表

对Class的实例cls,我们可以获取这个类有那些方法和属性,并且可以发现有许多getXXX和getDeclaredXXX的方法,它们又有什么不同呢。

package com.zhangyaoyu;

public class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}
package com.zhangyaoyu;

public class Dog extends Animal{
    private String name;

    public void say() {
        System.out.println("我叫" + name);
    }

    private void play() {
        System.out.println("跑");
    }

    protected void sleep() {
        System.out.println("睡觉");
    }
}
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Method[] method = cls.getMethods();
        for (int i = 0; i < method.length; i++) {
            System.out.println(method[i]);
        }
        System.out.println("=============");
        Method[] methods2 = cls.getDeclaredMethods();
        for (int i = 0; i <methods2.length; i++) {
            System.out.println(methods2[i]);
        }
    }
}

运行结果

public void com.zhangyaoyu.Dog.say()
public void com.zhangyaoyu.Animal.eat()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
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()
=============
protected void com.zhangyaoyu.Dog.sleep()
public void com.zhangyaoyu.Dog.say()
private void com.zhangyaoyu.Dog.play()

这次我们发现,通过getMethods获取的方法列表中,除了Dog中public方法say之外,还有从父类Animal继承的public方法eat,当然了,它们都是继承了Object,所以也有Object的9种方法。
而通过getDeclaredMethods,我们可以获取到的方法全都是Dog类中声明的方法,并没有从父类中继承的方法,并且能够获取到除了public以外的方法。
获取属性列表也是同理。

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Field[] fields = cls.getFields();
        for (int i = 0; i < fields.length; i++) {
            System.out.println(fields[i]);
        }
        System.out.println("==================");
        Field[] fields2 = cls.getDeclaredFields();
        for (int i = 0; i < fields2.length; i++) {
            System.out.println(fields2[i]);
        }
    }
}

只有getDeclaredFields能够获取到private方法name。

调用方法

平常我们编程时,如果一个属性或者方法被private修饰后,我们就不能在类的外部去调用或修改,但是通过反射,我们就能够去使用它们。

package com.zhangyaoyu;

public class Dog {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    private void say() {
        System.out.println("我叫" + name);
    }
}
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Object object = cls.newInstance();
        Method method = cls.getMethod("setName", String.class);
        method.invoke(object, "旺财");

        Method methodSay = cls.getDeclaredMethod("say");
        methodSay.setAccessible(true);
        methodSay.invoke(object);
    }
}

这里我们通过getMethod找到对应的Method,要注意,我们需要名字和参数完全对应,才能确定一个Method。之后使用invoke方法,给对象传入参数,完成public方法的调用。
但是现在我们不知道调用成功了没有,那么我们尝试调用private的say方法。要注意,getMethod只能获取public方法,private方法需要使用getDeclaredMethod。我们之前说过,平时编程时,如果一个方法被private修饰,那么我们在类外面是不能调用的,这时出于安全性考虑的,但是通过反射我们可以做到,不过需要对method设置methodSay.setAccessible(true),之后才能调用private修饰的方法。

修改属性

package com.zhangyaoyu;

public class Dog extends Animal{
    private String name;

    public void say() {
        System.out.println("我叫" + name);
    }
}
import com.zhangyaoyu.Dog;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Object object = cls.newInstance();
        Field field = cls.getDeclaredField("name");
        field.setAccessible(true);
        field.set(object, "旺财");
        Dog dog = (Dog) object;
        dog.say();
    }
}

其实修改属性和调用方法思路都是一致的。

使用接口

最后一个问题,我们看到上面的例子中,很多情况都需要最终转化为某个类,比如Dog dog = (Dog) object;那么问题来了,有时我需要一个Dog,而有时候我又需要Cat怎么办?对应平时工作中的业务,比如短信接口,如果平台换了怎么办?没关系,我们都实现同一个接口就行了。

package com.zhangyaoyu;

public interface Animal {
    public void say();
}
package com.zhangyaoyu;

public class Cat implements Animal {
    @Override
    public void say() {
        System.out.println("喵喵喵");
    }
}
package com.zhangyaoyu;

public class Dog implements Animal {

    @Override
    public void say() {
        System.out.println("汪汪汪");
    }
}
import com.zhangyaoyu.Animal;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class cls = Class.forName("com.zhangyaoyu.Dog");
        Object object = cls.newInstance();
        Animal dog = (Animal) object;
        dog.say();
    }
}

比如这个例子中,Dog和Cat都实现了Animal接口,我们现在使用的是Dog类,如果需要使用Cat,把forName的参数换成”com.zhangyaoyu.Cat”即可,而这个参数一般保存在配置文件中,这样的话,当我们更换的时候,就不用重新编译代码,只用修改配置文件即可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据