注解与反射

JAVA学习网 2020-11-07 17:45:17

注解与反射

注解(Java.Annotation)

注解入门

什么是注解

  • Annotation是从JDK5.0引入的技术
  • Annotation的作用:
    • 对程序作出解释
    • 可以被其他程序(如:编译器等)读取(使用反射机制)
  • Annotation的格式
    • 以“@注解名”在代码中存在,还可以添加一些参数值
  • Annotation的使用
    • 注解有检查和约束的作用
    • 可以附加在package,class,method,field等上面,相当于给它们添加了额外的辅助信息,可以通过反射机制来实现对这些元数据的访问

内置注解

java中最常见的三种注解

  • @Overide:定义在java.lang.Override中,此注解只使用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
  • @Deprecated:定义在java.lang.Deprecated中,此注解可以用于修饰方法,属性,类,表示不推荐使用这样的元素,因为它很危险或者存在更好的选择
  • @SuppressWarnings:定义在java.lang.SuppressWarmings中,用来抑制编译时的警告信息。与前两个注解有所不同,你需要添加一个参数才能正确的使用,这些参数是已经定义好了的,可以选择性的使用
    • @SuppressWarnings("all")
    • @SuppressWarnings("unchecked")
    • @SuppressWarnings(value={"unchecked", "deprecation"})
    • ......

元注解

元注解就是负责注解其他注解的注解。Java定义了4个标准的meta-annotation类型,被用来提供对其他Annotation类型做说明,这些类型和它们所支持的类在java.lang.annotation包中

  • @Target:用于描述注解的使用范围(即:该注解可以用在哪些地方)
    • @Target(ElementType.TYPE)——接口、类、枚举、注解
    • @Target(ElementType.FIELD)——字段、枚举的常量
    • @Target(ElementType.METHOD)——方法
    • @Target(ElementType.PARAMETER)——方法参数
    • @Target(ElementType.CONSTRUCTOR) ——构造函数
    • @Target(ElementType.LOCAL_VARIABLE)——局部变量
    • @Target(ElementType.ANNOTATION_TYPE)——注解
    • @Target(ElementType.PACKAGE)——包
  • @Retention:表示需要在什么级别保存该注解信息,用于描述注解的生命周期
    • @Retention(RetentionPolicy.SOURCE)
    • @Retention(RetentionPolicy.CLASS)
    • @Retention(RetentionPolicy.RUNTIME)
    • 生命周期长度:SOURCE < CLASS < RUNTIME,前者能作用的地方后者一定也能作用。
      • SOURCE:这种类型的注解只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。如果只是做一些检查性的操作,比如 @Override@SuppressWarnings,则可选用 SOURCE 注解
      • CLASS:这种类型的注解编译时被保留,默认的保留策略,在class字节码文件中存在,但JVM将会忽略,运行时无法获得。如果要在编译时进行一些预处理操作,比如生成一些辅助代码(为字段添加setter方法),就用 CLASS注解
      • RUNTIME:这种类型的注解将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解
  • @Documented:说明该注解将被包含在Javadoc中
    • @Documented用于描述注解应该被作为被标注的程序成员的公共API,因此可以被Javadoc此类的工具文档化。@Documented是一个标记注解,没有参数成员
  • @Inherited:说明子类可以继承父类中的该注解
    • @Documented一样是一个标记注解,@Inherited阐述了某个被标注的注解是被继承的。如果一个使用了@Inherited修饰的注解被用于一个class,则这个注解将被用于该class的子类

自定义注解

使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口

  • @interface用来声明一个注释,格式:public @interface 注释名{定义内容}
  • 只能用public或者默认(default)这两个访问修饰符来修饰
  • 注解参数的可支持数据类型:
    • 基本数据类型(int,float,boolean,byte,double, char,long,short)
    • String类型
    • Class类型
    • enum类型
    • Annotation类型
    • 以上所有类型的数组
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,参数名通常为value
  • 注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null,使用空字符串或0作为默认值是一种常用的做法,可以用来表现这个元素的存在或缺失状态
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Documented;

/**
 * 用户名注解
 * @author zerocode
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserName 
{
    String value() default "";
}

反射(Java.Reflection)

Reflection(反射)允许程序在执行期间借助Reflection API取得任何类的内部信息(包括private权限修饰符修饰的方法和属性),并能直接操作任意类的内部属性及方法

在加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。

class字节码文件 -> Class对象

Class对象的由来是将class字节码文件读入内存,并为之创建一个Class对象。

什么是反射

借助类加载产生的Class对象(其中有类的信息)来获知类的结构(成员变量,方法,构造方法,等信息)各个部分,映射成一个个的对象,拿到这些对象后可以做一些事情。

Java反射的优点和缺点

  • 优点
    • 在运行时获得类的各种信息,能够让我们很方便的创建灵活的代码。
  • 缺点
    • 反射会消耗一定的系统资源,如果不用动态的获取一个对象就不用反射。
    • 反射调用方法是可以忽略权限检查,导致安全问题。

反射的用途

  • 反编译:.class -> .java

  • 通过反射机制访问对象java对象的属性,方法,构造方法等

  • 一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码,反射在日常的开发中用到的不多,但是咱们还必须搞懂它,因为搞懂了反射以后,可以帮助理解框架的一些原理。所以说有一句很经典的话:反射是框架设计的灵魂。为了保证框架的通用性,他们可能需要配置加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射,通过运行时动态加载需要加载的对象。

    例如:在使用Strut2框架的开发过程中,一般使用struts.xml里去配置Action,比如

    <action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute">   
        <result>/shop/shop-index.jsp</result>           
        <result name="error">login.jsp</result>       
    </action>
    

反射机制中常用的类

  • Java.lang.Class;
  • Java.lang.reflect.Constructor;
  • Java.lang.reflect.Field;
  • Java.lang.reflect.Method;
  • Java.lang.reflect.Modifier;

Class类

Java运行时系统时钟为所有对象维护一个RTTI(运行时类型标识,Run-Time Type Identification),会跟踪每个对象所属的类。虚拟机利用该信息选择要执行的正确方法。

每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。

每一个类都有一个Class对象,程序运行时每当加载一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。

Employee e;
...
Class c = e.getClass();

就像Employee对象e描述一个特定员工的属性一样,一个Class对象c1描述一个特定类的属性(特定类的类名,特定类包含的字段,特定类包含的方法等)。

从Java内存来理解Class类

Java内存

由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。

Java程序执行流程:

1

如图所示,Java源码文件被Java编译器编译为字节码文件,然后由JVM中的类加载器加载各个类的字节码文件,加载后交由JVM执行引擎执行。执行过程中,JVM用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间被称为运行时数据区,也就是常说的Java内存。因此,在Java中我们常常说的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)

Java内存分为几个部分

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

2

名称 特征 作用
程序计数器 占用内存小,线程私有,生命周期与线程相同 大致为字节码行号指示器
虚拟机栈 线程私有,生命周期与线程相同,使用连续的内存空间 Java 方法执行的内存模型,存储局部变量表、操作栈、动态链接、方法出口等信息
线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 保存对象实例(包括Class对象),所有对象实例(包括数组)都要在堆上分配
方法区 线程共享,生命周期与虚拟机相同,可以不使用连续的内存地址 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,包含了所有的Class信息和static变量
运行时常量池 方法区的一部分,具有动态性 存放字面量及符号引用
类加载的过程

当程序主动使用某个类,如果类未被加载到内存中,系统会通过三个步骤来对类进行初始化。

  1. 类的装载(Load)

    将class字节码文件内容(类的描述信息)加载到方法区中,并将这些静态数据转换为方法区运行时数据结构,然后在堆中生成一个代表这个类的Class对象

  2. 类的链接(Link)

    将类的二进制代码合并到JVM的运行状态中的过程

    • 验证:确保加载的类信息符合JVM规范,有没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置默认初始值(并没有开始赋值,仅设置为默认值)的阶段,这些空间都将在方法区中进行分配。另外如果是final 修饰的常量,此时一并直接赋值
    • 解析:运行时常量池内的符号引用(常量)替换为直接引用(地址)的过程
  3. 类的初始化(Initialize)

    • 执行类构造器(类构造器:构造类信息,不是构造类对象的构造器)<clinit>()方法的过程。<clinit>()方法是有编译器自动收集类中所有类变量(static)的赋值动作静态初始化块中的语句合并产生的。
    • 初始化一个类的时候,如果发现父类还没有初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁和同步
    • <clinit>() 对于类或者接口并不是必须的,如果一个类没有静态语句块也没有对变量的赋值操作编译器可以不为这个类生成<clinit>()方法
类加载的时机

什么时候类加载 :第一次需要使用类信息时加载。

类加载的原则:延迟加载,能不加载就不加载。

触发类加载的几种情况

  • 虚拟机启动,先加载main方法所在的类

  • 当第一次new一个类的对象时

  • 调用这个类的静态成员(final常量除外)和静态方法

    通过子类调用父类的静态成员时,只会加载父类而不会加载子类

  • 调用java.lang.reflect包的方法对类进行反射调用

  • 加载一个类,其父类没有被加载,优先加载其父类

package com.zerocode.reflection;

public class Test 
{

    static 
    {
        System.out.println("加载Test类");
    }

    public static void main(String[] args)
    {
        System.out.println("进入main方法");

        System.out.println("通过Cat调用SuperCat类的静态成员");
        var test = Cat.superCatStaticField1;

        System.out.println("开始new一个Dog类的对象");
        new Dog();

        System.out.println("开始获取Cat的Class对象");
        try 
        {
            Class.forName("com.zerocode.reflection.Cat");
        } 
        catch (ClassNotFoundException e) 
        {
            System.out.println("没有找到Cat类");
        }

        System.out.println("结束main方法");
    }

}

class SuperCat {

    public static int superCatStaticField1 = 1;
    static 
    {
        System.out.println("加载SuperCat类");
    }

}

class Cat extends SuperCat {

    static 
    {
        System.out.println("加载Cat类");
    }

}

class SuperDog {

    static 
    {
        System.out.println("加载SuperDog类");
    }

}

class Dog extends SuperDog {

    static 
    {
        System.out.println("加载Dog类");
    }

}

/* Output:
加载Test类
进入main方法
通过Cat调用SuperCat类的静态成员
加载SuperCat类
开始new一个Dog类的对象
加载SuperDog类
加载Dog类
开始获取Cat的Class对象
加载Cat类
结束main方法
*/

从输出中可以看到,Class对象仅在需要的时候才被加载,static初始化是在类加载的时候进行。

Class.forName的好处就在于,不需要为了获得Class引用而持有该类型的对象,只要通过全限定名就可以返回该类型的一个Class引用

类加载的演示:

package com.zerocode.reflection;

public class Test
{

    static 
    {
        testStaticField1 = 1;
    }
    static int testStaticField1 = 2;

    public static void main(String[] args)
    {
        Cat cat = new Cat();
        System.out.println("输出Test类的静态成员的值:" + Test.testStaticField1);
    }

}

class Cat {

    static int catStaticField1 = 1;

    public int catField1 = 1;

    public Cat()
    {
        catField1 = 2;
    }
}

3

4

5

6

7

8

9

10

11

可以看到在类加载的过程中也有Class类的参与,而使用反射的过程就是通过Class对象反向获取类的信息。若这个类的信息之前没有加载,就会执行该类的类加载。

12

获取Class类对象

  • Object -> getClass
  • 任何数据类型(包括基本的数据类型)都有一个静态class属性
  • 通过Class类的静态方法:forName(String className)
  • 基本类型的包装类有一个TYPE属性
package com.zerocode.reflection;

public class Test
{
    public static void main(String[] args)
    {
        // 第一种获取Class对象的方法
        Cat cat = new Cat();
        Class catClass1 = cat.getClass();

        // 第二种获取Class对象的方法
        Class catClass2 = Cat.class;

        // 第三种获取Class对象的方法
        try
        {
            Class catClass3 = Class.forName("com.zerocode.reflection.Cat");
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }

        // 第四种获取Class对象的方法
        Class intClass1 = Integer.TYPE;
    }
}

class Cat{}

四种方式中最常用的就是第三种。第一种:不常用,一般用在想要去获取该对象的类的名字等信息,例如:System.out.println("The class of " + obj + " is " + obj.getClass().getName());。第二种:需要导入类包。第三种:通过带包名的类路径一个字符串可以传入,也可以写在配置文件中等多种方式。第四种与第二种方式类似,不过是基本类型中特有的。

Class类的常用方法

  • static Class forNmae(String className);

    返回与给定字符串名称的类或接口相关联的Class对象。

  • Object newInstance();

    创建由此Class对象表示的类的新实例(使用无参构造器)

  • Class getSuperClass();

    获取由此Class对象表示的类的父类

  • Class[] getInterface();

    获取由此Class对象表示的类实现的接口

  • ClassLoader getClassLoader();

    获取加载由此Class对象表示的类的类加载器

  • String getName();

    获取Class对象所表示的实体(类,接口,数组类或void)的名称

  • int getModifiers();

    返回此类或接口的修饰符,以整数编码,可以利用Modifier.toString()方法将修饰符打印出来

  • Field[] getFields()Mehtod[] getMethods()Constructors[] getConstructors()方法将分别返回这个类支持的公共字段、方法和构造器数组,其中包括超类的公共成员

  • Field[] getDeclareFileds()Mehtod[] getDeclareMethods()Constructors[] getDeclareConstructors()方法将返回类中声明的全部字段、方法和构造器的数组,其中包括公共成员(public)、私有成员(private)、包成员(default)和受保护成员(protected),但不包括超类的成员

反射的基本使用

使用Class对象创建对应类的实例

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例,使用的是无参构造器来创建对象

    Class<?> c = String.class;
    object str = c.newInstance();
    
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以指定构造器来构造类的实例

    Class<?> str = String.class; // 获取String的Class对象
    Constructor constructor = str.getConstructor(String.class);// 指定形参为String的构造器
    Object obj = constructor.newInstance("hello reflection");// 通过构造器创建实例
    

使用反射分析类的结构

打印一个一个类的全部信息,这个程序根据输入的类名,然后输出类中所有的方法和构造器的签名,以及全部实例字段名。

package com.zerocode.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

public class Test{

    public static void main(String[] args)
            throws ClassNotFoundException
    {
        String className;
        if (args.length > 0)
        {
            className = args[0];
        }
        else
        {
            var in = new Scanner(System.in);
            System.out.print("输入类名(例子:java.util.Date):");
            className = in.next();
        }

        // 获取类的Class对象
        Class cl = Class.forName(className);

        // 打印修饰符部分
        String modifiers = Modifier.toString(cl.getModifiers());
        if (modifiers.length() > 0)
        {
            System.out.print(modifiers + " ");
        }

        // 打印类的名字
        String name = cl.getName();
        System.out.print("class " + name);

        // 打印类的继承关系,如果打印的类是Object或者打印的类的父类为Object则不打印继承关系
        Class superCl = cl.getSuperclass();
        if (superCl != null && superCl != Object.class)
        {
            System.out.print(" extends " + superCl.getName());
        }

        System.out.println();
        System.out.println("{");

        // 分别打印构造器,类方法,字段
        printConstructors(cl);
        System.out.println();
        printMethods(cl);
        System.out.println();
        printField(cl);

        System.out.println("}");
    }

    /**
     * 打印Class对象对应的类的构造器方法
     * @param cl 需要打印构造器方法的CLass对象
     */
    public static void printConstructors(Class cl)
    {
        Constructor[] constructors = cl.getDeclaredConstructors();

        for (Constructor c: constructors)
        {
            System.out.print("    ");

            String modifiers = Modifier.toString(c.getModifiers());
            if(modifiers.length() > 0)
            {
                System.out.print(modifiers + " ");
            }

            String name = c.getName();
            System.out.print(name + "(");

            Class[] paramTypes = c.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++)
            {
                if (i > 0)
                {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }

            System.out.println(");");
        }
    }

    /**
     * 打印Class对象对应的类的方法
     * @param cl 需要打印方法的CLass对象
     */
    public static void printMethods(Class cl)
    {
        Method[] methods = cl.getDeclaredMethods();

        for (Method m: methods)
        {
            System.out.print("    ");

            String modifiers = Modifier.toString(m.getModifiers());
            if (modifiers.length() > 0)
            {
                System.out.print(modifiers + " ");
            }

            // 获取返回值的类型
            Class returnType = m.getReturnType();
            System.out.print(returnType.getName() + " ");

            String name = m.getName();
            System.out.print(name + "(");

            Class[] paramTypes = m.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++)
            {
                if (i > 0)
                {
                    System.out.print(", ");
                }
                System.out.print(paramTypes[i].getName());
            }

            System.out.println(");");
        }
    }

    /**
     * 打印Class对象对应的类的字段
     * @param cl 需要打印字段的CLass对象
     */
    public static void printField(Class cl)
    {
        Field[] fields = cl.getDeclaredFields();

        for (Field f: fields)
        {
            System.out.print("    ");

            String modifiers = Modifier.toString(f.getModifiers());
            if (modifiers.length() > 0)
            {
                System.out.print(modifiers + " ");
            }

            Class type = f.getType();
            System.out.print(type.getName() + " ");

            String name = f.getName();
            System.out.println(name + ";");
        }
    }
}

/* Output:
输入类名(例子:java.util.Date):java.lang.Integer
public final class java.lang.Integer extends java.lang.Number
{
    public java.lang.Integer(java.lang.String);
    public java.lang.Integer(int);

    public static int numberOfLeadingZeros(int);
    public static int numberOfTrailingZeros(int);
    public static int bitCount(int);
    public boolean equals(java.lang.Object);
    public static java.lang.String toString(int);
    public static java.lang.String toString(int, int);
    public java.lang.String toString();
    public static int hashCode(int);
    public int hashCode();
    public static int min(int, int);
    public static int max(int, int);
    public static int reverseBytes(int);
    static int getChars(int, int, [B);
    public volatile int compareTo(java.lang.Object);
    public int compareTo(java.lang.Integer);
    public byte byteValue();
    public short shortValue();
    public int intValue();
    public long longValue();
    public float floatValue();
    public double doubleValue();
    public static java.lang.Integer valueOf(int);
    public static java.lang.Integer valueOf(java.lang.String, int);
    public static java.lang.Integer valueOf(java.lang.String);
    public static java.lang.String toHexString(int);
    public static java.lang.Integer decode(java.lang.String);
    public static int compare(int, int);
    public static int reverse(int);
    static int stringSize(int);
    public static long toUnsignedLong(int);
    public static int parseInt(java.lang.String);
    public static int parseInt(java.lang.String, int);
    public static int parseInt(java.lang.CharSequence, int, int, int);
    public static int sum(int, int);
    public static int compareUnsigned(int, int);
    private static java.lang.String toStringUTF16(int, int);
    public static java.lang.String toUnsignedString(int, int);
    public static java.lang.String toUnsignedString(int);
    public static java.lang.String toOctalString(int);
    public static java.lang.String toBinaryString(int);
    private static java.lang.String toUnsignedString0(int, int);
    static void formatUnsignedInt(int, int, [C, int, int);
    static void formatUnsignedInt(int, int, [B, int, int);
    private static void formatUnsignedIntUTF16(int, int, [B, int, int);
    public static int parseUnsignedInt(java.lang.String, int);
    public static int parseUnsignedInt(java.lang.CharSequence, int, int, int);
    public static int parseUnsignedInt(java.lang.String);
    public static java.lang.Integer getInteger(java.lang.String, java.lang.Integer);
    public static java.lang.Integer getInteger(java.lang.String, int);
    public static java.lang.Integer getInteger(java.lang.String);
    public static int divideUnsigned(int, int);
    public static int remainderUnsigned(int, int);
    public static int highestOneBit(int);
    public static int lowestOneBit(int);
    public static int rotateLeft(int, int);
    public static int rotateRight(int, int);
    public static int signum(int);

    public static final int MIN_VALUE;
    public static final int MAX_VALUE;
    public static final java.lang.Class TYPE;
    static final [C digits;
    static final [B DigitTens;
    static final [B DigitOnes;
    static final [I sizeTable;
    private final int value;
    public static final int SIZE;
    public static final int BYTES;
    private static final long serialVersionUID;
}

*/

使用反射在运行时分析对象

同一个类型的几个类型中最大的不同就是其中字段的值的不同

要做到这一点,关键方法是Field类中的get方法。如果f是一个Field类的对象f.get(obj)将返回一个对象,为对象obj的中f所表示的字段的字段值

package com.zerocode.reflection;

import java.lang.reflect.Field;

public class Test{

    public static void main(String[] args) 
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException 
    {
        // new一个Cat对象
        Cat cat = new Cat("Tom", 12);
        // 获取Cat类的Class对象
        Class catClass = Class.forName("com.zerocode.reflection.Cat");
        // 从Cat类的Class对象中获取name字段的抽象
        Field name = catClass.getDeclaredField("name");
        // name字段的抽象中有一个get方法可以获取传入的Cat对象中的name字段的值
        Object catName = name.get(cat);
    }
}

class Cat{
    public String name;
    public int age;

    public Cat(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

不仅可以获得值,也可以设置值。调用f.set(obj, value)将把对象obj中的f所表示的字段设置为新值

但是如果f是一个私有字段,那么get和set方法就会抛出一个IlleaglAccessException。Java安全机制允许查看一个对象有哪些字段,但是除非拥有访问权限,否则不允许读写那些字段的值

反射机制默认受限于Java的访问控制的。不过,可以调用Field、Method或Constructor对象的setAccessible方法覆盖Java的访问控制

package com.zerocode.reflection;

import java.lang.reflect.Field;

public class Test{

    public static void main(String[] args) 
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException 
    {
        Cat cat = new Cat("Tom", 12);
        Class catClass = Class.forName("com.zerocode.reflection.Cat");
        Field name = catClass.getDeclaredField("name");
        // 在调用get方法前调用setAccessible方法覆盖Java的访问控制
        name.setAccessible(true);
        Object catName = name.get(cat);
    }
}

class Cat
{
    private String name;
    private int age;

    public Cat(String name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

使用反射调用任意方法

java.lang.reflection.Method有一个invoke方法,允许你调用包装在当前Method对象中的方法。invoke方法的签名是:

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

假设用Method类的ml对象,包装Cat类的setName方法。

Cat cat = new Cat(...);
ml.invoke(cat, "Tom");

如何得到Method对象

  • 调用Class对象的getDeclaredMethods方法,然后搜索返回的Method对象数组,直到发现想要的方法为止

  • 调用Class对象的getMethod方法,提供想要方法的名字,有鉴于可能存在若干个同名的方法,可以提供想要方法的参数类型。

    签名:Method getMethod(String name, Class... parameterTypes);

    Method m1 = Cat.class.getMethod("getName");

    Method m2 = Cat.class.getMethod("setName", String.class);

使用反射动态拓展数组

当一个数组的空间满了之后可以使用下面的代码进行扩展

int[] array = {1, 2, 3};
int[] newArray = new int[array.length * 2];
// 将array的值复制给扩充的数组
System.arraycopy(array, 0, newArray, 0, array.length);

那么有没有通用的办法来扩展任意类型的数组,而不仅只能根据上下文来编写扩展数组的代码

java.lang.reflection.Array类允许动态地创建数组,java.util.Arrays类中的copyOf方法实现就使用了这个类。

package com.zerocode.reflection;

import java.lang.reflect.Array;
import java.util.Arrays;

public class Test{

    public static void main(String[] args)
    {
        String[] names = {"Tom", "Jerry", "Spike"};
        String[] newNames = (String[]) goodCopyOf(names, 5);
        System.out.println(Arrays.toString(newNames));

        int[] ages = {1, 2, 3};
        int[] newAges = (int[]) goodCopyOf(ages, 5);
        System.out.println(Arrays.toString(newAges));

        // 不能强制转型,如果使用强制转型产生异常
        Object[] newObjects = badCopyOf(names, 5);
    }

  	// 仅能扩展对象数组
    public static Object[] badCopyOf(Object[] original, int newLength)
    {
        Object[] newArray = new Object[newLength];
        System.arraycopy(original, 0, newArray, 0, Math.min(original.length, newLength));
        return newArray;
    }
		
  	// 可以扩展任意类型的数组,而不仅是对象数组
    public static Object goodCopyOf(Object original, int newLength)
    {
        Class cl = original.getClass();
        //由于参数声明为Object,所以需要判断original是否为数组类型。
        if (!cl.isArray())
        {
            return null;
        }
        Class componentType = cl.getComponentType();
        int length = Array.getLength(original);
        // java.lang.reflection.Array允许动态的创建数组。
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(original, 0, newArray, 0, Math.min(newLength, length));
        return newArray;
    }
}

使用反射获取注解信息

反射和泛型

反射允许在运行时分析任意对象,如果对象是泛型类的实例,关于泛型类型参数得不到太多信息,因为它们已经被擦除了。

Class类是泛型的,例如:String.class获取的就是Class<String>类的对象。

参数类型十分有用,这是因为它允许Class<T>的方法的返回类型更具有特定性,这样就免除了类型转换。

使用Class<T>参数创建Pair<T>对象
public static <T> Pair<T> makePair(Class<T> cl) throws
        IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    return new Pair<>(cl.newInstance(), cl.newInstance());
}

如果调用makePair(Cat.class)Cat.class将是一个Class<T>对象。makePair的类型变量TCat匹配。编译器可以根据上下文推断出这个方法将返回Pair<Cat>

使用反射获取泛型信息

Java泛型的突出特性之一就是类型擦除。Java泛型仅仅是给编译器javac使用的。当编译完成后,所有和泛型有关的类型全部擦除。

为了通过反射操作泛型类型,可以通过java.lang.reflection包中的接口Type包含了如下子类型。

  • Class类:描述具体类型

  • TypeVariable接口:描述类型变量(如:T extends Comparable<? super T>

  • WildcardType接口:描述通配符(如 ?? extends Number ,或 ? super Integer

  • ParameterizedType接口:描述泛型类或泛型接口(如:Comparable<? super T>

  • GenericArrayType接口:描述泛型数组(如:T[]

参考视频、博客以及书籍

阅读(2378) 评论(0)