专栏首页浩Coding浅谈Java的反射机制

浅谈Java的反射机制

一 什么是反射?

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。 -- 来自百度百科

二 反射有什么作用?

Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法生成动态代理(AOP)。

三 反射的具体例子讲解

下面是一个基本的类Game:

package reflection;
public class Game {
  //私有属性
    private String name = "Tencent";
    //公有属性
    public int age = 10;
    //构造方法
    public Game() {    
    }
    //私有方法
    private void say(){
        System.out.println("private say()...");
    }
    //公有方法
    public void work(){
        System.out.println("public work()...");
    }
}

1 得到 Class 的三种方式

1.通过类名获取 类名.class

2.通过对象获取 对象名.getClass()

3.通过全类名获取 Class.forName(全类名:com.xx.类名)

package reflection;
import java.lang.*;
public class TestReflection {
  public static void main(String[] args) throws ClassNotFoundException {
    // 1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
    // 类型的对象,而我不知道你具体是什么类,用这种方法
    Game p1 = new Game();
    Class c1 = p1.getClass();

    // 2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
    // 这说明任何一个类都有一个隐含的静态成员变量 class
    Class c2 = Game.class;

    // 3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
    // 但可能抛出 ClassNotFoundException 异常
    Class c3 = Class.forName("reflection.Game");

    // 测试c1,c2,c3是否是同一个类
    if (c1 == c2 && c2 == c3) {
      System.err.println("c1,c2,c3是同一个类");
    }
  }
}

需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true。

2 通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等

查阅 API 可以看到 Class 有很多方法:

  getName():获得类的完整名字。   getFields():获得类的public类型的属性。   getDeclaredFields():获得类的所有属性。包括private 声明的和继承类   getMethods():获得类的public类型的方法。   getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类   getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。   getConstructors():获得类的public类型的构造方法。   getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。   newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

我们通过一个例子来综合演示上面的方法:

//获得类完整的名字
    String className = c2.getName();
    System.out.println(className);//输出reflection.Game
            
    //获得类的public类型的属性。
    Field[] fields = c2.getFields();
    for(Field field : fields){
       System.out.println(field.getName());//age
    }
            
    //获得类的所有属性。包括私有的
    Field [] allFields = c2.getDeclaredFields();
    for(Field field : allFields){
        System.out.println(field.getName());//name    age
    }
            
    //获得类的public类型的方法。这里包括 Object 类的一些方法
    Method [] methods = c2.getMethods();
    for(Method method : methods){
        System.out.println(method.getName());//work waid equls toString hashCode等
    }
            
    //获得类的所有方法。
    Method [] allMethods = c2.getDeclaredMethods();
    for(Method method : allMethods){
        System.out.println(method.getName());//work say
    }
            
    //获得指定的属性
    Field f1 = c2.getField("age");
    System.out.println(f1);
    //获得指定的私有属性
    Field f2 = c2.getDeclaredField("name");
    //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
    f2.setAccessible(true);
    System.out.println(f2);//public int reflection.Game.name
                    
    //创建这个类的一个对象
    Object p2 =  c2.newInstance();
    //将 p2 对象的  f2 属性赋值为 Bob,f2 属性即为 私有属性 name
    f2.set(p2,"Bob");
    //使用反射机制可以打破封装性,导致了java对象的属性不安全。 
    System.out.println(f2.get(p2)); //Bob
            
    //获取构造方法
    Constructor [] constructors = c2.getConstructors();
    for(Constructor constructor : constructors){
        System.out.println(constructor.toString());//public reflection.Game()
    }

3 通过反射获取父类的属性

我们写一个小例子来看看如何获取父类属性:

父类 Parent.java:

package reflection;

public class Parent {
    public String publicField = "parent_publicField";
    protected String protectField = "parent_protectField";
    String defaultField = "parent_defaultField";
    private String privateField = "parent_privateField";
}

子类 Son.java:

public class Son extends Parent{

}

测试类TestReflection.java:

package reflection;

import java.lang.reflect.Field;

public class TestReflection {
  public static void main(String[] args) throws InstantiationException, IllegalAccessException, Exception {
    Class c1 = Class.forName("reflection.Son");
    // 获取父类私有属性值
    System.out.println(getFieldValue(c1.newInstance(), "privateField"));
    // 获取父类公共属性值
    System.out.println(getFieldValue(c1.newInstance(), "publicField"));
    // 获取父类protected
    System.out.println(getFieldValue(c1.newInstance(), "protectField"));
  }

  /**
   * 返回父类的字段
   * @param object object
   * @param fieldName 要获取的字段名称
   * @return
   * @throws Exception
   */
  public static Field getDeclaredField(Object obj, String fieldName) {
    Field field = null;
    Class c = obj.getClass();
    for (; c != Object.class; c = c.getSuperclass()) {
      try {
        field = c.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field;
      } catch (Exception e) {
        // 这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。
        // 如果这里的异常打印或者往外抛,则就不会执行c = c.getSuperclass(),最后就不会进入到父类中了
      }
    }
    return null;
  }

  /**
   * 返回具体的字段值
   * @param object object
   * @param fieldName 要获取的字段名称
   * @return
   * @throws Exception
   */
  public static Object getFieldValue(Object object, String fieldName) throws Exception {
    Field field = getDeclaredField(object, fieldName);

    return field.get(object);
  }
}

通过执行上述代码,我们获得了父类的私有属性值,这里要注意的是直接通过反射获取子类的对象是不能得到父类的属性值的,必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象,然后再通过父类对象去获取父类的属性值

最后运行截图:

四 反射知识总结

反射知识点:

1. Class: 是一个类; 一个描述类的类.

  封装了描述方法的 Method,描述字段的 Filed, 描述构造器的 Constructor 等属性.
 
 2. 如何得到 Class 对象:
    2.1 Person.class
    2.2 person.getClass()
    2.3 Class.forName("com.atguigu.javase.Person")
  
 3. 关于 Method:
    3.1 如何获取 Method:
      1). getDeclaredMethods: 得到 Method 的数组.
      2). getDeclaredMethod(String methondName, Class ... parameterTypes)
  
    3.2 如何调用 Method
      1). 如果方法时 private 修饰的, 需要先调用 Method 的 setAccessible(true), 使其变为可访问
      2). method.invoke(obj, Object ... args);
 
  4. 关于 Field:
    4.1 如何获取 Field: getField(String fieldName)
    4.2 如何获取 Field 的值: 
      1). setAccessible(true)
      2). field.get(Object obj)
    4.3 如何设置 Field 的值:
      field.set(Obejct obj, Object val)

关于反射的应用:

灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动Hibernate 的实体类Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,要合理使用反射。

参考文章:

https://www.cnblogs.com/ysocean/p/6516248.html

https://www.cnblogs.com/tech-bird/p/3525336.html

https://baike.baidu.com/item/JAVA%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6/6015990?fr=aladdin

本文分享自微信公众号 - 浩Coding(gh_c4a2e63d2ca7),作者:浩

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • HTML中点击<a>超链接标签使其不跳转

    最近使用前端框架,需要使用一个<a>超链接标签的样式,但又不要让他跳转,所以搜集了网上的资料查到几个方法:

    浩Coding
  • Java EE实用教程笔记----(1)第1章 Java EE平台及开发入门

    最近从图书馆借了本JavaEE实用教程(第3版),打算学习SSH和SSM框架,特此做笔记,以飨读者.

    浩Coding
  • 修改github上仓库的项目的语言类型

    在把项目上传到github仓库上时语言会显示错误语言,github上的项目语言是自动识别的,也就是你某种语言的占比最多,就会自动识别为那种语言,这个方式显然不合...

    浩Coding
  • 《Java从入门到放弃》JavaSE入门篇:练习——单身狗租赁系统

    我们把之前使用数组做的这个单身狗系统改版成数据库版本,并且使用面向对象里面的一些简单思想。

    十方上下
  • Java基础---学生信息管理系统(借助Array List)

    1、通过键盘选择操作进行添加学生(学号,姓名,性别,年龄,成绩)的信息,这些信息通过数组存储。

    李志伟
  • 《Java从入门到放弃》JavaSE入门篇:练习——单身狗租赁系统

    十方上下
  • ASpectJ对AOP的实现

    切入点表达式 ? ? 基于注解 1 @Aspect // 表示当前类切面 2 public class MyAspect { 3 4 @Bef...

    用户1216491
  • 小朋友学Java(6):封装

    面向对象有三大特征:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。 本节讲封装。 程序1 class W...

    海天一树
  • 多线程编程学习八(原子操作类).

    Java 在 JDK 1.5 中提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地...

    JMCui
  • java之学习math类的方法概述

    吾爱乐享

扫码关注云+社区

领取腾讯云代金券