Java反射机制详解

前言

Java反射机制很早的时候就有耳闻,期间也会去看看相关资料,但是又很快会忘记,所以,写一篇Blog来加深记忆与理解!!

Java反射的定义

Java反射机制是指在运行状态(非编译)中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

Java反射机制的优点与缺点

在说明反射的优缺点前,我们需要知道一个概念:静态编译和动态编译
静态编译:在编译时确定类型,绑定对象。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,可以降低类之间的藕合性。

优点

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

Class类和类类型

Java反射机制中最基础的类就是Class类了。(There is a class named Class)
Class 类是什么类呢?
我们平时所说的类其实也是一个对象,都是Class这个类的对象。换句话说:(平时所说的)类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)。

如何创建Class对象

对于普通的对象,我们一般都会这样创建和表示:

Code code1 = new Code(); 

上面说了,所有的类都是Class的对象,那么如何表示呢,可不可以通过如下方式呢:

Class c = new Class();

我们去看看源码中的Class类

  /*
     * Constructor. Only the Java Virtual Machine creates Class
     * objects.
     */
    private Class() {}

可以看到他的构造函数是私有的而且只能由JVM来构建对象,我们知道,为了防止在其他地方构建类,我们就会把构造函数设为私有的。所以,这里不能用常规的方法构建Class的对象,那总是有办法的。下面我们就来看看有什么方法可以创建Class的对象呢?Java给我们提供了三种方法:

Class c1 = Code.class;
//这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的
Class c2 = code1.getClass();
//code1是Code的一个对象,这种方式是通过一个类的对象的getClass()方法获得的
Class c3 = Class.forName("com.trigl.reflect.Code");
//这种方法是Class类调用forName方法,通过一个类的全量限定名获得

这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。
这里就让人奇怪了,前面不是说Code是Class的对象吗,而c1、c2、c3也是Class的对象,那么Code和c1、c2、c3不就一样了吗?为什么还叫Code什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。

来个demo吧!

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种:Class c1 = Code.class;
        Class class1=ReflectDemo.class;
        System.out.println(class1.getName());

        //第二种:Class c2 = code1.getClass();
        ReflectDemo demo2= new ReflectDemo();
        Class c2 = demo2.getClass();
        System.out.println(c2.getName());

        //第三种:Class c3 = Class.forName("com.trigl.reflect.Code");
        Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
        System.out.println(class3.getName());
    }
}

执行结果:

com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

反射相关操作

前面我们知道了怎么获取Class,那么我们可以通过这个Class干什么呢?
总结如下:

1.获取与调用成员方法Method
2.获取操作成员变量Field
3.获取构造函数Constructor

下面来分别介绍着几种技能:

1.获取与调用成员方法:

1.单独获取某一个方法是通过Class类的以下方法获得的:

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的
public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的

两个参数分别是方法名和方法参数类的类类型列表。
例如类A有如下一个方法:

public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"岁");
    }

现在知道A有一个对象a,那么就可以通过:

Class c = Class.forName("com.tengj.reflect.Person");  //先生成class
Object o = c.newInstance();                           //newInstance可以初始化一个实例
Method method = c.getMethod("fun", String.class, int.class);//获取方法
method.invoke(o, "tengj", 10);                              //通过invoke调用该方法,参数第一个为实例对象,后面为具体参数值

完整代码如下:

public class Person {
    private String name="Simon";
    private int age;
    private String msg="hello wrold";
 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;
  }

    public Person() {
    }

    private Person(String name) {
        this.name = name;
  System.out.println(name);
  }

    public void fun() {
        System.out.println("fun");
  }

    public void fun(String name,int age) {
        System.out.println("我叫"+name+",今年"+age+"岁");
  }
}

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Object o = c.newInstance();
            Method method = c.getMethod("fun", String.class, int.class);
            method.invoke(o, "tengj", 10);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

我叫tengj,今年10岁

通过Class类获取Person类类型(上面提到过的),在通过newInstance()方法获取Person的实例。这里需要借助Method的invoke()方法调用fun()方法。

2.获取所有方法的数组:

Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 得到该类所有的方法,不包括父类的
或者:
Method[] methods = c.getMethods();// 得到该类所有的public方法,包括父类的

然后循环这个数组就得到每个方法了:

for (Method method : methods)

完整代码如下:
person类跟上面一样,这里以及后面就不贴出来了,只贴关键代码

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Method[] methods = c.getDeclaredMethods();
            for(Method m:methods){
                String  methodName= m.getName();
                System.out.println(methodName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

getName
setName
setAge
fun
fun
getAge

这里如果把c.getDeclaredMethods();改成c.getMethods();执行结果如下,多了很多方法,以为把Object里面的方法也打印出来了,因为Object是所有类的父类:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

2.获取与调用成员变量:

想一想成员变量中都包括什么:成员变量类型+成员变量名
类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。

单独获取某个成员变量,通过Class类的以下方法实现:

public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量

参数是成员变量的名字。
例如一个类A有如下成员变量:

private int n;

如果A有一个对象a,那么就可以这样得到其成员变量:

Class c = a.getClass();
Field field = c.getDeclaredField("n");

这样我们就可以获取他的成员变量了,当然,通过Field类我们还可以对变量进行操作。

    public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //获取成员变量
            Field field = c.getDeclaredField("msg"); //因为msg变量是private的,所以不能用getField方法
            Object o = c.newInstance();
            field.setAccessible(true);//设置是否允许访问,因为该变量是private的,所以要手动设置允许访问,如果msg是public的就不需要这行了。
            Object msg = field.get(o);
            System.out.println(msg);
            field.set(o,"hello Android");
            Object msg1 = field.get(o);
            System.out.println(msg1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

执行结果:

hello wrold
hello Android

同样,如果想要获取所有成员变量的信息,可以通过以下几步
1.获取所有成员变量的数组:

Field[] fields = c.getDeclaredFields();

2.遍历变量数组,获得某个成员变量field

for (Field field : fields)

完整代码:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            Field[] fields = c.getDeclaredFields();
            for(Field field :fields){
                System.out.println(field.getName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

name
age
msg

获取构造函数

最后再想一想构造函数中都包括什么:构造函数参数
同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。

单独获取某个构造函数,通过Class类的以下方法实现:

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所以public构造器,包括父类

这个参数为构造函数参数类的类类型列表。
例如类A有如下一个构造函数:

public A(String a, int b) {
    // code body
}

那么就可以通过:

Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

来获取这个构造函数。

完整代码:

public class ReflectDemo {
    public static void main(String[] args){
        try {
            Class c = Class.forName("com.tengj.reflect.Person");
            //获取构造函数
            Constructor constructor = c.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);//设置是否允许访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不需要这行了。
            constructor.newInstance("tengj");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

tengj

注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

获取所有的构造函数,可以通过以下步骤实现:

1.获取该类的所有构造函数,放在一个数组中:

Constructor[] constructors = c.getDeclaredConstructors();

2.遍历构造函数数组,获得某个构造函数constructor:

for (Constructor constructor : constructors)

完整代码:

public class ReflectDemo {
    public static void main(String[] args){
            Constructor[] constructors = c.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                System.out.println(constructor);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)

通过反射了解集合泛型的本质

首先下结论:

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

下面通过一个实例来验证:

public class GenericEssence {
    public static void main(String[] args) {
        List list1 = new ArrayList(); // 没有泛型 
        List<String> list2 = new ArrayList<String>(); // 有泛型


        /*
         * 1.首先观察正常添加元素方式,在编译器检查泛型,
         * 这个时候如果list2添加int类型会报错
         */
        list2.add("hello");
//      list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
        System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1


        /*
         * 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2
         * 的类类型相同,然后再通过方法反射绕过编译器来调用add方法,看能否插入int
         * 型的元素
         */
        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1 == c2); // 结果:true,说明类类型完全相同

        // 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
        try {
            Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
            m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
            System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增加了,并没有泛型检查
        } catch (Exception e) {
            e.printStackTrace();
        }

        /*
         * 综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入
         * 运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。
         */
    }
}

执行结果:

list2的长度是:1
true
list2的长度是:2

思维导图

最后给出一个别人整理的思维导图:

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

推荐阅读更多精彩内容