Java基础回归之反射Reflection

反射是什么鬼

反射其实就是允许我们获取目标类的方法、成员变量等信息,以及可以调用、改变某些方法和成员变量的值。( JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。)--莎士比亚

详细见某百科反射百科,这个不是重点,下面开始反射之旅。

反射的使用

先上一张反射相关知识点的脑图,如果对脑图上东西一窍不通或者有些模糊,那么请往下看。

反射Reflection.png
  • 反射机制相关的类

  • java.lang.Class; //类

  • java.lang.reflect.Constructor;//构造方法

  • java.lang.reflect.Field; //类的成员变量

  • java.lang.reflect.Method;//类的方法

  • java.lang.reflect.Modifier;//访问权限

  • java.lang.reflect.Parameter;//方法参数

  • Classclass的区别

  • class:小写字母c开头的class是声明一个类的关键字。

  • Class:在Java中,反射的源头就是Class,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,它封装了这个类的信息,包括上面说的反射机制相关的类等信息。

  • Class的获取
    上面说了,Class类用于封装类的各种信息,它是反射的源头,获取一个类的Class通常有以下三种方法:

  • Class cls = A.class;

  • A a = new A();
    Class cls = a.getClass();

  • Class cls = Class.forName("com.xx.A");

三种方法获取Class.png
  • 通过获取到的Class生成一个实例对象
    A a = (A) cls.newInstance(); //获取A的实例对象
    假设我们有一个Car类,里面有一个start()方法,我们可以以下方法得到Car类的实例并调用start()方法,注意,需要无参构造函数:

    反射获取Car实例.png

  • 类信息的获取

获取类的方法API 说明
Method[] methods = cls.getDeclaredMethods() 获取当前类声明的所有方法列表(无视权限修饰符)
Method[] methods = cls.getMethods() 获取当前类及父类所有public方法列表
Method method = cls.getDeclaredMethod(name, parameterTypes) 获取当前类声明的某个特定方法
Method method = cls.getMethod(name, parameterTypes) 获取当前类或父类某个特定public方法
获取类的成员变量API 说明
Field[] fields = cls.getDeclaredFields() 获取当前类声明的所有成员变量列表(无视权限修饰符)
Field[] fields = cls.getFields() 获取当前类及父类所有public成员变量列表
Field field = cls.getDeclaredField(name) 获取当前类声明的某个特定成员变量
Field field = cls.getField(name) 获取当前类或父类某个特定public成员变量
获取类的构造函数API 说明
Constructor[] constructors = cls.getDeclaredConstructors() 获取当前类声明的所有成员变量列表(无视权限修饰符)
Constructor[] constructors = cls.getConstructors() 获取当前类及父类所有public构造函数列表
Constructor constructor = cls.getDeclaredConstructor(parameterTypes) 获取当前类声明的某个特定构造函数
Constructor constructor = cls.getConstructor(parameterTypes) 获取当前类或父类某个特定public构造函数
获取类或方法的修饰符API 说明
int modifiers = cls.getModifiers() 获取类的修饰符,注意返回值是int类型,可通过Modifier.toString(modifiers)获取到相对应的String类型
int modifiers = method.getModifiers() 获取方法的修饰符,同上

修饰符返回值int类型对应的修饰符如下图:

访问修饰符.png
获取方法的参数API 说明
Class[] parameterTypes =method.getParameterTypes() 获取方法参数的类类型列表(即.class,如int.class)
Parameter[] parameters = method.getParameters() 获取方法参数列表(即.class,如int.class)
获取编程元素上的注解API 说明
Annotation[] annotations = cls(/method/field).getAnnotations() 获取类/方法/成员变量上标注的所有注解
Annotation[] declaredAnnotations = cls(/method/field).getDeclaredAnnotations() 获取类/方法/成员变量上标注的所有注解
Override annotation = cls(/method/field).getAnnotation(Override.class) 获取类/方法/成员变量上某个特定注解
Annotation[][] parameterAnnotations = m.getParameterAnnotations() 获取方法参数上的注解
boolean b = cls(/method/field).isAnnotationPresent(Bind.class) 该变成元素上是否有Bind.class注解

getDeclaredAnnotation(s):返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
getAnnotation(s):返回此元素上存在的所有注释。(如果此元素没有注释,则返回长度为零的数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
getDeclaredAnnotations得到的是当前成员所有的注释,不包括继承的。而getAnnotations得到的是包括继承的所有注释。
关键在于继承的问题上,getDeclaredAnnotations和getAnnotations是否相同,就在于父类的注解是否可继承,这可以用sun.reflect.annotation.AnnotationType antype3=AnnotationType.getInstance(Class.forName(annotationtype_class(example:"javax.ejb.Stateful")).isInherited())来判定,如果为true,说明可以被继承则存在与getAnnotations之中而不在getDeclaredAnnotations之中,否则,也不存在与getannnotations中,因为不能被继承。

  • 通过反射获取类中某个方法并调起
//我们有这个Car类
public class Car {
    @Override
    public void start() {
        System.out.println("Car start!");
    }
}
Car car = new Car();
Class cls = car.getClass();//获取类类型
Method method = cls.getDeclaredMethod("start", new Class[]{});//获取start方法,第一个参数为方法名字,  
//第二个参数是一个可变参数,接收的是方法的参数的类类型列表,此处Car里面有一个方法是无参的start(),则传入new Class[]{};同理,假设Car类还有一个重载的start(int speed)方法,则我们应该通过cls.getDeclaredMethod("start", new Class[]{int.class});
//method.setAccessible(true);//注意如果目标方法是private修饰的,则需要先调用setAccessible(true)破封装,此处Car类里的start方法是public,所以不需要调用
Object obj = method.invoke(car, new Class[]{});//传入的和上面的获取方法参数同理,此处不说。  
//需要说明的是如果方法没有返回值,则返回是null,即我们这里接收到的obj是null;如果对应方法有返回值则返回具体的返回值。
  • 通过反射获取类中某个成员变量并修改它的值
//1.获取intField成员变量
Field declaredField = cls.getDeclaredField("intField");
//2.如果该成员变量是private,此时我们应该先设置为可操作
declaredField.setAccessible(true);
//3.赋值
declaredField.set(ca, 5);```

## 举个栗子
下面通过一个栗子把上面所说反射的使用全部串联起来。需求:打印类中所有信息,并修改某个成员变量。具体看注释,并且跟着手打一遍印象深刻点,这里直接上代码。

首先我们声明一个父类:BaseActivity
```java
public class BaseActivity {
    
    public int basePublicFiled;//父类公有成员变量
    private String basePrivateFiled;//父类私有成员变量
    
    private BaseActivity(){
        //父类私有构造函数
    }
    
    public BaseActivity(int i){
        //父类公有构造函数
    }
    
    public void basePublicMethod(String str){
        //父类公有方法
    }
    
    private void basePrivateMethod(String str){
        //父类私有方法
    }
}

然后声明一个ChildActivity继承它:

public class ChildActivity extends BaseActivity{

    public ChildActivity(int i) {
        super(i);
    }
    
    public int childPublicFiled;
    private String childPrivateFiled;
    
    public void childPublicMethod(String str){
        //no-op
    }
    
    private void childPrivateMethod(String str){
        //no-op
    }
    
    private int intField = 0;//注意,这里值是0,待会反射修改这个成员变量的值
    public void plus(int i){
        //待会反射调用此方法,注意看原始值和反射后打印出来的结果
        System.out.println(String.valueOf(i) +" + "+ String.valueOf(intField) + " = " + (i + intField));
    }
    
}

编写核心类,取名ClassMessageGetter,用于获取反射操作

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;

import entity.ChildActivity;

public class ClassMessageGetter {

    /**
     * 获取某个类类信息
     * @param obj
     */
    public static void getMethodMessage(Object obj){
        
        //获取方法
        //1.getMethods()获取该类所有public,包括父类的public方法
        //2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)
        System.out.println("1.getMethods()获取该类所有public,包括父类的public方法\n");
        Class cls = obj.getClass();
        Method[] methods = cls.getMethods();
        Method[] declaredMethods = cls.getDeclaredMethods();
        for(Method method : methods){
            //通过method.getReturnType().getName()获取返回值名称
            //通过method.getName()获取方法名称
            int modifiers = cls.getModifiers();
            System.out.print(Modifier.toString(method.getModifiers())+" "+method.getReturnType().getName() + " " + method.getName()+"(");
            //通过method.getParameterTypes();获取方法参数的类类型
            Class[] parameterTypes = method.getParameterTypes();
            //通过method.getParameters()获取方法参数
            Parameter[] parameters = method.getParameters();
            for(int  i = 0 ; i < parameterTypes.length ; i ++){
                System.out.print(parameterTypes[i].getSimpleName() + " " +parameters[i].getName()+ (i!=parameterTypes.length-1?",":""));
            }
            System.out.println(")");
        }
        
        System.out.println("\n-----------------------------------------------------------\n");
        System.out.println("2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)\n");
        
        for(Method method : declaredMethods){
            System.out.println(method.getName());
        }
        
    }
    
    /**
     * 获取类成员变量信息
     * @param obj
     */
    public static void getFieldMessage(Object obj){
        Class cls = obj.getClass();
        //1.getFields()获取该类所有public,包括父类的public成员变量
        Field[] fields = cls.getFields();
        //2.getDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)
        Field[] declaredFields = cls.getDeclaredFields();
        System.out.println("cls.getFields() 获取该类所有public,包括父类的public成员变量");
        for (Field field : fields) {
            System.out.println(field.getType().getSimpleName() + " "+field.getName());
        }
        
        System.out.println("\ngetDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)");
        for (Field field : declaredFields) {
            System.out.println(field.getType().getSimpleName() + " "+field.getName());
        }
    }
    
    /**
     * 获取构造函数信息
     * @param obj
     */
    public static void getConstructorMessage(Object obj){
        Class cls = obj.getClass();
        Constructor[] constructors = cls.getConstructors();
        Constructor[] declaredConstructors = cls.getDeclaredConstructors();
        
        for (Constructor constructor : constructors) {
            System.out.println(constructor.getName());
        }
        
        System.out.println("------------------");
        for (Constructor constructor : declaredConstructors) {
            System.out.println(constructor.getName());
        }
        
        
    }
    
    
    /**
     * 通过反射操作方法和成员变量
     */
    public  static void reflectionProcess(){
        
        ChildActivity ca = new ChildActivity(0);
        Class cls = ca.getClass();
        try {
            //修改filed值
            //1.获取intField成员变量
            Field declaredField = cls.getDeclaredField("intField");
            //2.因为该成员变量是private,此时我们应该先设置为可操作
            declaredField.setAccessible(true);
            //3.赋值
            declaredField.set(ca, 5);
            
            //反射调用方法
            //1.获取目标方法plus(int i)
            Method method  = cls.getDeclaredMethod("plus", int.class);
            //因为该方法是public的,因此不需要调用 method.setAccessible(true);
            //2.调用method.invoke(对象,参数列表)
            method.invoke(ca, 6);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
}

最后写一个测试类Demain进行测试:

public class Domain {

    public static void main(String[] args) {
        System.out.println("===================方法信息===================");
        ClassMessageGetter.getMethodMessage(new ChildActivity(0));
        System.out.println("===================成员信息===================");
        ClassMessageGetter.getFieldMessage(new ChildActivity(0));
        System.out.println("===================构造函数信息===================");
        ClassMessageGetter.getConstructorMessage(new ChildActivity(0));
        System.out.println("===================修改成员变量及调用方法信息===================");
        ClassMessageGetter.reflectionProcess();
    }
}

打印结果如下:

===================方法信息===================
1.getMethods()获取该类所有public,包括父类的public方法

public void plus(int arg0)
public void childPublicMethod(String arg0)
public void basePublicMethod(String arg0)
public final void wait()
public final void wait(long arg0,int arg1)
public final native void wait(long arg0)
public boolean equals(Object arg0)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()

-----------------------------------------------------------

2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)

plus
childPublicMethod
childPrivateMethod
===================成员信息===================
cls.getFields() 获取该类所有public,包括父类的public成员变量
int childPublicFiled
int basePublicFiled

getDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)
int childPublicFiled
String childPrivateFiled
int intField
===================构造函数信息===================
entity.ChildActivity
------------------
entity.ChildActivity
===================修改成员变量及调用方法信息===================
6 + 5 = 11

到此,Java反射基本就完结。最后说一个与反射相关的东西,叫“类型擦除”,它和泛型有关。

泛型是1.5中引入的一个新的概念,由于不用进行强制转换类型了,所以具有较高的安全性和易用性。因为泛型其实只是在编译器中实现的而虚拟机并不认识泛型类项,所以要在虚拟机中将泛型类型进行擦除。也就是说,在编译阶段使用泛型,运行阶段取消泛型,即擦除

就是说泛型机制其实是在编译期进行“检查”,因此,只要我们能够绕过编译期,就可以为所欲为。先看下面代码:

泛型检查.png

可以看到,当我们add进去的类型不是String时,编译器就会报错,那么有什么办法可以add进去int类型的值吗?上面说了,泛型机制是在编译期进行的,所以我们可以通过反射,add进去其他类型的值。
先看下面代码:
list类类型比较.png

从上面的代码我们可以知道,两个list的Class其实是同一份,从侧面印证了泛型机制是在编译期进行的。接下来即将发生神奇的事情:

类型擦除.png

最终结果size打印的是3,说明我们成功地往猫碗(List<String>)里面撒了狗粮(int)。

The End

转载请注明出处。

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

推荐阅读更多精彩内容

  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,827评论 3 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 和往常一样,在天黑透的时候,赵英回到了租住的地方。 左手拎着冷掉的包子,右手攥着新出炉的报纸,想起杂乱的屋子,第三...
    逯晓风阅读 415评论 0 1
  • 2016年10月6日 镶珠花瓣凝水滴, 秋风轻拂润肤肌, 千树绿褪叶渐黄, 惟我犹自着彩衣。
    叶知秋yzq阅读 156评论 0 0