> 文档中心 > Java反射解析

Java反射解析

Java反射解析

  • 1 概述
    • 1.1 概念
    • 1.2 反射的作用
    • 1.3 反射的优缺点
  • 2 反射机制(Class 和 java.lang.reflect.*)
    • 2.1 概述
    • 2.2 Class
  • 3 使用反射
    • 3.1 获得Class的三种方式
    • 3.2 使用Class类的对象来生成目标类的实例
    • 3.3 统一形式调用
      • 3.3.1 统一形式解析类
      • 3.3.2 统一形式调用类的构造方法
      • 3.3.3 统一形式调用成员方法
      • 3.3.4 通用方法
  • 4 反射与工厂模式
    • 4.1 传统工厂设计模式
    • 4.2 添加反射
    • 4.3 添加泛型
  • 参考

1 概述

1.1 概念

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

1.2 反射的作用

  • 在运行时判断任意一个对象所属的类;
  • 在运行时获取类的对象;
  • 在运行时访问java对象的属性,方法,构造方法等

1.3 反射的优缺点

首先要搞清楚为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念。
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

反射机制的优点:可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容,进行了反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。

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

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

2 反射机制(Class 和 java.lang.reflect.*)

2.1 概述

Java中的反射机制,是由一系列组件构成的。反射机制为Java的其他特性(例如对象序列化和JavaBean)提供了支持,在高层抽象架构中具有重要的地位。下图展示了构成Java反射机制涉及的组件:
在这里插入图片描述

  • Class:Java是一门面向对象语言,万物皆对象。因此对于每一个字节码文件(.class)在内存中都有一个相对应的Class对象。需要反射得到对象,就必然需要对象所属类的信息,而Class包含了类的描述信息。
  • java.lang.reflect.*:上图中的Member以及实现类都来自于java.lang.reflect包,这些类抽象了一个类中的所有组件。例如,Field对应成员变量,Method对应方法,Constructor对应构造。使得Class有能力对类进行描述。
  • Object:所有的类都继承自Object。Object中包含了public final native Class getClass(),因此所有对象可以通过该方法获得所属类的Class对象。
  • ClassLoader:类加载器。Class中包含了对应的ClassLoader。

2.2 Class

在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。

Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:

  • 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。

  • 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。

  • 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。

所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。

在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。

3 使用反射

3.1 获得Class的三种方式

  1. 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
    实例:Class clazz = String.class;
  2. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
    实例:Class clazz = “student”.getClass();
  3. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
    实例:Class clazz = Class.forName(“java.lang.String”);
  4. 其他方式
    ClassLoader cl = this.getClass().getClassLoader();
    Class clazz = cl.loadClass(“类的全类名”);

3.2 使用Class类的对象来生成目标类的实例

生成不精确的object实例
获取一个Class类的对象后,可以用 newInstance() 函数来生成目标类的一个实例。然而,该函数并不能直接生成目标类的实例,只能生成object类的实例

Class obj=Class.forName("shapes");Object ShapesInstance=obj.newInstance();

使用泛化Class引用生成带类型的目标实例

Class<shapes> obj=shapes.class;shapes newShape=obj.newInstance();

因为有了类型限制,所以使用泛化Class语法的对象引用不能指向别的类。

Class obj1=int.class;Class<Integer> obj2=int.class;obj1=double.class;//obj2=double.class; 这一行代码是非法的,obj2不能改指向别的类了

然而,有个灵活的用法,使得你可以用Class的对象指向基类的任何子类

Class<? extends Number> obj=int.class;obj=Number.class;obj=double.class;

因此,以下语法生成的Class对象可以指向任何类

Class<?> obj=int.class;obj=double.class;obj=shapes.class;

最后一个奇怪的用法是,当你使用这种泛型语法来构建你手头有的一个Class类的对象的基类对象时,必须采用以下的特殊语法

public class shapes{}class round extends shapes{}Class<round> rclass=round.class;Class<? super round> sclass= rclass.getSuperClass();//Class sclass=rclass.getSuperClass();

我们明知道,round的基类就是shapes,但是却不能直接声明 Class ,必须使用特殊语法
Class

3.3 统一形式调用

3.3.1 统一形式解析类

统一形式解析类的构造方法、成员变量、成员方法

public class A {    public int m;    public A() {    }    public A(int m) { this.m = m;    }    public void func1() {    }    public void func2() {    }    public static void main(String[] args) throws ClassNotFoundException { Class classInfo = Class.forName("A"); System.out.println("类A的构造函数如下:"); Constructor[] cons = classInfo.getConstructors(); for (int i = 0; i < cons.length; i++) {     System.out.println(cons[i].toString()); } System.out.println(); System.out.println("类A的变量如下:"); Field[] fields = classInfo.getDeclaredFields(); for (int i = 0; i < fields.length; i++) {     System.out.println(fields[i].toString()); } System.out.println(); System.out.println("类A的方法如下:"); Method[] methods = classInfo.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) {     System.out.println(methods[i].toString()); }    }}

3.3.2 统一形式调用类的构造方法

public class A {    public A() { System.out.println("This is A:");    }    public A(Integer m) { System.out.println("This is " + m);    }    public A(String s, Integer m) { System.out.println(s + " : " + m);    }    public static void main(String[] args) throws Exception { Class classInfo = Class.forName("A"); // 第一种方法 Constructor[] cons = classInfo.getConstructors(); cons[0].newInstance(); cons[1].newInstance(new Object[]{10}); cons[2].newInstance(new Object[]{"Hello", 2020});  System.out.println("\n\n\n"); // 第二种方法 Constructor c = classInfo.getConstructor(); c.newInstance(); c = classInfo.getConstructor(Integer.class); c.newInstance(10); c = classInfo.getConstructor(String.class, Integer.class); c.newInstance("Hello", 2020);    }}

上述代码中,方法2优于方法1

3.3.3 统一形式调用成员方法

public class A {    public void func1() { System.out.println("This is func1:");    }    public void func2(Integer m) { System.out.println("This is func2:" + m);    }    public void func3(String s, Integer m) { System.out.println("This is func3:" + s + " , " + m);    }    public static void main(String[] args) throws Exception { Class classInfo = Class.forName("A"); Object object = classInfo.getConstructor().newInstance(); Method mt1 = classInfo.getMethod("func1"); mt1.invoke(object); Method mt2 = classInfo.getMethod("func2",Integer.class); mt2.invoke(object,10); Method mt3 = classInfo.getMethod("func3",String.class,Integer.class); mt3.invoke(object,"Hello",2020);    }}

3.3.4 通用方法

通过类名字符串、方法名字符串、方法参数值,运用反射机制执行该方法

public boolean process(String className, String funcName, Object[] para) throws Exception {    Class classInfo = Class.forName(className); Class[] c = new Class[para.length];    for (int i = 0; i < para.length; i++) { c[i] = para[i].getClass();    } Constructor ct = classInfo.getConstructor();    Object object = ct.newInstance(); Method mt = classInfo.getMethod(funcName,c);    mt.invoke(object,para); return true;}

4 反射与工厂模式

4.1 传统工厂设计模式

public class Test {    public static void main(String[] args) { IMessage iMessage = Factory.getInstance("NetMessage"); iMessage.send();    }}public interface  IMessage{     void  send();}public class NetMessage implements  IMessage{    @Override    public void send() { System.out.println("网络消息发送");    }}public class  Factory{    private Factory(){ //无参构造器,私有化     }    public  static  IMessage getInstance(String classname){ //实例化方法 if("NetMessage".equals(classname)){     return new NetMessage(); }else{     return null; }    }}

以上静态工厂模式存在弊端,每当增加一个子类,工厂类中就必须修改实例方法。所以要解决此问题,最好不使用关键字new.利用反射机制来实现。

4.2 添加反射

解决添加子类后必须修改工厂类的弊端

public class Test {    public static void main(String[] args) { IMessage iMessage = Factory.getInstance("NetMessage"); iMessage.send();    }}public interface  IMessage{     void  send();}public class NetMessage implements  IMessage{    @Override    public void send() { System.out.println("网络消息发送");    }}public class  Factory{    private   Factory(){    }    public  static  IMessage getInstance(String classname){ IMessage iMessage = null; try {     iMessage =  (IMessage)Class.forName(classname).getDeclaredConstructor().newInstance(); } catch (Exception e) {     e.printStackTrace(); } return iMessage;    }}

但是以上程序任然没有达到最好的效果,如果用户想增加接口,则必须增加工厂类中的实例化方法,任然要求修改factory类,所以,只能用泛型的方式来解决此问题。

4.3 添加泛型

public class Test {    public static void main(String[] args) { IMessage iMessage = Factory.getInstance("NetMessage",NetMessage.class); iMessage.send(); IService iService = Factory.getInstance("HouseService",HouseService.class); iService.service();    }}public interface  IMessage{     void  send();}public class NetMessage implements  IMessage{    @Override    public void send() { System.out.println("网络消息发送");    }}public interface IService{     void service();}public class HouseService implements IService{    @Override    public void service() { System.out.println("提供住房服务");    }}public class  Factory{    private   Factory(){    }    public  static <T> T getInstance(String classname,Class<T> clazz){ T intance = null; try {     intance =  (T)Class.forName(classname).getDeclaredConstructor().newInstance(); } catch (Exception e) {     e.printStackTrace(); } return intance;    }}

此时的工厂设计模式将不会受限于指定的接口。

参考

Java反射机制及IoC原理
Java中Class类详解、用法及泛化
java高级-反射的三种实例化模式及与工厂,单例模式的的关系