精华内容
下载资源
问答
  • java反射机制
    千次阅读
    2021-03-14 00:58:36

    运行时类型识别(Run-time Type Identification, RTTI)主要有两种方式,一种是我们在编译时和运行时已经知道了所有的类型,另外一种是功能强大的“反射”机制。

    要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由“Class对象”完成的,它包含了与类有关的信息。类是程序的重要组成部分,每个类都有一个Class对象,每当编写并编译了一个新类就会产生一个Class对象,它被保存在一个同名的.class文件中。在运行时,当我们想生成这个类的对象时,运行这个程序的Java虚拟机(JVM)会确认这个类的Class对象是否已经加载,如果尚未加载,JVM就会根据类名查找.class文件,并将其载入,一旦这个类的Class对象被载入内存,它就被用来创建这个类的所有对象。一般的RTTI形式包括三种:

    1.       传统的类型转换。如“(Apple)Fruit”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。

    2.       通过Class对象来获取对象的类型。如

    Class c = Class.forName(“Apple”);

    Object o = c.newInstance();

    3.       通过关键字instanceof或Class.isInstance()方法来确定对象是否属于某个特定类型的实例,准确的说,应该是instanceof / Class.isInstance()可以用来确定对象是否属于某个特定类及其所有基类的实例,这和equals() / ==不一样,它们用来比较两个对象是否属于同一个类的实例,没有考虑继承关系。

    反射

    如果不知道某个对象的类型,可以通过RTTI来获取,但前提是这个类型在编译时必须已知,这样才能使用RTTI来识别。即在编译时,编译器必须知道所有通过RTTI来处理的类。

    使用反射机制可以不受这个限制,它主要应用于两种情况,第一个是“基于构件的编程”,在这种编程方式中,将使用某种基于快速应用开发(RAD)的应用构建工具来构建项目。这是现在最常见的可视化编程方法,通过代表不同组件的图标拖动到图板上来创建程序,然后设置构件的属性值来配置它们。这种配置要求构件都是可实例化的,并且要暴露其部分信息,使得程序员可以读取和设置构件的值。当处理GUI时间的构件时还必须暴露相关方法的细细,以便RAD环境帮助程序员覆盖这些处理事件的方法。在这里,就要用到反射的机制来检查可用的方法并返回方法名。Java通过JavaBeans提供了基于构件的编程架构。

    第二种情况,在运行时获取类的信息的另外一个动机,就是希望能够提供在跨网络的远程平台上创建和运行对象的能力。这被成为远程调用(RMI),它允许一个Java程序将对象分步在多台机器上,这种分步能力将帮助开发人员执行一些需要进行大量计算的任务,充分利用计算机资源,提高运行速度。

    Class支持反射,java.lang.reflect中包含了Field/Method/Constructor类,每个类都实现了Member接口。这些类型的对象都是由JVM在运行时创建的,用来表示未知类里对应的成员。如可以用Constructor类创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。同时,还可以调用getFields()、getMethods()、getConstructors()等方法来返回表示字段、方法以及构造器的对象数组。这样,未知的对象的类信息在运行时就能被完全确定下来,而在编译时不需要知道任何信息。

    另外,RTTI有时能解决效率问题。当程序中使用多态给程序的运行带来负担的时候,可以使用RTTI编写一段代码来提高效率。

    Person p=new Person();

    这是什么?当然是实例化一个对象了.可是这种实例化对象的方法存在一个问题,那就是必须要知道类名才可以实例化它的对象,这样我们在应用方面就会受到限制.那么有没有这样一种方式,让我们不知道这个类的类名就可以实例化它的对象呢?Thank Goodness!幸亏我们用的是java, java就提供了这样的机制.

    1).java程序在运行时可以获得任何一个类的字节码信息,包括类的修饰符(public,static等),基类(超类,父类),实现的接口,字段和方法等信息.

    2).java程序在运行时可以根据字节码信息来创建该类的实例对象,改变对象的字段内容和调用对象方法.

    这样的机制就叫反射技术.可以想象光学中的反射,就像我们照镜子,镜子中又出现一个自己(比喻可能不太恰当,但是足以表达清楚意思了).反射技术提供了一种通用的动态连接程序组件的方法,不必要把程序所需要的目标类硬编码到源程序中,从而使得我们可以创建灵活的程序.

    Java的反射机制是通过反射API来实现的,它允许程序在运行过程中取得任何一个已知名称的类的内部信息.反射API位于java.lang.reflect包中.主要包括以下几类:

    1).Constructor类:用来描述一个类的构造方法

    2).Field类:用来描述一个类的成员变量

    3).Method类:用来描述一个类的方法.

    4).Modifer类:用来描述类内各元素的修饰符

    5).Array:用来对数组进行操作.

    Constructor,Field,Method这三个类都是JVM(虚拟机)在程序运行时创建的,用来表示加载类中相应的成员.这三个类都实现了java.lang.reflect.Member接口,Member接口定义了获取类成员或构造方法等信息的方法.要使用这些反射API,必须先得到要操作的对象或类的Class类的实例.通过调用Class类的newInstance方法(只能调用类的默认构造方法)可以创建类的实例.这样有局限性,我们可以先冲类的Class实例获取类需要的构造方法,然后在利用反射来创建类的一个实例.

    一.获取类的构造方法的Constructor对象(数组)

    ● Constructor[] getDeclaredConstructors();返回已加载类声明的所有的构造方法的Constructor对象数组.

    ● Constructor  getDeclaredConstructor(Class[] paramTypes);返回已加载类声明的指定构造方法的Constructor对象,paramTypes指定了参数类型.

    ● Constructor[] getConstructors();返回已加载类声明的所有的public类型的构造方法的Constructor对象数组.

    ● Constructor  getConstructor(Class[] paramTypes);返回已加载类声明的指定的public类型的构造方法的Constructor对象,paramTypes指定了参数类型.

    如果某个类中没有定义构造方法,第一个和第三个方法返回的数组中只有一个元素,就是缺省的构造方法;如果某个类中只定义了有参数的构造函数,而没有定义缺省构造函数,第一个和第三个方法返回的数组中不包含缺省的构造方法.

    例子:

    import java.lang.reflect.*;

    public class DumpMethods {

    public static void main(String[] args) {

    try{

    if(args.length<1){

    System.out.println("请输入完整的类名:");

    return;

    }

    Class strClass=Class.forName(args[0]);

    //检索带有指定参数的构造方法

    Class[] strArgsClass=new Class[]{ byte[].class,String.class};

    Constructor constructor=strClass.getConstructor(strArgsClass);

    System.out.println("Constructor:"+constructor.toString());

    //调用带有参数的构造方法创建实例对象object

    byte[] bytes="java就业培训".getBytes();

    Object[] strArgs=new Object[]{bytes,"gb2312"};

    Object object=constructor.newInstance(strArgs);

    System.out.println("Object"+object.toString());

    }catch(Exception e){

    e.printStackTrace();

    }

    }

    }

    运行结果:

    二.获取类成员变量的Field对象(数组)

    ●Field[] getDeclaredFields():返回已加载类声明的所有成员变量的Field对象数组,不包括从父类继承的成员变量.

    ●Field  getDeclaredField(String name):返回已加载类声明的所有成员变量的Field对象,不包括从父类继承的成员变量,参数name指定成员变量的名称.

    ●Field[] getFields():返回已加载类声明的所有public型的成员变量的Field对象数组,包括从父类继承的成员变量

    ●Field  getField(String name):返回已加载类声明的所有成员变量的Field对象,包括从父类继承的成员变量,参数name指定成员变量的名称.

    例子:

    import java.lang.reflect.*;

    public class ReflectTest {

    private String name;

    private String age;

    public ReflectTest(String name,String age){

    this.name=name;

    this.age=age;

    }

    public static void main(String[] args) {

    // TODO 自动生成方法存根

    try{

    ReflectTest rt=new ReflectTest("zhanghandong","shiba");

    fun(rt);

    }catch(Exception e){

    e.printStackTrace();

    }

    }

    public static void fun(Object obj) throws Exception{

    Field[] fields=obj.getClass().getDeclaredFields();

    System.out.println("替换之前的:");

    for(Field field:fields){

    System.out.println(field.getName()+"="+field.get(obj));

    if(field.getType().equals(java.lang.String.class)){

    field.setAccessible(true);  //必须设置为true才可以修改成员变量

    String org=(String)field.get(obj);

    field.set(obj,org.replaceAll("a","b"));

    }

    }

    System.out.println("替换之后的:");

    for(Field field:fields){

    System.out.println(field.getName()+"="+field.get(obj));

    }

    }

    }

    运行结果如下:

    三.获取类的方法的Method对象(数组)

    ●Method[] getDeclaredMethods():返回已加载类声明的所有方法的Method对象数组,不包括从父类继承的方法.

    ●Method  getDeclaredMethod(String name,Class[] paramTypes):返回已加载类声明的所有方法的Method对象,不包括从父类继承的方法,参数name指定方法的名称,参数paramTypes指定方法的参数类型.

    ●Method[] getMethods():返回已加载类声明的所有方法的Method对象数组,包括从父类继承的方法.

    ●Method  getMethod(String name,Class[] paramTypes):返回已加载类声明的所有方法的Method对象,包括从父类继承的方法,参数name指定方法的名称,参数paramTypes指定方法的参数类型.

    四.检索类的其他信息

    ●int  getModifiers():返回已加载类的修饰符的整形标识值.

    ●Package getPackage():返回已加载类的包名

    ●Class  getSuperclass():返回已加载类的父类的Class实例.

    ●Class  []  getInterfaces():返回已加载类实现的接口的Class对象数组.

    ●boolean  isInterface():返回已加载类是否是接口.

    反射的功能很强大,但是使用不当可能会缺点大于优点,反射使代码逻辑混乱,会带来维护的问题.

    ************************************

    摘要

    Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。本文借由实例,大面积示范Reflection APIs。

    关于本文:

    读者基础:具备Java 语言基础。

    本文适用工具:JDK1.5

    关键词:

    Introspection(内省、内观)

    Reflection(反射)

    有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们朗朗上口动态绑定(dynamic binding)、动态链接(dynamic linking)、动态加载(dynamic loading)等。然而“动态”一词其实没有绝对而普遍适用的严格定义,有时候甚至像对象导向当初被导入编程领域一样,一人一把号,各吹各的调。

    一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

    尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods1。这种“看透class”的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

    Java如何能够做出上述的动态特性呢?这是一个深远话题,本文对此只简单介绍一些概念。整个篇幅最主要还是介绍Reflection APIs,也就是让读者知道如何探索class的结构、如何对某个“运行时才获知名称的class”生成一份实体、为其fields设值、调用其methods。本文将谈到java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等classes。

    “Class”class

    众所周知Java有个Object class,是所有Java classes的继承根源,其内声明了数个应该在所有Java class中被改写的methods:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class object。

    Class class十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class object。如果您想借由“修改Java标准库源码”来观察Class object的实际生成时机(例如在Class的constructor内添加一个println()),不能够!因为Class并没有public constructor(见图1)。本文最后我会拨一小块篇幅顺带谈谈Java标准库源码的改动办法。

    Class是Reflection故事起源。针对任何您想探勘的class,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。这些APIs将在稍后的探险活动中一一亮相。

    #001 public final

    #002 class Classimplements java.io.Serializable,

    #003 java.lang.reflect.GenericDeclaration,

    #004 java.lang.reflect.Type,

    #005 java.lang.reflect.AnnotatedElement {

    #006    privateClass() {}

    #007    public String toString() {

    #008       return ( isInterface() ? "interface " :

    #009       (isPrimitive() ? "" : "class "))

    #010   + getName();

    #011 }

    ...

    图1:Classclass片段。注意它的private empty ctor,意指不允许任何人经由编程方式产生Classobject。是的,其object只能由JVM产生。

    “Class”object的取得途径

    Java允许我们从多种管道为一个class生成对应的Class object。图2是一份整理。

    Class object 诞生管道

    示例

    运用getClass()

    注:每个class 都有此函数

    String str = "abc";

    Class c1 = str.getClass();

    运用

    Class.getSuperclass()2

    Button b = new Button();

    Class c1 = b.getClass();

    Class c2 = c1.getSuperclass();

    运用static method

    Class.forName()

    (最常被使用)

    Class c1 = Class.forName ("java.lang.String");

    Class c2 = Class.forName ("java.awt.Button");

    Class c3 = Class.forName ("java.util.LinkedList$Entry");

    Class c4 = Class.forName ("I");

    Class c5 = Class.forName ("[I");

    运用

    .class 语法

    Class c1 = String.class;

    Class c2 = java.awt.Button.class;

    Class c3 = Main.InnerClass.class;

    Class c4 = int.class;

    Class c5 = int[].class;

    运用

    primitive wrapper classes

    的TYPE 语法

    Class c1 = Boolean.TYPE;

    Class c2 = Byte.TYPE;

    Class c3 = Character.TYPE;

    Class c4 = Short.TYPE;

    Class c5 = Integer.TYPE;

    Class c6 = Long.TYPE;

    Class c7 = Float.TYPE;

    Class c8 = Double.TYPE;

    Class c9 = Void.TYPE;

    图2:Java允许多种管道生成Class object。

    Java classes组成分析

    首先容我以图3的java.util.LinkedList为例,将Java class的定义大卸八块,每一块分别对应图4所示的Reflection API。图5则是“获得class各区块信息”的程序示例及执行结果,它们都取自本文示例程序的对应片段。

    package java.util;                     //(1)

    import java.lang.*;                //(2)

    public class LinkedList              //(3)(4)(5)

    extends AbstractSequentialList       //(6)

    implements List, Queue,

    Cloneable, java.io.Serializable        //(7)

    {

    private static class Entry { … }//(8)

    public LinkedList() { … }           //(9)

    public LinkedList(Collection extends E> c) { … }

    public E getFirst() { … }           //(10)

    public E getLast() { … }

    private transient Entry header = …;  //(11)

    private transient int size = 0;

    }

    图3:将一个Java class大卸八块,每块相应于一个或一组Reflection APIs(图4)。

    Java classes各成份所对应的Reflection APIs

    图3的各个Java class成份,分别对应于图4的Reflection API,其中出现的Package、Method、Constructor、Field等等classes,都定义于java.lang.reflect。

    Java class 内部模块(参见图3)

    Java class 内部模块说明

    相应之Reflection API,多半为Class methods。

    返回值类型(return type)

    (1) package

    class隶属哪个package

    getPackage()

    Package

    (2) import

    class导入哪些classes

    无直接对应之API。

    解决办法见图5-2。

    (3) modifier

    class(或methods, fields)的属性

    int getModifiers()

    Modifier.toString(int)

    Modifier.isInterface(int)

    int

    String

    bool

    (4) class name or interface name

    class/interface

    名称getName()

    String

    (5) type parameters

    参数化类型的名称

    getTypeParameters()

    TypeVariable []

    (6) base class

    base class(只可能一个)

    getSuperClass()

    Class

    (7) implemented interfaces

    实现有哪些interfaces

    getInterfaces()

    Class[]

    (8) inner classes

    内部classes

    getDeclaredClasses()

    Class[]

    (8') outer class

    如果我们观察的class 本身是inner classes,那么相对它就会有个outer class。

    getDeclaringClass()

    Class

    (9) constructors

    构造函数getDeclaredConstructors()

    不论 public 或private 或其它access level,皆可获得。另有功能近似之取得函数。

    Constructor[]

    (10) methods

    操作函数getDeclaredMethods()

    不论 public 或private 或其它access level,皆可获得。另有功能近似之取得函数。

    Method[]

    (11) fields

    字段(成员变量)

    getDeclaredFields()不论 public 或private 或其它access level,皆可获得。另有功能近似之取得函数。

    Field[]

    图4:Java class大卸八块后(如图3),每一块所对应的Reflection API。本表并非

    Reflection APIs的全部。

    Java Reflection API运用示例

    图5示范图4提过的每一个Reflection API,及其执行结果。程序中出现的tName()是个辅助函数,可将其第一自变量所代表的“Java class完整路径字符串”剥除路径部分,留下class名称,储存到第二自变量所代表的一个hashtable去并返回(如果第二自变量为null,就不储存而只是返回)。

    #001 Class c = null;

    #002 c = Class.forName(args[0]);

    #003

    #004 Package p;

    #005 p = c.getPackage();

    #006

    #007 if (p != null)

    #008    System.out.println("package "+p.getName()+";");

    执行结果(例):

    package java.util;

    图5-1:找出class隶属的package。其中的c将继续沿用于以下各程序片段。

    #001 ff = c.getDeclaredFields();

    #002 for (int i = 0; i < ff.length; i++)

    #003   x = tName(ff[i].getType().getName(), classRef);

    #004

    #005 cn = c.getDeclaredConstructors();

    #006 for (int i = 0; i < cn.length; i++) {

    #007   Class cx[] = cn[i].getParameterTypes();

    #008   for (int j = 0; j < cx.length; j++)

    #009       x = tName(cx[j].getName(), classRef);

    #010 }

    #011

    #012 mm = c.getDeclaredMethods();

    #013 for (int i = 0; i < mm.length; i++) {

    #014   x = tName(mm[i].getReturnType().getName(), classRef);

    #015   Class cx[] = mm[i].getParameterTypes();

    #016   for (int j = 0; j < cx.length; j++)

    #017       x = tName(cx[j].getName(), classRef);

    #018 }

    #019 classRef.remove(c.getName()); //不必记录自己(不需import 自己)

    执行结果(例):

    import java.util.ListIterator;

    import java.lang.Object;

    import java.util.LinkedList$Entry;

    import java.util.Collection;

    import java.io.ObjectOutputStream;

    import java.io.ObjectInputStream;

    图5-2:找出导入的classes,动作细节详见内文说明。

    #001 int mod = c.getModifiers();

    #002 System.out.print(Modifier.toString(mod)); //整个modifier

    #003

    #004 if (Modifier.isInterface(mod))

    #005    System.out.print(" "); //关键词 "interface" 已含于modifier

    #006 else

    #007    System.out.print(" class "); //关键词 "class"

    #008 System.out.print(tName(c.getName(), null)); //class 名称

    执行结果(例):

    public class LinkedList

    图5-3:找出class或interface的名称,及其属性(modifiers)。

    #001 TypeVariable[] tv;

    #002 tv = c.getTypeParameters(); //warning: unchecked conversion

    #003 for (int i = 0; i < tv.length; i++) {

    #004    x = tName(tv[i].getName(), null); //例如 E,K,V...

    #005    if (i == 0) //第一个

    #006       System.out.print("

    #007    else //非第一个

    #008       System.out.print("," + x);

    #009    if (i == tv.length-1) //最后一个

    #010       System.out.println(">");

    #011 }

    执行结果(例):

    public abstract interface Map

    或 public class LinkedList

    图5-4:找出parameterized types的名称

    #001 Class supClass;

    #002 supClass = c.getSuperclass();

    #003 if (supClass != null) //如果有super class

    #004   System.out.print(" extends" +

    #005 tName(supClass.getName(),classRef));

    执行结果(例):

    public class LinkedList

    extends AbstractSequentialList,

    图5-5:找出base class。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。

    #001 Class cc[];

    #002 Class ctmp;

    #003 //找出所有被实现的interfaces

    #004 cc = c.getInterfaces();

    #005 if (cc.length != 0)

    #006    System.out.print(", \r\n" + " implements "); //关键词

    #007 for (Class cite : cc) //JDK1.5 新式循环写法

    #008    System.out.print(tName(cite.getName(), null)+", ");

    执行结果(例):

    public class LinkedList

    extends AbstractSequentialList,

    implements List, Queue, Cloneable, Serializable,

    图5-6:找出implemented interfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。

    #001 cc = c.getDeclaredClasses(); //找出inner classes

    #002 for (Class cite : cc)

    #003    System.out.println(tName(cite.getName(), null));

    #004

    #005 ctmp = c.getDeclaringClass(); //找出outer classes

    #006 if (ctmp != null)

    #007    System.out.println(ctmp.getName());

    执行结果(例):

    LinkedList$Entry

    LinkedList$ListItr

    图5-7:找出inner classes和outer class

    #001 Constructor cn[];

    #002 cn = c.getDeclaredConstructors();

    #003 for (int i = 0; i < cn.length; i++) {

    #004    int md = cn[i].getModifiers();

    #005   System.out.print(" " + Modifier.toString(md) + " " +

    #006   cn[i].getName());

    #007    Class cx[] = cn[i].getParameterTypes();

    #008   System.out.print("(");

    #009   for (int j = 0; j < cx.length; j++) {

    #010       System.out.print(tName(cx[j].getName(), null));

    #011       if (j < (cx.length - 1)) System.out.print(", ");

    #012   }

    #013   System.out.print(")");

    #014 }

    执行结果(例):

    public java.util.LinkedList(Collection)

    public java.util.LinkedList()

    图5-8a:找出所有constructors

    #004 System.out.println(cn[i].toGenericString());

    执行结果(例):

    public java.util.LinkedList(java.util.Collection extends E>)

    public java.util.LinkedList()

    图5-8b:找出所有constructors。本例在for循环内使用toGenericString(),省事。

    #001 Method mm[];

    #002 mm = c.getDeclaredMethods();

    #003 for (int i = 0; i < mm.length; i++) {

    #004    int md = mm[i].getModifiers();

    #005   System.out.print(" "+Modifier.toString(md)+" "+

    #006    tName(mm[i].getReturnType().getName(), null)+" "+

    #007   mm[i].getName());

    #008    Class cx[] = mm[i].getParameterTypes();

    #009   System.out.print("(");

    #010   for (int j = 0; j < cx.length; j++) {

    #011       System.out.print(tName(cx[j].getName(), null));

    #012   if (j < (cx.length - 1)) System.out.print(", ");

    #013   }

    #014   System.out.print(")");

    #015 }

    执行结果(例):

    public Object get(int)

    public int size()

    图5-9a:找出所有methods

    #004 System.out.println(mm[i].toGenericString());

    public E java.util.LinkedList.get(int)

    public int java.util.LinkedList.size()

    图5-9b:找出所有methods。本例在for循环内使用toGenericString(),省事。

    #001 Field ff[];

    #002 ff = c.getDeclaredFields();

    #003 for (int i = 0; i < ff.length; i++) {

    #004    int md = ff[i].getModifiers();

    #005   System.out.println(" "+Modifier.toString(md)+" "+

    #006   tName(ff[i].getType().getName(), null) +" "+

    #007   ff[i].getName()+";");

    #008 }

    执行结果(例):

    private transient LinkedList$Entry header;

    private transient int size;

    图5-10a:找出所有fields

    #004 System.out.println("G: " + ff[i].toGenericString());

    private transient java.util.LinkedList.java.util.LinkedList$Entry ??

    java.util.LinkedList.header

    private transient int java.util.LinkedList.size

    图5-10b:找出所有fields。本例在for循环内使用toGenericString(),省事。

    找出class参用(导入)的所有classes

    没有直接可用的Reflection API可以为我们找出某个class参用的所有其它classes。要获得这项信息,必须做苦工,一步一脚印逐一记录。我们必须观察所有fields的类型、所有methods(包括constructors)的参数类型和回返类型,剔除重复,留下唯一。这正是为什么图5-2程序代码要为tName()指定一个hashtable(而非一个null)做为第二自变量的缘故:hashtable可为我们储存元素(本例为字符串),又保证不重复。

    本文讨论至此,几乎可以还原一个class的原貌(唯有methods 和ctors的定义无法取得)。接下来讨论Reflection 的另三个动态性质:(1) 运行时生成instances,(2) 执

    行期唤起methods,(3) 运行时改动fields。

    运行时生成instances

    欲生成对象实体,在Reflection 动态机制中有两种作法,一个针对“无自变量ctor”,

    一个针对“带参数ctor”。图6是面对“无自变量ctor”的例子。如果欲调用的是“带参数ctor“就比较麻烦些,图7是个例子,其中不再调用Class的newInstance(),而是调用Constructor 的newInstance()。图7首先准备一个Class[]做为ctor的参数类型(本例指定为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。接下来再准备一个Object[] 做为ctor实参值(本例指定3.14159和125),调用上述专属ctor的newInstance()。

    #001 Class c = Class.forName("DynTest");

    #002 Object obj = null;

    #003 obj = c.newInstance(); //不带自变量

    #004 System.out.println(obj);

    图6:动态生成“Class object所对应之class”的对象实体;无自变量。

    #001 Class c = Class.forName("DynTest");

    #002 Class[] pTypes = new Class[] { double.class, int.class };

    #003 Constructor ctor = c.getConstructor(pTypes);

    #004 //指定parameter list,便可获得特定之ctor

    #005

    #006 Object obj = null;

    #007 Object[] arg = new Object[] {3.14159, 125}; //自变量

    #008 obj = ctor.newInstance(arg);

    #009 System.out.println(obj);

    图7:动态生成“Class object对应之class”的对象实体;自变量以Object[]表示。

    运行时调用methods

    这个动作和上述调用“带参数之ctor”相当类似。首先准备一个Class[]做为ctor的参数类型(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),获得特定的Method object。接下来准备一个Object[]放置自变量,然后调用上述所得之特定Method object的invoke(),如图8。知道为什么索取Method object时不需指定回返类型吗?因为method overloading机制要求signature(署名式)必须唯一,而回返类型并非signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无二的method。

    #001 public String func(String s, Hashtable ht)

    #002 {

    #003 …System.out.println("func invoked"); return s;

    #004 }

    #005 public static void main(String args[])

    #006 {

    #007 Class c = Class.forName("Test");

    #008 Class ptypes[] = new Class[2];

    #009 ptypes[0] = Class.forName("java.lang.String");

    #010 ptypes[1] = Class.forName("java.util.Hashtable");

    #011 Methodm = c.getMethod("func",ptypes);

    #012 Test obj = new Test();

    #013 Object args[] = new Object[2];

    #014 arg[0] = new String("Hello,world");

    #015 arg[1] = null;

    #016 Object r = m.invoke(obj, arg);

    #017 Integer rval = (String)r;

    #018 System.out.println(rval);

    #019 }

    图8:动态唤起method

    运行时变更fields内容

    与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Field object之后便可直接调用Field的get()和set(),如图9。

    #001 public class Test {

    #002 public double d;

    #003

    #004 public static void main(String args[])

    #005 {

    #006 Class c = Class.forName("Test");

    #007 Field f= c.getField("d"); //指定field 名称

    #008 Test obj = new Test();

    #009 System.out.println("d= " + (Double)f.get(obj));

    #010 f.set(obj, 12.34);

    #011 System.out.println("d= " + obj.d);

    #012 }

    #013 }

    图9:动态变更field内容

    Java源码改动办法

    先前我曾提到,原本想借由“改动Java标准库源码”来测知Class object的生成,但由于其ctor原始设计为private,也就是说不可能透过这个管道生成Class object(而是由class loader负责生成),因此“在ctor中打印出某种信息”的企图也就失去了意义。

    这里我要谈点题外话:如何修改Java标准库源码并让它反应到我们的应用程序来。假设我想修改java.lang.Class,让它在某些情况下打印某种信息。首先必须找出标准源码!当你下载JDK 套件并安装妥当,你会发现jdk150\src\java\lang 目录(见图10)之中有Class.java,这就是我们此次行动的标准源码。备份后加以修改,编译获得Class.class。接下来准备将.class 搬移到jdk150\jre\lib\endorsed(见图10)。

    这是一个十分特别的目录,class loader将优先从该处读取内含classes的.jar文件——成功的条件是.jar内的classes压缩路径必须和Java标准库的路径完全相同。为此,我们可以将刚才做出的Class.class先搬到一个为此目的而刻意做出来的\java\lang目录中,压缩为foo.zip(任意命名,唯需夹带路径java\lang),再将这个foo.zip搬到jdk150\jre\lib\endorsed并改名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个批处理文件(batch file),如图11,在DOS Box中使用。

    图10:JDK1.5安装后的目录组织。其中的endorsed是我新建。

    del e:\java\lang\*.class //清理干净

    del c:\jdk150\jre\lib\endorsed\foo.jar //清理干净

    c:

    cd c:\jdk150\src\java\lang

    javac -Xlint:unchecked Class.java //编译源码

    javac -Xlint:unchecked ClassLoader.java //编译另一个源码(如有必要)

    move *.class e:\java\lang //搬移至刻意制造的目录中

    e:

    cd e:\java\lang //以下压缩至适当目录

    pkzipc-add -path=root c:\jdk150\jre\lib\endorsed\foo.jar *.class

    cd e:\test //进入测试目录

    javac -Xlint:unchecked Test.java //编译测试程序

    java Test //执行测试程序

    图11:一个可在DOS Box中使用的批处理文件(batch file),用以自动化java.lang.Class

    的修改动作。Pkzipc(.exe)是个命令列压缩工具,add和path都是其命令。

    更多信息

    以下是视野所及与本文主题相关的更多讨论。这些信息可以弥补因文章篇幅限制而带来的不足,或带给您更多视野。

    l         "Take an in-depth look at the Java Reflection API -- Learn about the new Java 1.1 tools forfinding out information about classes", by Chuck McManis。此篇文章所附程序代码是本文示例程序的主要依据(本文示例程序示范了更多Reflection APIs,并采用JDK1.5 新式的for-loop 写法)。

    l         "Take a look inside Java classes -- Learn to deduce properties of a Java class from inside aJava program", by Chuck McManis。

    l         "The basics of Java class loaders -- The fundamentals of this key component of the Javaarchitecture", by Chuck McManis。

    l         《The Java Tutorial Continued》, Sun microsystems. Lesson58-61, "Reflection".

    注1用过诸如MFC这类所谓 Application Framework的程序员也许知道,MFC有所谓的dynamic creation。但它并不等同于Java的动态加载或动态辨识;所有能够在MFC程序中起作用的classes,都必须先在编译期被编译器“看见”。

    更多相关内容
  • java 反射机制

    2018-05-09 13:40:08
    java 反射机制深入理解,java 反射机制深入理解,java 反射机制深入理解,
  • java反射机制

    2018-06-24 13:39:01
    该文件有如何动态加载类,动态获得属性,动态调用方法,动态创建对象等
  • Java反射机制

    千次阅读 多人点赞 2022-02-27 20:28:29
    Java反射机制前言Java反射机制 前言 Java反射机制 反射机制原理示意图

    前言

    本文参考了https://blog.csdn.net/sinat_38259539/article/details/71799078

    一、反射的概述

    反射是框架设计的灵魂

    (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))

    就像人照镜子可以看到人的整体,五官等结构,通过反射我们也可以看到类的完整结构,所以说Class对象就像是一面镜子一样。
    !

    反射的定义

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

    要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

    比如之前在没有使用反射的时候,我们这里有一个类,如果我们想要调用里面的属性,方法等,我们要先创建一个对象,才可以调用里面的属性等。类似的,我们如果使用反射,想要使用一个类的成员变量,方法等,我们就要先获取一个Class对象,这个Class对象里面有类的成员变量,方法等信息,这样我们才可以进行相关的操作

    java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
    
    java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
    
    java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
    
    java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
    
    java.lang.Class:
    我们可以把下面这些代码看成一个整体,属于Class里面的内容
    	public class User{
    		// Field
    		int no;
    
    		// Constructor
    		public User(){
    		
    		}
    		public User(int no){
    			this.no = no;
    		}
    
    		// Method
    		public void setNo(int no){
    			this.no = no;
    		}
    		public int getNo(){
    			return no;
    		}
    	}
    

    初步了解反射以后,在使用它之前,我们来想想反射有什么用,以及我们为什么要使用反射

    反射的作用

    通过java语言中的反射机制可以操作字节码文件。
    ​通过反射机制可以操作代码片段。(class文件。)

    java的反射机制就是增加程序的灵活性,避免将程序写死到代码里
    例如: 实例化一个 person()对象, 不使用反射, ,就要调用构造器来实例化对象,new person(); 如果有一天,我们想要实例化其他对象, 那么必须修改源代码,并重新编译 。
    使用反射: class.forName(“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。
    反射在框架用的很多,我们可以通过外部文件的配置,在不修改源代码的情况下,来控制程序,符合OCP开闭原则
    反射的缺点是影响效率

    反射的应用场合

    有的时候,比如说我们想要创建一个对象, 但是
    在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息.
    在运行阶段使用,不能写死;工厂模式,动态生成对象;框架底层;运行过程中修改jar包中的一些内容(由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。)

    二、Java反射机制

    反射相关的类在java.lang.reflect这个包中

    反射机制原理示意图

    在这里插入图片描述

    Java Reflection

    反射机制可以完成下面的功能:
    在这里插入图片描述
    下面是反射相关的主要类
    在这里插入图片描述
    接下来,根据上面提供的和反射相关的类,进行相应的测试

    package com.zyh.java;
    
    import java.io.File;
    import java.io.FileReader;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    /**反射的引入
     * @author zengyihong
     * @create 2022--02--27 19:17
     */
    public class ReflectionQuestion {
        public static void main(String[] args) throws  Exception {
            /**
             * 根据配置文件 re.properties指定信息
             * 创建Cat对象,并调用hi方法
             */
            //传统方式 new对象-->调用方法
    //        Cat cat=new Cat();
    //        cat.hi();
    
             //尝试做一下,理解反射
            //1.Properties可以读取配置文件
            Properties properties=new Properties();
            File file=new File("day11/src/re.properties");
            FileReader reader=new FileReader(file);
            //把文件加载进来
            properties.load(reader);
    
            String classfullpath = properties.getProperty("classfullpath");
            String methodName = properties.getProperty("method");
            System.out.println("classfullpath="+classfullpath);
            System.out.println("method="+methodName);
            //2.创建对象 传统的方法,解决不了
            //3.使用反射机制解决
            System.out.println("---------------利用反射机制创建对象和调用方法-------------");
            //(1)加载类,返回Class类型的对象
            //参数的路径要写类的全类名,包名也得加上
            Class  aClass = Class.forName(classfullpath);
    
            //(2)通过aclass得到我们加载的类 com.zyh.java.Cat的对象实例
            Object instance = aClass.newInstance();
            //运行类型
            System.out.println("运行类型"+instance.getClass());
            //(3)通过aclass得到我们加载的类 com.zyh.java.Cat  的methodName  的方法对象
            //在反射中,可以把方法视为对象(万物皆对象)
            Method method1 = aClass.getMethod(methodName);
            //(4)通过method1 调用方法:即通过方法对象来调用方法
            //传统方法:对象.方法()  反射:方法.invoke(对象)
            method1.invoke(instance);
            //getField不能得到私有的属性
            Field nameField = aClass.getField("age");
            System.out.println(nameField.get(instance));
    
            //getConstructor  代表类的构造方法
            Constructor constructor = aClass.getConstructor();
            //public com.zyh.java.Cat()
            System.out.println(constructor);
            Constructor constructor1 = aClass.getConstructor(String.class);
            //public com.zyh.java.Cat(java.lang.String)
            System.out.println(constructor1);
    
        }
    }
    class Cat{
        private String name="招财猫";
        public   int age=10;
    
        public Cat() {
        }
    
        public Cat(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public Cat(String name) {
            this.name = name;
        }
    
        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 void hi(){
            System.out.println("你好:我是"+name);
        }
    
    }
    

    通过不同阶段,来获取对象实例
    在这里插入图片描述

    反射使用步骤

    反射 API 用来生成 JVM 中的类、接口或则对象的信息。

    1. Class 类:反射的核心类,可以获取类的属性,方法等信息。
    2. Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性 值。
    3. Method 类: Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或 者执行方法。
    4. Constructor 类: Java.lang.reflec 包中的类,表示类的构造方法。
    1. 获取想要操作的类的 Class 对象是反射的核心,通过 Class 对象我们可以任意调用类的方法。
    2. 调用 Class 类中的方法,既就是反射的使用阶段。
    3. 使用反射 API 来操作这些信息。

    三、 Class类

    基本介绍

    在这里插入图片描述
    在这里插入图片描述

    
    package com.hspedu.reflection.class_;
    import com.hspedu.Cat;
    
    import java.util.ArrayList;
    
    /**
    *  对 Class 类特点的梳理 
    **/
    public class Class01 {
    public static void main(String[] args) throws ClassNotFoundException {
    //看看 Class 类图
    //1. Class 也是类,因此也继承 Object 类
    //Class
    //2. Class 类对象不是 new 出来的,而是系统创建的
    //(1)  传统 new 对象
    /*    ClassLoader 类
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    	return loadClass(name, false);
    }
    */
    //Cat cat = new Cat();
     
    /*
    ClassLoader 类,  仍然是通过 ClassLoader 类加载 Cat 类的 Class 对象
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    	return loadClass(name, false);
    }
    */
    Class cls1 = Class.forName("com.zyh.java.Cat");
    
    //3.  对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
    Class cls2 = Class.forName("com.zyh.java.Cat");
    System.out.println(cls1.hashCode());
    System.out.println(cls2.hashCode());
    Class cls3 = Class.forName("com.zyh.java.Dog");
    System.out.println(cls3.hashCode());
    
    
    
    
    
    }
    }
    

    常用方法

    在这里插入图片描述

    package com.zyh.java;
    
    import java.lang.reflect.Field;
    
    /**
     * @author zengyihong
     * @create 2022--02--28 13:36
     */
    public class ClassMethod {
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    
            //类的全路径
            String fullpath="com.zyh.java.Car";
            //通过Class.forName获得Class对象
            Class carClass=Class.forName(fullpath);
            //com.zyh.java.Car
            System.out.println(carClass.getName());
            //获取该对象的实例
            Object instance = carClass.newInstance();
            //看出运行类型为 class com.zyh.java.Car
            System.out.println(instance.getClass());
            //getName返回这个对象的实体 class com.zyh.java.Car
            System.out.println(instance.getClass());
            //得到包名 package com.zyh.java
            System.out.println(carClass.getPackage());
            //返回所有字段
            Field[] fields = carClass.getFields();
            for (Field f:fields){
                System.out.println(f.getName());
            }
            //通过反射得到属性brand
            Field brand = carClass.getField("brand");
            System.out.println(brand.getName());
            //通过反射获取属性值
            //宝马
            Object o = brand.get(instance);
            System.out.println(o);
            //通过反射来修改属性值
            brand.set(instance,"奔驰");
            //奔驰
            System.out.println(brand.get(instance));
    
    
        }
    }
    

    获取Class类对象

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    package com.zyh.java;
    
    /**
     * 获取Class对象的方式
     *
     * @author zengyihong
     * @create 2022--02--28 8:41
     */
    public class GetClass_ {
    
        public static void main(String[] args) throws ClassNotFoundException {
            //方式1:Class.forName
            //这一步通常是通过配置文件读取
            String path= "com.zyh.java.Car";
            Class<?> aClass = Class.forName(path);
            System.out.println(aClass);
            //方式2:类名.class 多用于参数传递
            Class catClass = Car.class;
            System.out.println(catClass);
            //方式3:对象名.getClass()  应用场景:有对象实例
            Car car=new Car();
            Class  aClass1 = car.getClass();
            System.out.println(aClass1);
            //方式4:通过类加载器(4)来获取Class类对象
            //先得到类加载器car
            ClassLoader classLoader = car.getClass().getClassLoader();
            //通过类加载器得到Class对象 把类的全路径写上去
    
            Class<?> aClass2 = classLoader.loadClass(path);
    
            System.out.println(aClass2);
    
            Class<Integer> integerClass = int.class;
            System.out.println(integerClass);
    
            Class<Integer> type = Integer.TYPE;
            System.out.println(type);
    
    		System.out.println(type==integerClass ) ;//true
    
        }
    
    
    }
    

    对象实例化的方式

    Class 对象的 newInstance()
    1. 使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求
    该 Class 对象对应的类有默认的空构造器。 否则,编译不报错,运行报错
    调用 Constructor 对象的 newInstance()
    2. 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()
    方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
    
    
     //获取 Person 类的 Class 对象
     Class clazz=Class.forName("reflection.Person"); 
     //使用.newInstane 方法创建对象
     Person p=(Person) clazz.newInstance();
    //获取构造方法并创建对象
     Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
     //创建对象并设置属性
     Person p1=(Person) c.newInstance("李四","男",20);
    

    通过反射获取类的相关信息

    第一组: java.lang.Class 类
    在这里插入图片描述

    package com.zyh.java;
    
    import java.lang.reflect.Field;
    
    /**
     * @author zengyihong
     * @create 2022--02--28 14:37
     */
    public class ClassMethod1 {
        public static void main(String[] args) throws Exception{
    
            Class aClass=Class.forName("com.zyh.java.Person");
            //获取全类名
            System.out.println(aClass.getName());
            //获取简单名字
            System.out.println(aClass.getSimpleName());
    
            //获取所有public修饰的属性,包括本类以及父类的
            Field[] fields = aClass.getFields();
            for (Field f :fields) {
                System.out.println("本类以及父类的属性:"+f.getName());
            }
            //获取本类所有属性,包括public和非public
            Field[] declaredFields = aClass.getDeclaredFields();
            for(Field f:declaredFields){
                System.out.println("所有的属性包括public和非public的:"+f.getName());
            }
    
    
        }
    }
    
    class A  {
        public String hobby;
    
    
    }
    
    
    
    class Person extends A{
        public String name;
    
        protected int age;
        String job;
        private double sal;
        //方法
    
        public void m1() {
    
        }
    
        protected void m2() {
    
        }
    
        void m3() {
    
        }
    
        private void m4() {
    
        }
    
        public Person() {
        }
    
        public Person(String name, int age, String job, double sal) {
            this.name = name;
            this.age = age;
            this.job = job;
            this.sal = sal;
        }
    }
    

    第二组: java.lang.reflect.Field 类
    在这里插入图片描述

    第三组: java.lang.reflect.Method 类
    在这里插入图片描述
    第四组: java.lang.reflect.Constructor 类
    在这里插入图片描述

    四、类加载

    基本说明

    静态成员的初始化和类加载有关
    在这里插入图片描述

    在这里插入图片描述
    编译的时候,很明显会报错,这就体现了静态加载,不过我们输入的值也有可能是1,2,或者其他值,不一定会输入1,也就是说在运行的时候,可能没有用到Dog这个类,但是它在编译的时候就会加载这个类,不管我们有没有使用
    在这里插入图片描述
    在这里插入图片描述

    这样子编译通过了,我们使用了反射,反射是动态加载,如果我们没有输入2,程序没有执行到那里,就不会真正加载Person类,所以虽然程序中此时没有Person类,也不会报错

    动态加载就类似于延迟加载

    类加载时机

    在这里插入图片描述

    类加载过程图

    在这里插入图片描述
    验证:主要是对我们的文件进行安全的校验,比如说文件格式是否正确,元数据验证是否正确,符号引用是否正确,字节码是否正确

    准备:对静态变量进行分配内存并且完成默认初始化
    解析:虚拟机会把常量池的符号引用替换成直接引用

    初始化:真正执行类中定义的Java代码

    加载阶段和准备阶段是由JVM来控制的,程序员是无法控制的,初始化,程序员可以控制,比如说A类有静态代码块,我们在里面写什么,完全是由我们来控制的

    类加载各个阶段完成的任务

    在这里插入图片描述

    加载阶段

    在这里插入图片描述

    连接阶段—验证

    在这里插入图片描述

    连接阶段—准备

    在这里插入图片描述

    package com.hspedu.reflection.classload_;
    
    /**
    *  我们说明一个类加载的链接阶段-准备
    */
    public class ClassLoad02 {
    public static void main(String[] args) {
    
    }
    }
    class A {
    //属性-成员变量-字段
    // 分析类加载的链接阶段-准备 属性是如何处理
    //1. n1  是实例属性,  不是静态变量,因此在准备阶段,是不会分配内存
    //2. n2  是静态变量,分配内存 n2  是默认初始化 0 ,而不是 20
    //3. n3  是 static final  是常量,  他和静态变量不一样,  因为一旦赋值就不变 n3 = 30
    public int n1 = 10;
    public static    int n2 = 20;
    public static final    int n3 = 30;
    
    }	
    

    连接阶段-解析

    在这里插入图片描述
    在编译的时候,A类和B类还没有真正加载到内存,还没有分配真正空间,这个时候是以符号的方式,记录的是相对的引用
    在这里插入图片描述
    假设这里有符号,1和2,这就说1引用了2
    一旦到了类加载,就会有A类的Class对象和B类的Class对象
    因为原来编译的时候,他们是符号引用,他们还没有真正加载到内存,没有实际的内存地址,只能按符号的方式来记录
    在这里插入图片描述
    比如说现在有两个人,他们之间的距离刚开始是以一种相对的方式来记录,如果放在地球上,为了更精确记录他们之间的关系,用经度和纬度来记录他们之间的关系
    现在再回到刚刚讲的A类和B类
    当他们加载到内存中,A类和B类都有真正的内存地址,它们之间就通过这个内存地址来实现相互之间的引用,这就是符号引用替换成直接引用

    Initialization(初始化)

    在这里插入图片描述

    package com.hspedu.reflection.classload_;
    
     
    // 演示类加载-初始化阶段  
    public class ClassLoad03 {
    public static void main(String[] args) throws ClassNotFoundException {
     
    //1.  加载 B 类,并生成 B 的 class 对象
    //2.  链接 num = 0
    //3.  初始化阶段
    //依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
    /*
    clinit() {
    	System.out.println("B  静态代码块被执行");
    	//num = 300;
    	num = 100;
    }
    	//合并: num = 100
    
    
    */
    
    //new B();//类加载
    //System.out.println(B.num);//100,  如果直接使用类的静态属性,也会导致类的加载
    
    //看看加载类的时候,是有同步机制控制
    /*
    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    //正因为有这个机制,才能保证某个类在内存中,  只有一份 Class 对象
    	synchronized (getClassLoadingLock(name)) {
    	//....
    	}
    }
    */
    	B b = new B();
    	}
    }
    
    class B {
    	static {
    		System.out.println("B  静态代码块被执行");
    		num = 300;
    	}
    
    	static int num = 100;
    
    	public B() {//构造器
    		System.out.println("B()  构造器被执行");
    	}
    }
    

    五、总结

    动态语言

    动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结
    构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,
    而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。

    反射机制概念 (运行状态中知道类所有的属性和方法)
    在这里插入图片描述

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

    反射的应用场合

    编译时类型和运行时类型

    在 Java 程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如:
    Person p=new Student();

    其中编译时类型为 Person,运行时类型为 Student。的编译时类型无法获取具体方法
    程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用
    该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。
    然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象
    和类的真实信息,此时就必须使用到反射了。

    展开全文
  • 这是『Java学习指南系列』的第18篇教程 ,是Java开发的高级课程,介绍反射机制、注解和框架设计的一般性原理。 二、主要内容  本篇包含以下内容: * 使用反射机制,读取Class中的字段信息 * 使用反射机制,对...
  • 主要介绍了Java 反射机制的实例详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握反射机制,需要的朋友可以参考下
  • java反射机制.md

    2021-01-26 09:54:45
    深入了解java反射机制的原理,通过反射机制可以破坏单例模式,如何防止通过反射机制拿到单例模式的构造器呢?用枚举类可破
  • java反射机制.xmind

    2020-08-09 19:41:14
    自己总结的java反射机制的笔记,绘制了详细的思维导图,每个思维导图中均有详细的博文解释,方便大家学习和理解,免费分享给大家。适合java的爱好者和学习者
  • Java反射机制,万物皆对象,class也是一个类的对象,通过类类型,反射获得类的成员属性,构造方法,成员方法,并调用类中的方法,也可以调用私有方法。
  • 主要介绍了Java反射机制及Method.invoke详解,本文讲解了JAVA反射机制、得到某个对象的属性、得到某个类的静态属性、执行某对象的方法、执行某个类的静态方法等内容,需要的朋友可以参考下
  • NULL 博文链接:https://chinesethink.iteye.com/blog/1601198
  • Java反射机制详解

    2020-09-03 01:54:51
    主要介绍了Java反射机制,首先简单介绍了反射机制的预备知识,进一步分析了Java反射机制的原理、实现技巧与应用方法,需要的朋友可以参考下
  • 图解java反射机制及常用应用场景

    千次阅读 多人点赞 2022-02-27 17:13:53
    一、什么是java反射? 二、Hello World 三、类加载与反射关系 四、操作反射的java类 五、反射的常用场景 六、反射的优缺点

    在这里插入图片描述

    一、什么是java反射?

    在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载

    但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。

    在这里插入图片描述

    • 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
    • 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
    • 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的

    面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的“java反射机制”。

    那么java的反射机制能够做那些事呢?大概是这样几种:

    • 在程序运行期动态的根据package名.类名实例化类对象

    • 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法

    • 在程序运行期动态使用对象的成员变量属性

    • 在程序运行期动态调用对象的方法(私有方法也可以调用)

    二、Hello World

    先入个门,大佬可以略过这一段。我们定义一个类叫做Student

    package com.zimug.java.reflection;
    
    public class Student {
        public String nickName;
        private Integer age;   //这里是private
    
        public void dinner(){
            System.out.println("吃晚餐!");
        }
    
        private void sleep(int minutes){   //private修饰符
            System.out.println("睡" + minutes + "分钟");
        }
    }
    

    如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法

    Student student = new Student();
    student.dinner();
    

    如果是反射的方式我们该怎么调用呢?

    //获取Student类信息
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //对象实例化
    Object obj = cls.getDeclaredConstructor().newInstance();
    //根据方法名获取并执行方法
    Method dinnerMethod = cls.getDeclaredMethod("dinner");
    dinnerMethod.invoke(obj);  //打印:吃晚餐!
    

    通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。

    三、类加载与反射关系

    java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。

    java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如对于Student类在加载阶段会有如下动作:

    • 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象
    • 一个Class类(字节码文件)对应一个Class对象,并且只有一个
    • 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息。

    在这里插入图片描述

    有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。

    • 在运行期你可以直接new一个Student对象
    • 也可以使用反射的方法构造一个Student对象

    在这里插入图片描述

    但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    Class cls2 = new Student().getClass();
    
    System.out.println(cls == cls2); //比较Class对象的地址,输出结果是true
    

    四、操作反射的java类

    了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类和方法了:

    • java.lang.Class: 代表一个类
    • java.lang.reflect.Constructor: 代表类的构造方法
    • java.lang.reflect.Method: 代表类的普通方法
    • java.lang.reflect.Field: 代表类的成员变量
    • Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
    • java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解

    4.1.获取Class对象的三种方法

    Class.forName()方法获取Class对象

    /**
    * Class.forName方法获取Class对象,这也是反射中最常用的获取对象的方法,因为字符串传参增强了配置实现的灵活性
    */
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    

    类名.class获取Class对象

    /**
    * `类名.class`的方式获取Class对象
    */
    Class clz = User.class;
    

    类对象.getClass()方式获取Class对象

    /**
    * `类对象.getClass()`方式获取Class对象
    */
    User user = new User();
    Class clazz = user.getClass();
    

    虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。

    4.2.获取Class类对象的基本信息

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    
    //获取类的包名+类名
    System.out.println(cls.getName());          //com.zimug.java.reflection.Student
    //获取类的父类
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //这个类型是不是一个注解?
    System.out.println(cls.isAnnotation());     //false
    //这个类型是不是一个枚举?
    System.out.println(cls.isEnum());      //false
    //这个类型是不是基础数据类型?
    System.out.println(cls.isPrimitive()); //false
    

    Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法

    • 获取Class类对象代表的类实现了哪些接口: getInterfaces()
    • 获取Class类对象代表的类使用了哪些注解: getAnnotations()

    4.3. 获得Class对象的成员变量

    结合上文中的Student类的定义理解下面的代码

    Class cls = Class.forName("com.zimug.java.reflection.Student");
    
    Field[] fields = cls.getFields();
    for (Field field : fields) {
    System.out.println(field.getName());      //nickName
    }
    
    fields = cls.getDeclaredFields();
    for (Field field : fields) {
    System.out.println(field.getName());      //nickName 换行  age
    }
    
    • getFields()方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量
    • getDeclaredFields方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量

    4.4.获取Class对象的方法

    • getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,包含从父类继承而来的方法
    • getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,但是不包含从父类继承而来的方法
    • getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法
    • getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法
            Class cls = Class.forName("com.zimug.java.reflection.Student");
    
            Method[] methods = cls.getMethods();
            System.out.println("Student对象的非私有方法");
            for (Method m : methods) {
                System.out.print(m.getName() + ",");
            }
            System.out.println("  end");
    
    
            Method[] allMethods = cls.getDeclaredMethods();
            System.out.println("Student对象的所有方法");
            for (Method m : allMethods) {
                System.out.print(m.getName() + ",");
            }
            System.out.println("  end");
    
    
            Method dinnerMethod = cls.getMethod("dinner");
            System.out.println("dinner方法的参数个数" + dinnerMethod.getParameterCount());
    
            Method sleepMethod = cls.getDeclaredMethod("sleep",int.class);
            System.out.println("sleep方法的参数个数" + sleepMethod.getParameterCount());
            System.out.println("sleep方法的参数对象数组" + Arrays.toString(sleepMethod.getParameters()));
            System.out.println("sleep方法的参数返回值类型" + sleepMethod.getReturnType());
    

    上面代码的执行结果如下:

    Student对象的非私有方法
    dinner,wait,wait,wait,equals,toString,hashCode,getClass,notify,notifyAll,  end
    
    Student对象的所有方法
    dinner,sleep,  end
    
    dinner方法的参数个数0
    sleep方法的参数个数1
    sleep方法的参数对象数组[int arg0]
    sleep方法的参数返回值类型void
    

    可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:

    • 获取参数相关的属性:
      • 获取方法参数个数:getParameterCount()
      • 获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
    • 获取返回值相关的属性
      • 获取方法返回值的数据类型:getReturnType()

    4.5.方法的调用

    实际在上文中已经演示了方法的调用,如下invoke调用dinner方法

    Method dinnerMethod = cls.getDeclaredMethod("dinner");
    dinnerMethod.invoke(obj);  //打印:吃晚餐!
    

    dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object... args有多少参数就按照方法定义依次传参就可以了。

    public Object invoke(Object obj, Object... args)
    

    4.6.创建类的对象(实例化对象)

    //获取Student类信息
    Class cls = Class.forName("com.zimug.java.reflection.Student");
    //对象实例化
    Student student = (Student)cls.getDeclaredConstructor().newInstance();
    //下面的这种方法是已经Deprecated了,不建议使用。但是在比较旧的JDK版本中仍然是唯一的方式。
    //Student student = (Student)cls.newInstance();
    

    五、反射的常用场景

    • 通过配置信息调用类的方法
    • 结合注解实现特殊功能
    • 按需加载jar包或class

    5.1. 通过配置信息调用类的方法

    将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!

    public void invokeClassMethod(String className,String methodName) throws ClassNotFoundException, 
                NoSuchMethodException, 
                InvocationTargetException, 
                InstantiationException, 
                IllegalAccessException {
            //获取类信息
            Class cls = Class.forName(className);
            //对象实例化
            Object obj = cls.getDeclaredConstructor().newInstance();
            //根据方法名获取并执行方法
            Method dinnerMethod = cls.getDeclaredMethod(methodName);
            dinnerMethod.invoke(obj);
    }
    

    5.2.结合注解实现特殊功能

    大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实体类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。

    @TableName("t_student")
    public class Student {
        public String nickName;
        private Integer age;
    }
    

    下面我们自定义TableName这个注解

    @Target(ElementType.TYPE)  //表示TableName可作用于类、接口或enum Class, 或interface
    @Retention(RetentionPolicy.RUNTIME) //表示运行时由JVM加载
    public @interface TableName {
           String value() ;   //则使用@TableName注解的时候: @TableName(”t_student”);
    }
    

    有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以

    <dependency>
        <groupId>org.reflections</groupId>
        <artifactId>reflections</artifactId>
        <version>0.9.10</version>
    </dependency>
    

    看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息

    // 要扫描的包
    String packageName = "com.zimug.java.reflection";
    Reflections f = new Reflections(packageName);
    // 获取扫描到的标记注解的集合
    Set<Class<?>> set = f.getTypesAnnotatedWith(TableName.class);
    for (Class<?> c : set) {
    // 循环获取标记的注解
    TableName annotation = c.getAnnotation(TableName.class);
    // 打印注解中的内容
    System.out.println(c.getName() + "类,TableName注解value=" + annotation.value());
    

    输出结果是:

    com.zimug.java.reflection.Student类,TableName注解value=t_student
    

    有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?

    反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!

    5.3.按需加载jar包或class

    在某些场景下,我们可能不希望JVM的加载器一次性的把所有classpath下的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。

    把jar包放在classpath外面,指定加载路径,实现动态加载。

    //按路径加载jar包
    File file = new File("D:/com/zimug/commons-lang3.jar");
    URL url = file.toURI().toURL();
    //创建类加载器
    ClassLoader classLoader = new URLClassLoader(new URL[]{url});
    
    Class cls = classLoader.loadClass("org.apache.commons.lang3.StringUtils");
    

    同样的把.class文件放在一个路径下,我们也是可以动态加载到的

    //java的.class文件所在路径
    File file = new File("D:/com/zimug");
    URL url = file.toURI().toURL();
    //创建类加载器
    ClassLoader classLoader = new URLClassLoader(new URL[]{url});
    //加载指定类,package全路径
    Class<?> cls = classLoader.loadClass("com.zimug.java.reflection.Student");
    

    类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动web容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。

    六、反射的优缺点

    • 优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。在框架开发方面也有非常广泛的应用,特别是结合注解的使用。
    • 缺点:
      • 也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
      • 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
      • 破坏java类封装性,类的信息隐藏性和边界被破坏

    言尽于此,限于笔者的知识结构,可能有不严谨之处,欢迎大家讨论与指正!期待您的关注,我将持续带来更哇塞的作品。

    在这里插入图片描述

    展开全文
  • Java反射机制的实现_Reflection,适合学习了解反射机制。
  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 Java...
  • Java反射机制在数据持久层轻量级ORM框架中的应用研究.pdf
  • Java反射机制应用实践

    2020-12-22 20:01:50
     Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis都可以看见反射的身影。通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,...
  • java反射机制创建对象实现:java 深度拷贝 -超完美 ,自己做的,下面  package aop; public class Student { private String name; private int age; public String getName() { return name; } public ...
  • 今天小编就为大家分享一篇关于Java反射机制的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
  • NULL 博文链接:https://1017401036.iteye.com/blog/2343706
  • 下面小编就为大家带来一篇利用java反射机制调用类的私有方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 226,452
精华内容 90,580
关键字:

java反射机制