前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java面试手册:反射

Java面试手册:反射

作者头像
南风
发布2019-04-22 16:20:45
4960
发布2019-04-22 16:20:45
举报
文章被收录于专栏:Java大联盟Java大联盟

Java大联盟

致力于最高效的Java学习

什么是反射?

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

如何理解反射?

简单来讲反射就是将传统的开发方式进行反转,传统的方式是通过类创建对象,反射则是通过对象获取类的内部结构,当然也可以通过其他方式来获取类的内部结构。

通过对象得到类,如何表示?

我们知道 Java 是面向对象的编程语言,世间万物都可以抽象成对象,反射也不例外,即通过反射机制获取的类的结构也可以抽象成一个对象,如何来创建这个对象呢?对象一定是通过类来创建的,这个类就是 Class 类,所以我们说 Class 类是反射的源头,Class 类的每一个实例化对象都表示某个类的结构。

如何获取 Class 实例化对象?

不能在外部通过构造函数创建 Class 的实例化对象,这是因为 Class 类的构造函数是私有的,如下图所示。

Java 提供了三种方式来实例化 Class:

1、调用 Class 的静态方法 forName(String className) 创建,将目标类的全限定类名(全限定类名就是包含所在包信息的类名全称,如java.lang.String)作为参数传入,即可获取对应的 Class 对象,forName(StringclassName) 方法的定义如下图所示。

2、通过目标类的 class 创建,Java 中的每一个类都可以调用类.class,这里的 class 不是属性,它叫作“类字面量”,其作用是获取内存中目标类型 class 对象的引用。

3、通过目标类实例化对象的 getClass() 方法创建。getClass() 方法定义在 Object 类中,被所有类继承,通过实例对象获取内存中该类 class 对象的引用,getClass() 方法的定义如下图所示。

上述三种实例化 Class 对象的具体操作如下所示。

代码语言:javascript
复制
public class Test {
   public static void main(String[] args) {
      try {
         Class clazz = Class.forName("java.lang.String");
      } catch (ClassNotFoundException e) {
      }
      Class clazz2 = String.class;
      String str = new String("Hello");
      Class clazz3 = str.getClass();
   }
}

获取类的结构

我们可以通过 Class 实例化对象获取类的结构,那么类的结构包括哪些内容呢?比如成员变量、成员方法、构造函数、父类、实现的接口等等,都是类的结构,反射也提供了相应的 API 来获取这些内容。

Class 用来描述目标类的结构,叫做目标类的运行时类,常用方法如下。

代码语言:javascript
复制
public Class<?>[] getInterfaces():返回运行时类实现的全部接口。
public Class<? Super T> getSuperclass():返回运行时类的父类。
public Constructor<T>[] getConstructors():返回运行时类的public构造方法。
public Constructor<T>[] getDeclaredConstructors():返回运行时类的全部构造方法。
public Method[] getMethods():返回运行时类的public方法。
public Method[] getDeclaredMethods(): 返回运行时类的全部方法。
public Field[] getFields() :返回运行时类的public成员变量。
public Field[] getDeclaredFields() :返回运行时类的全部成员变量。

Method 用来描述运行时类的方法,常用方法如下。

代码语言:javascript
复制
public Class<?> getReturnType() : 返回方法的返回值类型。
public Class<?>[] getParameterTypes() : 返回方法的参数列表
public int getModifiers() : 返回方法的访问权限修饰符。
public String getName(); : 返回方法名。
public Class<?>[] getExceptionTypes() : 返回方法的异常信息。

Field 用来描述运行时类的成员变量,常用方法如下。

代码语言:javascript
复制
public int getModifiers() : 返回成员变量的访问权限修饰符。
public Class<?> getType() : 返回成员变量的数据类型。
public String getName() : 返回成员变量的名称。

Constructor 用来描述运行时类的构造方法,常用方法如下。

代码语言:javascript
复制
public int getModifiers() : 返回构造方法的访问权限修饰符。
public String getName() : 返回构造方法名。
public Class<?>[] getParameterTypes() : 返回构造方法参数列表。

反射的应用

1、通过反射调用成员方法,反射就是将常规方式进行反转,首先创建实例化对象,然后该对象的方法也抽象成对象,我们称之为方法对象。调用方法对象的 invoke 方法来实现业务需求,并将实例化对象作为参数传入 invoke 方法中。整个过程操作的是方法对象,与常规方式恰好相反,如下所示。

代码语言:javascript
复制
public class Student{
   public static void main(String[] args) {
      Student student = new Student();
      student.setId(1);
      student.setName("张三");
      Class clazz = student.getClass();
      try {
         //获取showInfo()方法
         Method method = clazz.getDeclaredMethod("showInfo", null);
         //通过invoke方法完成调用
         method.invoke(student, null);
      } catch (NoSuchMethodException e) {
      } 
   }
}

2、通过反射机制,我们也可以在程序运行期间访问成员变量,并获取成员变量的相关信息,如名称、数据类型、访问权限等,具体操作如下所示。

代码语言:javascript
复制
public class Student{
   private int id;
   public String name;
}

public class Test {
   public static void main(String[] args) {
      Class clazz = Student.class;
      Field[] fields = clazz.getDeclaredFields();
      for (Field field : fields) {
         int modifiers = field.getModifiers();
         Class typeClass = field.getType();
         String name = field.getName();
         System.out.println("成员变量"+name+"的数据类型是:"+typeClass.getName()+",访问权限是:"+getModifiers(modifiers));
      }
   }
   public static String getModifiers(int modifiers) {
      String result = null;
      switch (modifiers) {
      case 0:
         result = "";
         break;
      case 1:
         result = "public";
         break;
      case 2:
         result = "private";
         break;
      case 4:
         result = "protected";
         break;
      }
      return result;
   }
}

3、通过反射机制修改成员变量的值,我们知道 private 成员变量在外部是无法访问的,所以直接通过反射来修改 private 成员变量会抛出异常,如何解决呢?通过反射的“暴力修改”即可,所谓的“暴力修改”是指修改 private 成员变量的访问权限,设置为 ture 时,表示可以修改,false 表示不能修改,会抛出异常,默认值为false 。可通过调用 setAccessible(booleanflag) 方法完成权限设置,具体操作如下所示。

代码语言:javascript
复制
public class Test {
   public static void main(String[] args) {
      Class clazz = Student.class;
      Field[] fields = clazz.getDeclaredFields();
      Student student = new Student();
      for (Field field : fields) {   
         try {
            if(field.getName().equals("id")) {
               field.setAccessible(true);
               field.set(student, 1);
            }
            if(field.getName().equals("name")) {
               field.set(student, "张三");
            }
         } catch (IllegalArgumentException e) {
         } 
      }
      System.out.println("学生信息");
      System.out.println("ID:"+student.getId());
      System.out.println("姓名:"+student.name);
   }
}

4、通过反射机制调用构造函数来创建对象,这种操作在一些工具类或者框架中应用非常广泛,通过配置文件来创建实例化对象就是通过这种方式来完成的,具体操作如下所示。

代码语言:javascript
复制
public class Student{
   private int id;
   public String name;

   public Student() {
   }
   public Student(int id,String name) {
      this.id = id;
      this.name = name;
   }
   @Override
   public String toString() {
      return "Student [id=" + id + ", name=" + name + "]";
   }
}

public class Test {
   public static void main(String[] args) {
      Class clazz = Student.class;
      try {
         //获取Student无参构造函数
         Constructor<Student> constructor = clazz.getConstructor(null);
         Student student = constructor.newInstance(null);
         System.out.println(student);
         //获取Student有参构造函数
         Constructor<Student> constructor2 = clazz.getConstructor(int.class,String.class);
         Student student2 = constructor2.newInstance(1,"张三");
         System.out.println(student2);
      } catch (NoSuchMethodException e) {
      } 
   }
}

动态代理

动态代理是反射非常重要的一个实际应用,是指在编译期并没有确定具体的代理类,在程序运行期间根据Java代码的指示动态地生成的方式,那么这个动态生成的功能是谁来完成的呢?

java.lang.reflect 包中提供了 InvocationHandler 接口,通过该接口可以在程序运行期间动态生成代理类。首先,自定义一个类MyInvocationHandler,实现 InvocationHandler 接口,这个类就是动态代理类的模版,如下所示。

代码语言:javascript
复制
public class MyInvocationHandler implements InvocationHandler {
   private Object obj = null;
   public Object bind(Object obj) {
      this.obj = obj;
      return Proxy.newProxyInstance(MyInvocationHandler.class.getClassLoader(), obj.getClass().getInterfaces(), this);
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // TODO Auto-generated method stub
      Object result = method.invoke(obj, args);
      return result;
   }
}

在 invoke 方法中可以添加非业务代码,从而做到业务代码和非业务代码的分离,动态代理的具体使用如下所示。

代码语言:javascript
复制
public interface Phone {
   public String salePhone();
}

public class Apple implements Phone{
   @Override
   public String salePhone() {
      // TODO Auto-generated method stub
      return "销售iPhone手机";
   }
}

public interface Car {
   public String saleCar();
}

public class BMW implements Car{
   @Override
   public String saleCar() {
      // TODO Auto-generated method stub
      return "销售宝马汽车";
   }
}

public class Test {
   public static void main(String[] args) {
      MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
      Phone phone = (Phone)myInvocationHandler.bind(new Apple());
      System.out.println(phone.salePhone());
      Car car = (Car)myInvocationHandler.bind(new BMW());
      System.out.println(car.saleCar());
   }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java大联盟 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档