欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java反射--反射与类操作

程序员文章站 2022-05-22 12:37:45
...

内容学习于:edu.aliyun.com


1. 反射获取类结构

  使用Class实现了对象实例化,并且通过Class类反射构造了类的实例化对象,但是这并不意味着这些全部都属于反射机制的功能,如果认真去分析的话,实际上反射可以完整的实现Java允许规定的类的操作形式。

  如果在日后的开发之中你突然发现需要对二进制文件做更深入的一层分析的时候,那么此时你有两个选择:

  • 选择一:通过Oracle的官方标准进行二进制字节流数据的读取分析:
  • 选择二:使用一些第三方工具包(Java Relection), 这个包可以实现“*.class"文件的分析。

  Class作为所有反射操作的源头,于是在Class类里面就可以获取一些结构 上的信息,例如:类所在的包、类所继承的父类以及类所实现的相关的接口,这些的操作方法如下:

  • 获取程序所在的包名称:public String getPackageName()
  • 获取所继承的父类:public Class<? super T> getSuperclass()
  • 获取所有的父接口:public Class<?>[] getInterfaces()

  结构如下图所示:Java反射--反射与类操作

获取父结构信息:

interface IMessage {
}

interface IChannel {
}

abstract class AbstaractChannelMessage implements IMessage, IChannel {
}

class CloudMessage extends AbstaractChannelMessage implements IMessage, IChannel {
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = CloudMessage.class;
        System.out.println("【获取包的名称】:" + clazz.getPackage().getName());
        System.out.println("【获取继承父类】:" + clazz.getSuperclass().getName());
        System.out.println("【获取实现接口】:" + Arrays.toString(clazz.getInterfaces()));
    }
}

结果:

【获取包的名称】:com.xzzz.demo
【获取继承父类】:com.xzzz.demo.AbstaractChannelMessage
【获取实现接口】:[interface com.xzzz.demo.IMessage, interface com.xzzz.demo.IChannel]

  之所以通过反射可以获取这些信息,主要是由于Class类拥有了“*.class" 二进制数据的分析能力,它实际上是根据二进制的结构文件动态获取的内容。

2. 反射调用构造方法

  在一个类之中会提供有大量的构造方法出现,那么所有的构造方法都可以利用反射来进行获取,在Class类里面定义有如下的构造方法获取的操作:

No. 方法名称 类型 描述
01 public Constructor getConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException 方法 根据指定的参数类型获取指定的构造方法(public)
02 public Constructor<?>[] getConstructors() throws SecurityException 方法 获取类所有的构造方法(public)
03 public Constructor getDeclaredConstructor(Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException 方法 根据指定的参数类型获取指定的构造方法(任意类型)
04 public Constructor<?>[] getDeclaredConstructors() throws SecurityException 方法 获取类所有的构造方法(任意类型)

  结构图如下图所示:Java反射--反射与类操作

获取构造信息:

class CloudMessage {
    protected CloudMessage(String str){}
    private CloudMessage(){}
    CloudMessage(int a){}
    public CloudMessage(String str,int a){}
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = CloudMessage.class;
        System.out.println("【获取部分构造】"+Arrays.toString(clazz.getConstructors()));
        System.out.println("【获取全部构造】"+Arrays.toString(clazz.getDeclaredConstructors()));
    }
}

结果:

【获取部分构造】[public com.xzzz.demo.CloudMessage(java.lang.String,int)]
【获取全部构造】[public com.xzzz.demo.CloudMessage(java.lang.String,int), com.xzzz.demo.CloudMessage(int), private com.xzzz.demo.CloudMessage(), protected com.xzzz.demo.CloudMessage(java.lang.String)]

  此时可以直接获取本类的全部构造方法,如果要想获取父类构造只需要编写如下代码即可:clazz.getSuperclass().getConstructors()
  在以后编写反射的过程里面,构造方法不一定只使用public来定义,  所以获取的方法应该采用“getDeclaredConstructors()”。
  但是获取构造并不意味着要进行简单的输出,这种获取信息的操作只在开发工具中比较常见,但是获取Constructor最大的意义实际上在于其可以进行反射构造调用,提供有如下的方法:

  • public T newInstance() throws InstantiationException, IllegalAccessException

  这个类中提供有一个newlnstance()方法,这个方法的主要功能是可以调用指定的构造进行对象实例化处理。

调用构造:

class Ball {
    private String name;
    private double price;

    public Ball(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Ball{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Ball.class;//获取反射对象
        Constructor<?> con = clazz.getDeclaredConstructor(String.class,double.class);
        Ball ball = (Ball) con.newInstance("aaaa",1.2);
        System.out.println(ball);
    }
}

结果:

Ball{name=‘aaaa’, price=1.2}

  以上的操作是在JDK 1.9 之后官方推荐的做法,无参构造为Class保留。在JDK 1.8 及以前的版本里面,关于反射对象的实例化操作实际上提供了两个不同的操作方法:

  • Class类: public T newInstance() throws InstantiationException, IllegalAccessException;默认调用无参构造,如果类中没有无参构造则将抛出异常,JDK 1.9后就直接修改为Constructor类调用;
  • Constructor 类: public T newInstance(Oject… initargs) throws InstantiationException, llgalAccessException,
    IllegalArgumentException, InvocationTargetException;

3. 反射调用方法

  类中构造方法是在对象实例化的时候调用一-次,但是有了对象之后就可以进行普通方法的多次调用,那么在Class类里面同样定义了可以获取类中方法实例的操作。

  • 获取本类全部的方法:public Method[] getDeclaredMethods() throws SecurityException
  • 获取本类一个指定类型的Method实例:public Method getDeclaredMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException
  • 获取所有的public方法(包括父类):public Method[] getMethods() throws SecurityException
  • 获取一个指定类型的方法(包括父类):public Method getMethod(String name, Class<?>… parameterTypes) throws NoSuchMethodException, SecurityException

  当获取了一个方法之中将以Method类的实例进行该方法的描述,而此类的定义如下:

  • public final class Method extends Executable

  这个类的定义与之前的Construtor类是完全一样的, 此类的继承结构如下。Java反射--反射与类操作

获取方法信息:

class Ball {
}


public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Ball.class;//获取反射对象
        Method methods [] = clazz.getMethods();
        for (int i = 0;i<methods.length;i++){
            System.out.print(Modifier.toString(methods[i].getModifiers()));
            System.out.print(" "+methods[i].getReturnType().getSimpleName());
            System.out.print(" "+methods[i].getName() + " (");
            Class<?> params [] = methods[i].getParameterTypes();//获取所有参数
            for (int j = 0;j<params.length;j++){
                System.out.print(params[j].getSimpleName() + " arg-" + j);
                if (j<params.length-1){
                    System.out.print(" , ");
                }
            }
            System.out.print(")");
            Class<?> [] exps = methods[i].getExceptionTypes();//获取异常信息
            if (exps.length>0){
                System.out.print(" thorws ");
            }
            for (int j = 0;j<exps.length;j++){
                System.out.print(exps[j].getSimpleName());
                if (j<params.length-1){
                    System.out.print(" , ");
                }
            }
            System.out.println();
        }
    }
}

结果:

public final native void wait (long arg-0) thorws InterruptedException
public final void wait (long arg-0 , int arg-1) thorws InterruptedException ,
public final void wait () thorws InterruptedException
public boolean equals (Object arg-0)
public String toString ()
public native int hashCode ()
public final native Class getClass ()
public final native void notify ()
public final native void notifyAll ()

  此时的确已经获取了类中的所有的public方法,但是这些方法是直接通过Method类中的toString0方法得到的。
  在以后如果配置了任何的第三方工具包,各个开发工具之所以可以进行代码的检测以及方法的获取,就是由于反射机制的支持(二进制数据流的解析处理),但是对我们开发人员来讲,真正有意义的代码在于Method类中提供的反射调用方法:

  • public Object invoke(Object obj, Object… args) throws llegalAccessException, IlegalArgumentException, Invocation TargetException

  invoke()可以直接依据Object类的实例化对象(不一定是具体类型)实现反射方法调用。

用反射调用setter、getter方法:

定义一个类:

package com.xzzz.vo;

public class Ball {
    private String brand;

    public String getBrand() {
        return this.brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}

定义一个工具类:

package com.xzzz.demo;

public class StringUtil {
    private StringUtil() {
    }

    public static String initcap(String str) {
        if (str == null || "".equals(str)) {
            return str;
        }
        if (str.length() == 1) {
            return str.toUpperCase();
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

反射调用方法:

package com.xzzz.demo;

import java.lang.reflect.Method;

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        String fieldName = "brand";//描述要操作的属性
        String fieldValue = "我是一个品牌";//属性的内容
        Class<?> clazz = Class.forName("com.xzzz.vo.Ball");//获取类对象
        //不管使用反射还是具体的类型,一定要有实例化对象
        Object obj = clazz.getDeclaredConstructor().newInstance();
        //此时对于setter方法的参数类型无法动态获取。先固定一个String.class
        Method setMethod = clazz.getDeclaredMethod("set" + StringUtil.initcap(fieldName), String.class);
        setMethod.invoke(obj, fieldValue);//等价于实例化对象.setBrand(fielValue)
        Method getMethod = clazz.getDeclaredMethod("get" + StringUtil.initcap(fieldName));
        Object returnObject = getMethod.invoke(obj);
        System.out.println(returnObject);
    }
}

结果:

我是一个品牌

  此时的程序实现了方法的反射调用,同时也就解释了为什么简单Java类中的命名要有setter、getter 规则了。

4. 反射调用成员

  类结构里面包含的主要是三个组成:构造、方法、成员属性,那么对于所有的成员属性也是可以通过反射来实现调用的,在Class类里面和Field里面提供有如下的与成员有关的操作方法:

No. 方法名称 类型 描述
01 public Field[] getFields() throws SecurityException 方法 获取所有继承来的public成员
02 public Field getField(String name) throws NoSuchFieldException, SecurityException 方法 获取一个指定类型的成员
03 public Field[] getDeclaredFields() throws SecurityException 方法 根据指定的参数类型获取本类定义的全部的成员
04 public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException 方法 获取一个本类定义成员
05 public Class<?> getType() 方法 获取属性类型
06 public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException 方法 获取属性内容
07 public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException 方法 设置属性内容
08 public void setAccessible(boolean flag) 方法 取消封装

  类结构如下图所示:Java反射--反射与类操作

调用类中的属性:

public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = ball.class;
        Object obj = clazz.getDeclaredConstructor().newInstance();
        Field brandField = clazz.getDeclaredField("brand");//获取属性
        brandField.setAccessible(true);//取消封装
        System.out.println("【成员类型】"+brandField.getType().getName());
        brandField.set(obj,"我是一个品牌");//等价于对象.brand = "我是一个品牌"
        System.out.println(brandField.get(obj));//等价于 System.out.println(对象.brand)
    }
}

结果:

【成员类型】java.lang.String
我是一个品牌

5. UnSafe工具类

  为了进一步进行反射操作的支持扩充,在Java里面有一个sun.misc.Unsafe类,这个类主要功能是可以通过反射来获取对象,并且打破JVM固定的对象使用流程(因为其底层直接利用C++进行数据的读取),这个类可以在没有实例化对象的时候进行类中方法的调用。

  • 构造方法: private Unsafe() {}
  • 常量: private static final Unsafe theUnsafe = new Unsafe();

  那么此时应该通过类内部定义的“theUnsafe"常量对象来进行Unsafe类的调用。

调用类中方法:

class Singleton{
    private Singleton(){
        System.out.println("Singleton【构造调用】");
    }

    public void print(){
        System.out.println("www.mldn.cn");
    }
}
public class JavaAPIDemo {
    public static void main(String[] args) throws Exception {
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);//取消内封装
        Unsafe unsafe =(Unsafe) unsafeField.get(null);
        Singleton instance = (Singleton) unsafe.allocateInstance(Singleton.class);
        instance.print();
    }
}

结果:

www.mldn.cn

  现在可以发现利用Unsafe类可以打破已有的JVM中关于对象的使用的模型,可以在没有实例化对象的时候直接调用类中所提供的普通方法,但是这样的操作表示所有的处理全部由程序自已完成,而所有的代码不受到JVM的控制,也就意味着所有的垃圾回收机制就将失效
  讲解意义:UnSafe只是提供了一种底层的直接进行对象操作的支持,与实际的开发意义不大。如果在以后笔试的过程里面要求你编写单例设计模式的时候,请注意如下几点:要使用懒汉式单例设计、要针对于数据的同步做出处理、补充上利用Unsafe可以绕过对象的实例化机制,直接调用类中的方法。