Java中的反射

什么是反射呢?

Java的反射机制是在运行状态中,对于任何一个类,我们都能知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能叫做反射机制。

我们为什么要用反射呢?

首先我们先明确两个概念,静态编译和动态编译。

静态编译:在编译时确定类型,绑定对象
动态编译:在运行是确定类型,绑定对象

动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。
而反射就是运用了动态编译创建对象。

  • 先举个例子来说明
public class FruitFactory {

    interface fruit{
        void eat();
    }

    private static class Apple implements fruit{
        @Override
        public void eat() {

        }
    }

    private static class Watermelon implements fruit{
        @Override
        public void eat() {

        }
    }

    public static fruit getInstance(String fruitName){
            fruit fName = null;
            if ("Apple".equals(fruitName)){
                fName = new Apple();
            }else if ("Watermelon".equals(fruitName)){
                fName = new Watermelon();
            }else {
                try {
                    throw new Exception("类型错误!");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return fName;

    }

    static class Demo{
        public static void main(String[] a){
            fruit apple = FruitFactory.getInstance("Apple");
            apple.eat();
        }
    }
}

当我们每增加一种水果是不得不修改工厂类中的方法,随着水果的增加,工厂会越来越臃肿,我们来对比一下反射如何做呢

public static fruit getInstance(String ClassName){
    fruit fName = null;
    try{
        fName = (fruit)Class.forName(ClassName).newInstance();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return fName;
}

static class Demo{
    public static void main(String[] a){
        fruit f= FruitFactory.getInstance("com.csii.tech.Apple");
        if(f!=null){
            f.eat();
        }
    }
}

看到了吧,这就是反射的优越性,完全不用去修改原有代码。
为什么我们在写代码的时候很少用的或时看到呢,因为它的开销很昂贵,所以尽量在最需要的时候用反射。

怎么去用反射呢?

这里需要跟大家说一下,所谓反射其实是获取类的字节码文件,也就是.class文件,那么我们就可以通过Class这个对象进行获取。
1、根据对象:对象.getClass()

Test test =  new Test();
test.getClass();

2、根据类名:类名.class

Test.getClass();

3、根据全限定类名:Class.forName(全限定类名)

"这里需要注意,通过类的全路径名获取Class对象会抛出一个异常,如果根据类路径找不到这个类那么就会抛出这个异常。"
try {
    Class test = Class.forName("com.csii.tech.Test");//字符串完整路径包含包名
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

那么这3中方式我们一般选用哪种方式呢?第一种已经创建了对象,那么这个时候就不需要去进行反射了,显得有点多此一举。第二种需要导入类的包,依赖性太强。所以我们一般选中第三种方式

如何通过反射获取类的构造方法、方法以及属性呢?

先放一个用来测试的对象

public class Test {
    private static final String TAG = "Test";
    public String publicStr = "0";
    private String fruitName = "apple";
    private int fruitNumber = 0;

    public String getFruitName() {
        return fruitName;
    }

    public void setFruitName(String fruitName) {
        this.fruitName = fruitName;
    }

    public int getFruitNumber() {
        return fruitNumber;
    }

    public void setFruitNumber(int fruitNumber) {
        this.fruitNumber = fruitNumber;
    }

    //-------构造方法------------
    public Test(){
        System.out.println("调用了无参构造方法");
    }

    public Test(String str){
        System.out.println("调用了有参构造方法");
    }


    private Test(Integer integer){
        System.out.println("调用了私有参构造方法");
    }

    public void a(){
        System.out.println("调用了公共方法");
    }

    public void a(String a){
        System.out.println("调用了公共方法"+a);
    }

    private void d(){
        System.out.println("调用了私有方法");
    }

    private void d(Integer integer){
        System.out.println("调用了私有方法"+integer.toString());
    }
}

1、 获取对象的构造方法
public class MyClass {
    public static void main(String[] str){
        try {
            Class test =Class.forName("com.lrd.lib.Test");

            System.out.println("-----------所有的构造方法-------------");
            Constructor[] declaredConstructors = test.getDeclaredConstructors();
            for (Constructor c : declaredConstructors){
                System.out.println(c);
            }

            System.out.println("-----------所有的公共构造方法-------------");
            Constructor[] constructors = test.getConstructors();
            for (Constructor c : constructors){
                System.out.println(c);
            }

            System.out.println("-----------公共&无参 构造方法-------------");
            Constructor constructor = test.getConstructor(null);
            System.out.println(constructor);

            System.out.println("-----------公共&有参 构造方法-------------");
            Constructor constructor1 = test.getConstructor(new Class[]{String.class});
            System.out.println(constructor1);

            System.out.println("-----------私有&有参 构造方法-------------");
            Constructor declaredConstructor = test.getDeclaredConstructor(new Class[]{Integer.class});
            System.out.println(declaredConstructor);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

OK,接下来看控制台输出

-----------所有的构造方法-------------
public com.lrd.lib.Test(java.lang.String)
private com.lrd.lib.Test(java.lang.Integer)
public com.lrd.lib.Test()
-----------所有的公共构造方法-------------
public com.lrd.lib.Test(java.lang.String)
public com.lrd.lib.Test()
-----------公共&无参 构造方法-------------
public com.lrd.lib.Test()
-----------公共&有参 构造方法-------------
public com.lrd.lib.Test(java.lang.String)
-----------私有&有参 构造方法-------------
private com.lrd.lib.Test(java.lang.Integer)
2、获取类的属性
System.out.println("-----------所有的字段-------------");
Field[] declaredFields = test.getDeclaredFields();
for (Field field : declaredFields){
    System.out.println(field);
}
System.out.println("-----------所有的公共字段-------------");
Field[] fields = test.getFields();
for (Field field : fields){
    System.out.println(field);
}

控制台打印结果

-----------所有的字段-------------
private static final java.lang.String com.lrd.lib.Test.TAG
public java.lang.String com.lrd.lib.Test.publicStr
private java.lang.String com.lrd.lib.Test.fruitName
private int com.lrd.lib.Test.fruitNumber
-----------所有的公共字段-------------
public java.lang.String com.lrd.lib.Test.publicStr

当我们知道字段名后就可以使用它们了,如下

System.out.println("-----------使用公有字段-------------");
Field publicStr = test.getField("publicStr");
Object obj = test.getConstructor().newInstance();
Test newTest = (Test) obj;
System.out.println("默认值:"+newTest.publicStr);
publicStr.set(obj,"1");
System.out.println("更改后:"+newTest.publicStr);

System.out.println("-----------使用私有字段-------------");
Field fruitName = test.getDeclaredField("fruitName");
Object obj2 = test.getConstructor().newInstance();
fruitName.setAccessible(true);//暴力反射
Test newTest2 = (Test) obj2;
System.out.println("默认值:"+newTest2.getFruitName());
fruitName.set(obj2,"watermelon");
System.out.println("更改后:"+newTest2.getFruitName());

控制台打印结果

-----------使用公有字段-------------
调用了无参构造方法
默认值:0
更改后:1
-----------使用私有字段-------------
调用了无参构造方法
默认值:apple
更改后:watermelon

这里有个点要注意,使用私有字段时要使用.setAccessible(true)方法暴力反射,不然就会报如下错误

java.lang.IllegalAccessException: Class com.lrd.lib.MyClass can not access a member of class com.lrd.lib.Test with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
    at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
    at java.lang.reflect.Field.set(Field.java:761)
    at com.lrd.lib.MyClass.main(MyClass.java:62)

3、获取类中的方法

System.out.println("-----------获取所有的方法-------------");
Method[] declaredMethods = test.getDeclaredMethods();
for (Method method : declaredMethods){
    System.out.println(method);
}
Thread.sleep(1000);
System.out.println("-----------获取所有公共的方法-------------");
Method[] methods = test.getMethods();
for (Method method : methods){
    System.out.println(method);
}
Thread.sleep(1000);
System.out.println("-----------获取指定的方法-------------");
Method a = test.getMethod("a");
System.out.println(a);
Method b = test.getDeclaredMethod("d");
System.out.println(b);
System.out.println("-----------获取指定的方法 含有参数-------------");
Method b2 = test.getDeclaredMethod("d",Integer.class);
b2.setAccessible(true);
System.out.println(b2);
//使用这个方法
Object obj = test.getConstructor().newInstance();
Object invoke = b2.invoke(obj, 100);

控制台打印结果(这里面有Object里的方法,也同时反射出来了,不清楚Object里方法的同学请移步到这里https://www.jianshu.com/p/ea26dba0acc5
)

-----------获取所有的方法-------------
public java.lang.String com.lrd.lib.Test.getFruitName()
public void com.lrd.lib.Test.setFruitName(java.lang.String)
public int com.lrd.lib.Test.getFruitNumber()
public void com.lrd.lib.Test.setFruitNumber(int)
public void com.lrd.lib.Test.a(java.lang.String)
public void com.lrd.lib.Test.a()
private void com.lrd.lib.Test.d()
private void com.lrd.lib.Test.d(java.lang.Integer)
-----------获取所有公共的方法-------------
public java.lang.String com.lrd.lib.Test.getFruitName()
public void com.lrd.lib.Test.setFruitName(java.lang.String)
public int com.lrd.lib.Test.getFruitNumber()
public void com.lrd.lib.Test.setFruitNumber(int)
public void com.lrd.lib.Test.a(java.lang.String)
public void com.lrd.lib.Test.a()
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 final void java.lang.Object.wait() 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()
-----------获取指定的方法-------------
public void com.lrd.lib.Test.a()
private void com.lrd.lib.Test.d()
-----------获取指定的方法 含有参数-------------
private void com.lrd.lib.Test.d(java.lang.Integer)
调用了无参构造方法
调用了私有方法100

最后非常感谢前人的文章

https://www.cnblogs.com/zhaoguhong/p/6937364.html
https://www.cnblogs.com/yrstudy/p/6500982.html
https://baijiahao.baidu.com/s?id=1619748187138646880&wfr=spider&for=pc

屏幕快照 2019-07-24 下午4.30.40.png

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