浅析Java反射机制

欢迎浏览我的博客 获得更多精彩文章
https://boyn.top

反射

1.1 反射简介

在Java中,反射是一个十分有趣的特性.所谓反射,就是在我们运行时,可以分析一个类和对象.

最常见的反射的应用,莫过于是Spring了,在Spring中,大量运用到了JavaBean和反射机制

在Core Java中,他是这样来介绍反射的:

反射库( Reflection library) 提供了一个非常丰富且精心设计的工具集, 以便编写能够动
态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans 中, 它是 Java组件的体系结构
(有关 JavaBeans 的详细内容在卷 II 中阐述)。使用反射, Java 可以支持 Visual Basic 用户习惯
使用的工具。特别是在设计或运行中添加新类时, 能够快速地应用开发工具动态地查询新添
加类的能力。

我们在深入学习反射之前,需要先了解他的作用是什么:

  • 在运行时分析类的能力。
  • 在运行时查看对象, 例如, 编写一个 toString() 方法供所有类使用。
  • 实现通用的数组操作代码

1.2 Class类

在Java程序运行时,JVM会创建一个运行时标识(Runtime Signal)来跟踪每个对象的类,我们可以根据这个,来获取类的信息

首先来看一个实例

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Person p1 = new Person("Jack");
        System.out.println(p.getClass().getName());
        //Output:Reflection.Person
        
        
    }
}

在这里,定义了一个简单的Person类,并在main方法中输出了他的类名.

我们把目光放到getClass()方法中,它是Object类定义的一个本地方法,其实现细节对用户隐藏,那么我们也就不深究,它返回一个Class对象,其中尖括号表示它是一个泛型类.

public final native Class<?> getClass();

Class类定义在了java.lang包中,从JDK1.0就已经存在了,是一个非常古老的类,它代表的是在java程序运行时的类和接口的代表类,值得注意的是,虽然Java的原始类型(boolean, byte, char, short, int, long, float, and double)不是Object的子类,但是他们也被表示为Class对象,其原因我认为是为了暴露原始类型的数据,以及在不同的类中对这些原始类型的引用.

鉴于上面所说的,如果当我们知道一个类的类型,那么我们是否可以通过Class类来构造一个跟这个类相同的实例呢?当然是可以的!请看下面

try {
    Person p = new Person("Jack");
    String className = p.getClass().getName();
    Person p2 = (Person)Class.forName(className).newInstance();
}catch (Exception e){
    e.printStackTrace();
}

通过获取一个实例名字的方式,可以创建该实例的一个无参实例,但是这个方法在Java9中被标记被Deprecated,意味着用这个函数是不安全的,随时可能在以后的版本被删除,那么,我们可以用新的版本

try {
    Person p = new Person("Jack");
    String className = p.getClass().getName();
    Constructor<Person> p_con = (Constructor<Person>) Class.forName(className).getConstructor();
    Person p2 = p_con.newInstance();
    Person p3 = p_con.newInstance("Carl");
} catch (Exception e) {
    e.printStackTrace();
}

就是通过Constructor这个类来进行这样的构造.

1.3 通过反射来分析类

对于反射机制,其最重要的内容和功能,就是检查类的结构,通过反射,我们可以了解到一个类的方法域,数据域.

一个实际的例子
public static void main(String[] args) throws Exception{
    String className;
    if(args.length>0) className = args[0];
    //在此处可以通过运行程序时的参数 获取类名
    else{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        System.out.println(">>>");
        className = br.readLine();
    }

    Class classRe = Class.forName(className);
    Class superClassRe = classRe.getSuperclass();//是为了获取它的父类以提供继承的情况
    String modifiers = Modifier.toString(classRe.getModifiers());
    if(modifiers.length()>0) System.out.println(modifiers+" ");
    System.out.println("class "+className);
    if(superClassRe!=null && superClassRe != Object.class) System.out.println(" extends "+superClassRe.getName());
    //获取了它的父类之后,当其父类不为空或者为Object类时,就将他的直接父类打印出来
    {
        System.out.print("\n{\n");
    }
    //TODO:打印构造器
    System.out.println();
    //TODO:打印公有 私有成员
    System.out.println();
    //TODO:打印数据
    System.out.println("}");
}

在此处 我们先将一个类名传入(完整类名 如java.io.BufferedReader),用forName方法返回一个待反射的Class对象,然后获取其父类.Modifier.toString()方法是用来分析一个类,方法或者数据成员的修饰符使用情况,接着将其打印.后面给出了三个TODO,其代码分别在下面给出

打印构造器
public static void printConstructors(Class cl){
    Constructor[] constructors = cl.getDeclaredConstructors();
    //通过getDeclaredConstructors()方法获取这个类的构造器
    for(Constructor c:constructors){
        String constructorName = c.getName();
        System.out.println("  ");
        String modifiers = Modifier.toString(c.getModifiers());
        if(modifiers.length()>0) System.out.println(modifiers+" ");
        System.out.println(constructorName+"(");

        Class[] paramTypes = c.getParameterTypes();
        for(int j = 0 ;j<paramTypes.length;j++){
            if(j>0) System.out.println(", ");
            System.out.println(paramTypes[j].getName());
        }
        System.out.println(");");
    }
}
打印方法
public static void printMethods(Class cl){
    Method[] methods = cl.getMethods();
    //通过getDeclaredConstructors()方法获取这个类的构造器
    for(Method m:methods){
        Class returnType = m.getReturnType();
        String methodName = m.getName();
        System.out.println("  ");
        String modifiers = Modifier.toString(m.getModifiers());
        if(modifiers.length()>0) System.out.println(modifiers+" ");
        System.out.println(returnType.getName());
        System.out.println(methodName+"(");
        Class[] paramTypes = m.getParameterTypes();
        for(int j = 0 ;j<paramTypes.length;j++){
            if(j>0) System.out.println(", ");
            System.out.println(paramTypes[j].getName());
        }
        System.out.println(");");
    }
}

打印方法和打印构造器的实现方式非常相似,不同的就是打印方法的函数需要将其返回类型打印出来.

打印类成员
public static void printFields(Class cl){
    Field[] fields = cl.getDeclaredFields();
    for(Field f:fields){
        Class type = f.getType();
        String name = f.getName();
        System.out.println("  ");
        String modifiers = Modifier.toString(f.getModifiers());
        if(modifiers.length()>0) System.out.println(modifiers+" ");
        System.out.println(type.getName()+" "+name+" ");
    }
}

通过这几个方法,你就可以打印出一个类的各种属性了.当然,打印出来的结果或许不那么称心如意,我们将在文章的后面给出一个改进的方法.

1.4 通过反射来分析对象

在上一节中,给出了一个分析类的方法和数据成员的方法,那么,可不可以在运行时通过反射来分析一个对象的各个成员,具体来说,就是通过反射来实现一个对象的getter/setter方法呢?答案当然是可以的.与上一节相似,我们也通过一个例子来入手

在这个例子中,我们通过反射机制来实现一个通用的toString方法,让被传入的成员可以通过这个方法返回一个关于其成员的字符串

public String toString(Object obj) {
        if (obj == null) return "";
        if (visited.contains(obj)) return "...";
        visited.add(obj);
        Class cl = obj.getClass();
        //将一个obj对象转成Class对象
        if (cl == String.class) return (String) obj;//当其为String类型的时候,直接返回
        if (cl.isArray()) {
            //对数组的情况要额外处理
            StringBuilder r = new StringBuilder(cl.getComponentType() + "[] {");
            for (int i = 0; i < Array.getLength(obj); i++) {
                if (i > 0) r.append(",");
                Object val = Array.get(obj, i);
                if (cl.getComponentType().isPrimitive()) r.append(val);
                    //判断是否为基本类型,如果是基本类型,就直接添加,如果不是,就将这个对象
                    // 通过递归toString方法转成字符串
                else r.append(toString(val));
            }
            return r.toString() + "}";
        }
        String r = new String(cl.getSimpleName());
        do {
            r += "[";
            Field[] fields = cl.getDeclaredFields();
            AccessibleObject.setAccessible(fields, true);
            //通过这个设置可以让程序访问所有的域
            for (Field f : fields) {
                if (!Modifier.isStatic(f.getModifiers())) {
                    //在这里我们选择不打印静态域,就是static修饰的成员
                    if (!r.endsWith("[")) r += ",";
                    r += f.getName() + " = ";
                    //获取每个属性的名字
                    try {
                        Class t = f.getType();
                        Object val = f.get(obj);
                        if (t.isPrimitive()) r += val;
                            //检查这个域的类型是不是基本类型,如果是基本类型的话,就直接添加,这样可以减少
                            //调用toString方法的次数,提升速度
                        else r += toString(val);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            r += "]";
            cl = cl.getSuperclass();
        } while (cl != null);

        return r;

    }

接下来详细讲一下这个方法.对这个方法传入一个对象(根据Java的自动装箱机制,如果传入的是基本类型,会自动转成他们的包装类).判断这个对象是否已经在visited数组里面,如果已经是被转换过的对象,就直接返回… 然后判断是否数组,将数组里面的对象用{}包裹起来.对于基本的对象,就获取其域名字和值,最后返回字符串.

1.5 通过反射来调用方法

在C/C++中,可以通过函数指针来调用函数,而Java中,则可以通过反射来达到同样的效果(虽然会复杂得多)

首先,我们需要获取一个方法,在Java中,获取方法可以通过Methon类来解决:

在Person类中,我们定义了两个方法

public static void sayHello(){
    System.out.println(" Hello");
}

public void saySomething(String message){
    System.out.println(name+" "+message);
}

其中sayHello是静态方法,saySomething是非静态的方法

我们先获取Method对象

Method m1 = Person.class.getMethod("sayHello");
Method m2 = Person.class.getMethod("saySomething", String.class);

在Class类中,通过getMethod方法,并指定方法名和参数的类型,可以得到一个方法变量(需要处理特定异常)

m1.invoke(null);
m2.invoke(null,"ABCD");

在这两个语句中,都用到了一个invoke方法,我们先观察一下invoke方法的声明:

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

invoke方法中,第一个参数obj是运行方法时对应的对象,args是Object类数组

比如我们invoke对应的是方法f1,那么我们传入参数,就相当于调用 obj.f1(args[0],args[1]…),如果调用的是静态方法,那么obj可以传入该类的对象,或者null.如果调用的是非静态的方法,那么obj必须传入一个该类的对象,并且如果方法中用到了关于类成员变量,那么就会与传入的类的对象中的成员变量进行对应.

再说说它的返回值,如果调用的是一个void方法,那么就没什么好担心的,如果调用的方法具有返回值,就需要在其前面用强制转型,基本类型需要用其包装类.

调用代码例子如下:

Method m1 = Person.class.getMethod("sayHello");
Method m2 = Person.class.getMethod("saySomething", String.class);
Person p = new Person("Jack");
m1.invoke(null);
m2.invoke(p,",Hi!");

输出结果:

Hello
Jack ,Hi!

2 对反射方法的总结

反射机制使得人们可以通过在运行时查看域和方法, 让人们编写出更具有通用性的程序。
这种功能对于编写系统程序来说极其实用,但是通常不适于编写应用程序。反射是很脆弱的,
即编译器很难帮助人们发现程序中的错误, 因此只有在运行时才发现错误并导致异常。

对于反射来说,我认为在进行一个框架,引擎编写的时候,会大量地用到反射技术,它可以方便地看到一个类中的成员和方法,但是其缺点在于,如果要在应用程序中使用反射,要是在调用时出了差错,在编译期时无法看得到其中的错误,必须在运行时才可以知道是否成功调用,这对于某些应用来说是十分敏感的.

下面给出一些Class类的常用方法

1.名称信息

方法描述
public String getName()获取完整的类名,包括包名
public String getSimpleName()只获取类名,不包括包名
public String getCanonicalName()返回更友好的名称(良好地显示数组)
public String getPackage()返回包信息

2.字段信息

Class类方法

方法描述
public Field[] getFields()返回所有的public字段,包括父类
public Field[] getDeclaredField()返回所有的public字段,不包括父类
public Field getField(String name)返回该类或者父类中指定名称的public字段
public Field getDeclaredField()返回该类中声明的指定名称的public字段

Field类方法

方法描述
public String getName()获取字段的名称
public boolean isAccessible()是否有该字段的访问权限
public void setAccessible(boolean flag)设为true表示忽略访问权限控制,可以访问private方法
public Object get(Object obj)获取指定对象中字段的值
public void set(Object obj,Object value)将指定对象obj中该字段的值换成value

3.方法信息

Class类方法

方法描述
public Method[] getMethods()返回所有的public字段,包括父类
public Method[] getDeclaredMethod()返回所有的public字段,不包括父类
public Method getMethod(String name,Class<?>… parameterTypes)返回该类或者父类中指定名称的public字段
public Method getDeclaredMethod()返回该类中声明的指定名称的public字段

Method类方法

方法描述
public String getName()获取字段的名称
public Object invoke(Object obj, Object… args)在上方已经介绍过
public void setAccessible(boolean flag)设为true表示忽略访问权限控制,可以访问private方法

3.完整的反射机制类分析应用代码

/**
 * 这个类利用反射机制,通过用户输入的类名,获取这个类的方法与数据并打印出来
 */
public class GetClassName {

    public static void main(String[] args) throws Exception{
        String className;
        if(args.length>0) className = args[0];
        //在此处可以通过运行程序时的参数 获取类名
        else{
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.print(">>>");
            className = br.readLine();
        }

        Class classRe = Class.forName(className);
        Class superClassRe = classRe.getSuperclass();//是为了获取它的父类以提供继承的情况
        Class[] interfaceClassRe = classRe.getInterfaces();
        String modifiers = Modifier.toString(classRe.getModifiers());
        if(modifiers.length()>0) System.out.print(modifiers+" ");
        System.out.print("class "+classRe.getSimpleName());
        if(superClassRe!=null && superClassRe != Object.class) System.out.print(" extends "+superClassRe.getSimpleName());
        System.out.print(" implements");
        for(Class in :interfaceClassRe){
            System.out.print(" "+in.getSimpleName());
        }
        //获取了它的父类之后,当其父类不为空或者为Object类时,就将他的直接父类打印出来
        {
            System.out.print("\n{\n");
        }
        //TODO:打印构造器
        printConstructors(classRe);
        System.out.println();
        //TODO:打印公有 私有成员
        printFields(classRe);
        System.out.println();
        //TODO:打印数据
        printMethods(classRe);
        System.out.println("}");
    }

    public static void printConstructors(Class cl){
        Constructor[] constructors = cl.getDeclaredConstructors();
        //通过getDeclaredConstructors()方法获取这个类的构造器
        for(Constructor c:constructors){
            c.setAccessible(true);
            String constructorName = cl.getSimpleName();
            System.out.print("  ");
            String modifiers = Modifier.toString(c.getModifiers());
            if(modifiers.length()>0) System.out.print(modifiers+" ");
            System.out.print(constructorName+"(");

            Parameter[] parameters = c.getParameters();
            for(int j = 0 ;j<parameters.length;j++){
                if(j>0) System.out.print(", ");
                System.out.print(parameters[j].getType().getSimpleName()+" "+parameters[j].getName());
            }
            System.out.println(");");
        }
    }

    public static void printMethods(Class cl){
        Method[] methods = cl.getMethods();
        //通过getDeclaredConstructors()方法获取这个类的构造器
        for(Method m:methods){
            m.setAccessible(true);
            Class returnType = m.getReturnType();
            String methodName = m.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(m.getModifiers());
            if(modifiers.length()>0) System.out.print(modifiers+" ");
            System.out.print(returnType.getSimpleName()+" ");
            System.out.print(methodName+" (");
            m.getParameters();
            Parameter[] paramTypes = m.getParameters();
            for(int j = 0 ;j<paramTypes.length;j++){
                if(j>0) System.out.print(", ");
                System.out.print(paramTypes[j].getType().getSimpleName()+" "+paramTypes[j].getName());
            }
            System.out.println(");");
        }
    }

    public static void printFields(Class cl){
        Field[] fields = cl.getDeclaredFields();
        for(Field f:fields){
            f.setAccessible(true);
            Class type = f.getType();
            String name = f.getName();
            System.out.print("  ");
            String modifiers = Modifier.toString(f.getModifiers());
            if(modifiers.length()>0) System.out.print(modifiers+" ");
            System.out.print(type.getSimpleName()+" "+name+" ");
            System.out.println(";");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值