反射

什么是反射

    反射库是一个具有丰富功能的工具集,能编写能够动态操纵Java代码的程序。而能够分析类并能把类的各种信息映射成对象的程序称之为反射。这个分析能力包括以下功能:

  • 能获取一个类的所有属性或者方法
  • 能够调用任意一个对象的属性和方法

反射的原理

    反射依赖的是Class对象,而Class对象可以说是由JVM自动生成的,而反射就是利用JVM生成的Class对象反向获取类信息。例如我们创建一个Employee类,JVM生成Class对象流程如下:

  1. 执行程序动态生成class文件,一个类只可能对应一个.class文件。但由于一个类经常会引用到另一个类,所以可能产生多个类的.class文件;
  2. JVM在本地磁盘寻找.class文件,并将其加载到JVM内存当中;
  3. JVM自动为.class文件生成Class对象。并且尽管一个程序多次运行,也会伴随着多次Class对象属性的变动,但肯定与第一次生成的Class对象是同一个,都存储的是Employee类信息。

Class类

    Class和普通类一样,如Employee类对象表示的是一个特定雇员的属性,而Class对象就可以类比为一个特定类的属性,它可以表示一个类、一个接口甚至是一个基本类型,通俗点讲,也就是这三种类型都具有自己的Class对象。

    那么我们想通过反射获取一个类的一些信息,首先就必须获得这个类的Class对象。JavaApi提供三种方式获取Class对象。

Class对象三种获取方式

1. 调用Object类下的getClass()方法

public static void main(String[] args) {

    Integer integer = 1;
    System.out.println(integer.getClass());
    
    }
    
输出结果:
class java.lang.Integer

2. 调用Class类的静态方法forName(“类名或者接口名”)

public static void main(String[] args) {

    String s = "java.lang.Integer";

    try {
        System.out.println(Class.forName(s));
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

输出结果:
class java.lang.Integer

forName()入参必须为类名或者接口名,否则会抛出异常。

3. 类名.class直接获取

    任何数据类型都有一个静态的Class属性,直接执行类名.class即可获得Class对象。(个人觉得使用第三种方式最为方便,即不像第一种需要一个对象引用,也不像第二种需要知道类名,而基本类型无类名)

反射的主要作用

1. 检查类的结构

    检查类结构是反射机制最重要的内容,实现此功能除了适用java.lang包下的Class类之外,还需要使用java.lang.reflect包中的三个类:Field、Method和Constructor。这三个类分别描述的是类的域、方法和构造器,并且这三个类并不是一个对象就包含了一个类的所有域或者是所有方法,而是每一个对象仅仅代表的只是一条数据。例如Field类,一个类有多少个域就会产生多少个Field对象,所以我们通过Class类获取到的是Field[]数组,数组中的数据则表示的是类的域。

image

实例:

    public static void main(String[] args) {

        //获取Integer类的所有域
        Field[] fields = Integer.class.getDeclaredFields();
        System.out.println("Integer类所有域如下:");
        for(Field field : fields)
            System.out.println(field);

        System.out.println();


        //获取Integer类所有构造方法
        Constructor[] constructors = Integer.class.getConstructors();
        System.out.println("Integer类所有构造方法如下: ");
        for (Constructor constructor : constructors)
            System.out.println(constructor);

        System.out.println();

        //获取Integer类所有方法
        Method[] methods = Integer.class.getDeclaredMethods();
        System.out.println("Integer类所有普通方法如下: ");
        for(Method method : methods)
            System.out.println(method);

    }

输出结果如下:

Integer类所有域如下:
public static final int java.lang.Integer.MIN_VALUE
public static final int java.lang.Integer.MAX_VALUE
public static final java.lang.Class java.lang.Integer.TYPE
static final char[] java.lang.Integer.digits
static final char[] java.lang.Integer.DigitTens
static final char[] java.lang.Integer.DigitOnes
static final int[] java.lang.Integer.sizeTable
private final int java.lang.Integer.value
public static final int java.lang.Integer.SIZE
public static final int java.lang.Integer.BYTES
private static final long java.lang.Integer.serialVersionUID

Integer类所有构造方法如下: 
public java.lang.Integer(int)
public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException

Integer类所有普通方法如下: 
public static int java.lang.Integer.numberOfLeadingZeros(int)
public static int java.lang.Integer.numberOfTrailingZeros(int)
public static int java.lang.Integer.bitCount(int)
public boolean java.lang.Integer.equals(java.lang.Object)
public static java.lang.String java.lang.Integer.toString(int,int)

......太多了,此处省略


用于类结构检查的主要方法整理如下:

  • Class类中
方法名功能描述
Field[] getFields()返回该类及其超类的公有域到Filed数组
Field[] getDeclaredFields()返回该类的全部域,不包含超类的域
Method[] getMethods()返回包含超类继承来的所有公有方法
Method[] getDeclaredMethods()返回该类所有方法,不包含超类方法
Constructor<?>[] getConstructors()返回该类及其超类所有公有构造器
Constructor<?>[] getDeclaredConstructors()返回该类所有构造器
  • Field、Method、Constructor类中
方法名功能描述
Class getDeclaringClass()返回当前所描述的类的Class对象
int getModifiers()返回一个用于描述构造器、方法或者域的修饰符的整型tag值,需要适用Modifier类中方法对此tag值进行解析
String getName()返回一个用于描述方法、构造器或者是域的字符串
Class<?>[] getParameterTypes()返回方法参数类型的Class对象数组,该方法只存在于Constructor和Method中,Field代表域,没有参数可言
Class<?> getReturnType()返回一个用于描述返回类型的Class对象,只存在与Method中,因为域和构造都没有返回值之说

补充:Modifier是配合int getModifiers() 使用的类,getModifiers()方法只是返回一个能代表权限修饰符的int值,而我们要知道或者判断这个值代表的权限的意思,可以适用Modifier类中的静态方法来判断。主要方法如下所示:

方法名功能描述
static String toString(int mod)返回对应modifiers值对应的修饰符的字符串表示
static boolean isPublic(int mod)包括以下均是判断是否是相应类型
static boolean isPrivate(int mod)
static boolean isProtected(int mod)

2. 运行时使用反射分析对象

    Field描述的是一个类的域,它提供方法对域的操作是非常强大的,甚至是非常可怕的。我们可以通过getDeclaredField(“域的字符串key”)来获取该Class对象所描述的类的任何一个域,即Field对象,更可怕的是它能修改一个域的值,包括采用private修饰的私有域,当然不可能修改静态域,毕竟静态常量域是属于类而不是属于某一个对象。如下所示:

    public static void main(String[] args) {

        ArrayList<String> arrayList  = new ArrayList<>();
        arrayList.add("a");
        arrayList.add("b");

        try {
            //拿到当前ArrayList对象的size
            Field f = arrayList.getClass().getDeclaredField("size");

            f.setAccessible(true);

            System.out.println(f.get(arrayList));

            f.set(arrayList,10);
            System.out.println(f.get(arrayList));

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    
输出结果:
2
10

    一个类的私有数据是只有本类的方法能访问和修改,外部的类若要修改或者访问一个类的私有域,可能只能通过set或者get更或者是有参构造来进行操作,而反射机制则可以直接逾越这条“鸿沟”,直接调用setAccessiable(true)来覆盖私有域的私有权限,使其可以被访问并能够被修改。由上述代码见,添加两个元素之后,ArrayList的size将会变为2,如果未采用setAccessiable(true)方法来修改权限的话,第一个System打印都将会抛出异常,更别说试图取修改size的值为10。

3. 编写泛型数组,实现数组copy

    泛型数组的说法实际上是一种抽象的说法,使用反射机制来改良数组的copy实际上根本就没有用到泛型参量,而是用到了Class类的getComponentType()方法和java.lang.reflect.Array类的getLength()方法和newInstance(componentType , newLength)等方法。在没出现反射机制之前时,数组的克隆可能是直接创建一个同等大小的Object数组,然后调用System.arraycopy()方法来进行copy元素。如下所示:

public class Demo {
    
    public Object[] copyOfArray(Object[] objects, int newLength) {
        Object[] objects1 = new Object[newLength];
        System.arraycopy(objects, 0, objects1, 0, newLength);
        return objects1;
    }

    public static void main(String[] args) {

        String[] strings = new String[2];
        strings[0] = "a";
        strings[1] = "b";
        String[] objects = (String[]) new Demo().copyOfArray(strings, strings.length);

    }

}

运行结果:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
	at com.actchen.controller.Demo.main(Demo.java:26)

    我们会发现,采用这种方式进行克隆,理论上是可以将数组完整的克隆下来,但当我们在将克隆好的数组强制转换为原来的类型时,发现这已经是不可能事件了,java也会抛出类型转换异常。而反射是怎么解决原始数组克隆所产生的问题的呢?从根本上的性质来说,反射就是避免了在clone时创建一个新的Object数组。我们都知道,方法的形参只是一个引用,它只是指向的传入的实参对象地址空间,当我们传入Stirng类型的数组时,虽然copyOfArray()方法采用了Object类型的引用来接收,但数组的本质没有发生变化。反射克隆数组就是利用String数组传过来的对象仍然是String的特点来进行克隆。克隆步骤如下:

  • 获取原数组的class对象
  • 根据class对象获取数组的类型
  • 采用Array.newInstance()方法创建一个新的数组对象
  • System.arraycopy()进行数组元素的copy

    与原始的clone方法比较,最大的特点就是采用Array.newIntstance()方法创建了一个与原数组类型一模一样的数组,然后再将数组元素进行copy,这样就完美的避免了数组的类型发生改变。代码实例:

public class Demo {

    public Object copyOfArray(Object[] objects, int newLength) {
        Class cl = objects.getClass();
        Class arrayType = cl.getComponentType();
        System.out.println(arrayType);
        Object newArray = Array.newInstance(arrayType, newLength);
        System.arraycopy(objects, 0, newArray, 0, newLength);
        return newArray;

    }

    public static void main(String[] args) {

        String[] strings = new String[2];
        strings[0] = "a";
        strings[1] = "b";

        String[] strings1 = (String[]) new Demo().copyOfArray(strings, strings.length);

        for (String string : strings1)
            System.out.println(string);

    }

}

输出结果:
class java.lang.String
a
b

4. 调用任意方法

    Method类为反射提供了一个可以调用方法的方法invoke(),它允许调用当前包装在Method中的方法。
invoke()方法如下:

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {}

代码实例:

public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);

    try {

        //获取add方法的method对象
        Method method = arrayList.getClass().getMethod("add", Object.class);
        method.invoke(arrayList, 123);
        method.invoke(arrayList, "str");

        for (Object o : arrayList)
            System.out.println(o);

    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
            e.printStackTrace();
    } catch (InvocationTargetException e) {
            e.printStackTrace();
    }

}

输出结果:
1
123
str

    invoke(Object obj , Object… args)方法中第一个参数对应的是普通方法中的隐式参数,在原本的方法之中,默认隐式参数为this。Object… args对应的是方法中的显示参数。反射机制中的Method类会提供getMethod()方法根据方法签名来获得一个Method对象,并且可覆盖方法中的参数类型。如上述代码所示,ArrayList被定义为Integer类型的集合,按照正常的编译流程来说,如果我们强制为add()方法传参数为String类型,很显然在编译期间都没有办法通过泛型检查。而发射则完全绕过了泛型检查,只要将方法参数定义为Object类型或者是被添加类型的公有父类,我们就可以在一个Integer类型的集合存入一个String类型的对象,这就是反射的魅力所在。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值