小白都能学会的Java注解与反射机制

前言

Java 注解和反射是很基础的 Java 知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override这一个注解。我建议大家还是尽量能在开发中使用注解和反射,有时候使用它们能让你事半功倍,简化代码提高编码的效率。很多优秀的框架都基本使用了注解和反射,在 Spring AOP 中,就把注解和反射用得淋漓尽致。

什么是注解

Java 注解(Annotation)亦叫 Java 标注,是 JDK5.0 开始引入的一种注释机制。 注解可以用在类、接口,方法、变量、参数以及包等之上。注解可以设置存在于不同的生命周期中,例如 SOURCE(源码中),CLASS(Class 文件中,默认是此保留级别),RUNTIME(运行期中)。

注解以@注解名的形式存在于代码中,Java 中内置了一些注解,例如@Override,当然我们也可以自定义注解。注解也可以有参数,例如 @MyAnnotation(value = "陈皮")。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

那注解有什么作用呢?其一是作为一种辅助信息,可以对程序做出一些解释,例如 @Override 注解作用于方法上,表示此方法是重写了父类的方法。其二,注解可以被其他程序读取,例如编译器,例如编译器会对被 @Override 注解的方法检测判断方法名和参数等是否与父类相同,否则会编译报错;而且在运行期可以通过反射机制访问某些注解信息。

内置注解

Java 中有 10 个内置注解,其中 6 个注解是作用在代码上的,4 个注解是负责注解其他注解的 (即元注解),元注解提供对其他注解的类型说明。

注解 作用 作用范围
@Override 检查该方法是否是重写方法。如果其继承的父类或者实现的接口中并没有该方法时,会报编译错误。 作用在代码上
@Deprecated 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。 作用在代码上
@SuppressWarnings 告诉编译器忽略注解中声明的警告。 作用在代码上
@SafeVarargs Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 作用在代码上
@FunctionalInterface Java 8 开始支持,标识一个匿名函数或函数式接口。 作用在代码上
@Repeatable Java 8 开始支持,标识某注解可以在同一个声明上使用多次。 作用在代码上
@Retention 标识这个注解的保存级别,是只在代码中,还是编入 class 文件中,或者是在运行时可以通过反射访问。包含关系 runtime>class>source。 作用在其他注解上,即元注解
@Documented 标记这些注解是否包含在用户文档中 javadoc。 作用在其他注解上,即元注解
@Target 标记某个注解的使用范围,例如作用方法上,类上,属性上等等。如果注解未使用 @Target,则注解可以用于任何元素上。 作用在其他注解上,即元注解
@Inherited 说明子类可以继承父类中的此注解,但这不是真的继承,而是可以让子类 Class 对象使用 getAnnotations() 获取父类被 @Inherited 修饰的注解 作用在其他注解上,即元注解

自定义注解

使用 @interface 关键字自定义注解,其实底层就是定义了一个接口,而且自动继承java.lang.annotation.Annotation接口。

我们自定义一个注解如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
    String value();
}

我们使用命令 javap 反编译我们定义的 MyAnnotation 注解的 class 文件,结果显示如下。虽然注解隐式继承了 Annotation 接口,但是 Java 不允许我们显示通过 extends 关键字继承 Annotation 接口甚至其他接口,否则编译报错。

D:\>javap MyAnnotation.class
Compiled from "MyAnnotation.java"
public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation {
  public abstract java.lang.String value();
}

注解的定义内容如下:

  • 格式为 public @interface 注解名
  • 内部的每一个方法实际是声明了一个参数,方法的名称就是参数的名称。
  • 返回值类型就是参数的类型,而且返回值类型只能是基本类型(int,float,long,short,boolean,byte,double,char),Class,String,enum,Annotation 以及上述类型的数组形式。
  • 如果定义了参数,可通过 default 关键字声明参数的默认值,若不指定默认值,使用时就一定要显示赋值,而且不允许使用 null 值,一般会使用空字符串或者 0。
  • 如果只有一个参数,一般参数名为 value,因为使用注解时,赋值可以不显示写出参数名,直接写参数值。
import java.lang.annotation.*;

/**

  • @Description 自定义注解
  • @Author Mr.nobody
  • @Date 2021/3/30
  • @Version 1.0
    */
    @Target(ElementType.METHOD) // 此注解只能用在方法上。
    @Retention(RetentionPolicy.RUNTIME) // 此注解保存在运行时期,可以通过反射访问。
    @Inherited // 说明子类可以继承此类的此注解。
    @Documented // 此注解包含在用户文档中。
    public @interface CustomAnnotation {
    String value(); // 使用时需要显示赋值
    int id() default 0; // 有默认值,使用时可以不赋值
    }
/**
 * @Description 测试注解
 * @Author Mr.nobody
 * @Date 2021/3/30
 * @Version 1.0
 */
public class TestAnnotation {

    // @CustomAnnotation(value = "test") 只能注解在方法上,这里会报错
    private String str = "Hello World!";

    @CustomAnnotation(value = "test")
    public static void main(String[] args) {
        System.out.println(str);
    }
}

Java8 注解

在这里讲解下 Java8 之后的几个注解和新特性,其中一个注解是 @FunctionalInterface,它作用在接口上,标识是一个函数式接口,即只有有一个抽象方法,但是可以有默认方法。

@FunctionalInterface
public interface Callback<P,R> {
<span class="hljs-keyword">public</span> R <span class="hljs-title function_">call</span><span class="hljs-params">(P param)</span>;

}

还有一个注解是 @Repeatable,它允许在同一个位置使用多个相同的注解,而在 Java8 之前是不允许的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(OperTypes.class)
public @interface OperType {
    String[] value();
}
// 可以理解 @OperTypes 注解作为接收同一个类型上重复 @OperType 注解的容器
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperTypes {
    OperType[] value();
}
@OperType("add")
@OperType("update")
public class MyClass {

}

注意,对于重复注解,不能再通过 clz.getAnnotation(Class<A> annotationClass) 方法来获取重复注解,Java8 之后,提供了新的方法来获取重复注解,即 clz.getAnnotationsByType(Class<A> annotationClass) 方法。

package com.nobody;

import java.lang.annotation.Annotation;

/**

  • @Description

  • @Author Mr.nobody

  • @Date 2021/3/31

  • @Version 1.0
    */
    @OperType("add")
    @OperType("update")
    public class MyClass {

    public static void main(String[] args) {
    Class<MyClass> clz = MyClass.class;
    Annotation[] annotations = clz.getAnnotations();
    for (Annotation annotation : annotations) {
    System.out.println(annotation.toString());
    }

     <span class="hljs-type">OperType</span> <span class="hljs-variable">operType</span> <span class="hljs-operator">=</span> clz.getAnnotation(OperType.class);
     System.out.println(operType);
    
     OperType[] operTypes = clz.getAnnotationsByType(OperType.class);
     <span class="hljs-keyword">for</span> (OperType type : operTypes) {
         System.out.println(type.toString());
     }
    

    }

}

// 输出结果为
@com.nobody.OperTypes(value=[@com.nobody.OperType(value=[add]), @com.nobody.OperType(value=[update])])
null
@com.nobody.OperType(value=[add])
@com.nobody.OperType(value=[update])


在 Java8 中,ElementType 枚举新增了两个枚举成员,分别为 TYPE_PARAMETER 和 TYPE_USE,TYPE_PARAMETER 标识注解可以作用于类型参数,TYPE_USE 标识注解可以作用于标注任意类型 (除了 Class)。

Java 反射机制

我们先了解下什么是静态语言和动态语言。动态语言是指在运行时可以改变其自身结构的语言。例如新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或者结构上的一些变化。简单说即是在运行时代码可以根据某些条件改变自身结构。动态语言主要有 C#,Object-C,JavaScript,PHP,Python 等。静态语言是指运行时结构不可改变的语言,例如 Java,C,C++ 等。

Java 不是动态语言,但是它可以称为准动态语言,因为 Java 可以利用反射机制获得类似动态语言的特性,Java 的动态性让它在编程时更加灵活。

反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法等。类在被加载完之后,会在堆内存的方法区中生成一个 Class 类型的对象,一个类只有一个 Class 对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。

比如我们可以通过Class clz = Class.forName("java.lang.String");获得 String 类的 Class 对象。我们知道每个类都隐式继承 Object 类,Object 类有个getClass()方法也能获取 Class 对象。

Java 反射机制提供的功能

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判断任意一个类具有的成员变量和方法
  4. 在运行时获取泛型信息
  5. 在运行时调用任意一个对象的成员变量和方法
  6. 在运行时获取注解
  7. 生成动态代理
  8. ...

Java 反射机制的优缺点

  • 优点:实现动态创建对象和编译,有更加的灵活性。
  • 缺点:对性能有影响。使用反射其实是一种解释操作,即告诉 JVM 我们想要做什么,然后它满足我们的要求,所以总是慢于直接执行相同的操作。

Java 反射相关的主要 API

  • java.lang.Class:代表一个类
  • java.lang.reflect.Method:代表类的方法
  • java.lang.reflect.Field:代表类的成员变量
  • java.lang.reflect.Constructor:代表类的构造器

我们知道在运行时通过反射可以准确获取到注解信息,其实以上类(Class,Method,Field,Constructor 等)都直接或间接实现了 AnnotatedElement 接口,并实现了它定义的方法,AnnotatedElement 接口的作用主要用于表示正在 JVM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以获取到注解信息。

java.lang.Class 类

在 Java 反射中,最重要的是 Class 这个类了。Class 本身也是一个类。当程序想要使用某个类时,如果此类还未被加载到内存中,首先会将类的 class 文件字节码加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个 Class 类型的对象(Class 对象只能由系统创建),一个类只有一个 Class 对象,这个对象包含了类的结构信息。我们可以通过这个对象看到类的结构。每个类的实例都会记得自己是由哪个 Class 实例所生成的。

通过 Class 对象可以知道某个类的属性,方法,构造器,注解,以及实现了哪些接口等信息。注意,只有 class,interface,enum,annotation,primitive type,void,[] 等才有 Class 对象。

package com.nobody;

import java.lang.annotation.ElementType;
import java.util.Map;

public class TestClass {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {

    <span class="hljs-comment">// 类</span>
    Class&lt;MyClass&gt; myClassClass = MyClass.class;
    <span class="hljs-comment">// 接口</span>
    Class&lt;Map&gt; mapClass = Map.class;
    <span class="hljs-comment">// 枚举</span>
    Class&lt;ElementType&gt; elementTypeClass = ElementType.class;
    <span class="hljs-comment">// 注解</span>
    Class&lt;Override&gt; overrideClass = Override.class;
    <span class="hljs-comment">// 原生类型</span>
    Class&lt;Integer&gt; integerClass = Integer.class;
    <span class="hljs-comment">// 空类型</span>
    Class&lt;Void&gt; voidClass = <span class="hljs-keyword">void</span>.class;
    <span class="hljs-comment">// 一维数组</span>
    Class&lt;String[]&gt; aClass = String[].class;
    <span class="hljs-comment">// 二维数组</span>
    Class&lt;String[][]&gt; aClass1 = String[][].class;
    <span class="hljs-comment">// Class类也有Class对象</span>
    Class&lt;Class&gt; classClass = Class.class;

    System.out.println(myClassClass);
    System.out.println(mapClass);
    System.out.println(elementTypeClass);
    System.out.println(overrideClass);
    System.out.println(integerClass);
    System.out.println(voidClass);
    System.out.println(aClass);
    System.out.println(aClass1);
    System.out.println(classClass);
}

}

// 输出结果
class com.nobody.MyClass
interface java.util.Map
class java.lang.annotation.ElementType
interface java.lang.Override
class java.lang.Integer
void
class [Ljava.lang.String;
class [[Ljava.lang.String;
class java.lang.Class

获取 Class 对象的方法

  1. 如果知道具体的类,可通过类的 class 属性获取,这种方法最安全可靠并且性能最高。Class clz = User.class;
  2. 通过类的实例的 getClass() 方法获取。Class clz = user.getClass();
  3. 如果知道一个类的全限定类名,并且在类路径下,可通过 Class.forName() 方法获取,但是可能会抛出 ClassNotFoundException。Class clz = Class.forName("com.nobody.User");
  4. 内置的基本数据类型可以直接通过类名.Type 获取。Class<Integer> clz = Integer.TYPE;
  5. 通过类加载器 ClassLoader 获取

Class 类的常用方法

  • public static Class<?> forName(String className):创建一个指定全限定类名的 Class 对象
  • public T newInstance():调用 Class 对象所代表的类的无参构造方法,创建一个实例
  • public String getName():返回 Class 对象所代表的类的全限定名称。
  • public String getSimpleName():返回 Class 对象所代表的类的简单名称。
  • public native Class<? super T> getSuperclass():返回 Class 对象所代表的类的父类的 Class 对象,这是一个本地方法
  • public Class<?>[] getInterfaces():返回 Class 对象的接口
  • public Field[] getFields():返回 Class 对象所代表的实体的 public 属性 Field 对象数组
  • public Field[] getDeclaredFields():返回 Class 对象所代表的实体的所有属性 Field 对象数组
  • public Field getDeclaredField(String name):获取指定属性名的 Field 对象
  • public Method[] getDeclaredMethods():返回 Class 对象所代表的实体的所有 Method 对象数组
  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回指定名称和参数类型的 Method 对象
  • myClassClass.getDeclaredConstructors();:返回所有 Constructor 对象的数组
  • public ClassLoader getClassLoader():返回当前类的类加载器

在反射中经常会使用到 Method 的 invoke 方法,即public Object invoke(Object obj, Object... args),我们简单说明下:

  • 第一个 Object 对应原方法的返回值,若原方法没有返回值,则返回 null。
  • 第二个 Object 对象对应调用方法的实例,若原方法为静态方法,则参数 obj 可为 null。
  • 第二个 Object 对应若原方法形参列表,若参数为空,则参数 args 为 null。
  • 若原方法声明为 private 修饰,则调用 invoke 方法前,需要显示调用方法对象的 method.setAccessible(true) 方法,才可访问 private 方法。

反射操作泛型

泛型是 JDK 1.5 的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候再指定具体的类型。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

在 Java 中,采用泛型擦除的机制来引入泛型,泛型能编译器使用 javac 时确保数据的安全性和免去强制类型转换问题,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。并且一旦编译完成,所有和泛型有关的类型会被全部擦除。

Java 新增了ParameterizedTypeGenericArrayTypeTypeVariableWildcardType等几种类型,能让我们通过反射操作这些类型。

  • ParameterizedType:表示一种参数化类型,比如 Collection<String>
  • GenericArrayType:表示种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable:是各种类型变量的公共父接口
  • WildcardType:代表种通配符类型表达式
package com.nobody;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;

public class TestReflectGenerics {

<span class="hljs-keyword">public</span> Map&lt;String, Person&gt; <span class="hljs-title function_">test</span><span class="hljs-params">(Map&lt;String, Integer&gt; map, Person person)</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> NoSuchMethodException {
    <span class="hljs-comment">// 获取test方法对象</span>
    <span class="hljs-type">Method</span> <span class="hljs-variable">test</span> <span class="hljs-operator">=</span> TestReflectGenerics.class.getDeclaredMethod(<span class="hljs-string">"test"</span>, Map.class, Person.class);
    <span class="hljs-comment">// 获取方法test的参数类型</span>
    Type[] genericParameterTypes = test.getGenericParameterTypes();
    <span class="hljs-keyword">for</span> (Type genericParameterType : genericParameterTypes) {
        System.out.println(<span class="hljs-string">"方法参数类型:"</span> + genericParameterType);
        <span class="hljs-comment">// 如果参数类型等于参数化类型</span>
        <span class="hljs-keyword">if</span> (genericParameterType <span class="hljs-keyword">instanceof</span> ParameterizedType) {
            <span class="hljs-comment">// 获得真实参数类型</span>
            Type[] actualTypeArguments =
                    ((ParameterizedType) genericParameterType).getActualTypeArguments();
            <span class="hljs-keyword">for</span> (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(<span class="hljs-string">"    "</span> + actualTypeArgument);
            }
        }
    }

    <span class="hljs-comment">// 获取方法test的返回值类型</span>
    <span class="hljs-type">Type</span> <span class="hljs-variable">genericReturnType</span> <span class="hljs-operator">=</span> test.getGenericReturnType();
    System.out.println(<span class="hljs-string">"返回值类型:"</span> + genericReturnType);
    <span class="hljs-comment">// 如果参数类型等于参数化类型</span>
    <span class="hljs-keyword">if</span> (genericReturnType <span class="hljs-keyword">instanceof</span> ParameterizedType) {
        <span class="hljs-comment">// 获得真实参数类型</span>
        Type[] actualTypeArguments =
                ((ParameterizedType) genericReturnType).getActualTypeArguments();
        <span class="hljs-keyword">for</span> (Type actualTypeArgument : actualTypeArguments) {
            System.out.println(<span class="hljs-string">"    "</span> + actualTypeArgument);
        }
    }

}

}

class Person {}

// 输出结果
方法参数类型:java.util.Map<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
方法参数类型:class com.nobody.Person
返回值类型:java.util.Map<java.lang.String, com.nobody.Person>
class java.lang.String
class com.nobody.Person

反射操作注解

在 Java 运行时,通过反射获取代码中的注解是比较常用的手段了,获取到了注解之后,就能知道注解的所有信息了,然后根据信息进行相应的操作。下面通过一个例子,获取类和属性的注解,解析映射为数据库中的表信息。

package com.nobody;

import java.lang.annotation.*;

public class AnalysisAnnotation {

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception {
    Class&lt;?&gt; aClass = Class.forName(<span class="hljs-string">"com.nobody.Book"</span>);
    <span class="hljs-comment">// 获取类的指定注解,并且获取注解的值</span>
    <span class="hljs-type">Table</span> <span class="hljs-variable">annotation</span> <span class="hljs-operator">=</span> aClass.getAnnotation(Table.class);
    <span class="hljs-type">String</span> <span class="hljs-variable">value</span> <span class="hljs-operator">=</span> annotation.value();
    System.out.println(<span class="hljs-string">"Book类映射的数据库表名:"</span> + value);

    java.lang.reflect.<span class="hljs-type">Field</span> <span class="hljs-variable">bookName</span> <span class="hljs-operator">=</span> aClass.getDeclaredField(<span class="hljs-string">"bookName"</span>);
    <span class="hljs-type">TableField</span> <span class="hljs-variable">annotation1</span> <span class="hljs-operator">=</span> bookName.getAnnotation(TableField.class);
    System.out.println(<span class="hljs-string">"bookName属性映射的数据库字段属性 - 列名:"</span> + annotation1.colName() + <span class="hljs-string">",类型:"</span>
            + annotation1.type() + <span class="hljs-string">",长度:"</span> + annotation1.length());
    java.lang.reflect.<span class="hljs-type">Field</span> <span class="hljs-variable">price</span> <span class="hljs-operator">=</span> aClass.getDeclaredField(<span class="hljs-string">"price"</span>);
    <span class="hljs-type">TableField</span> <span class="hljs-variable">annotation2</span> <span class="hljs-operator">=</span> price.getAnnotation(TableField.class);
    System.out.println(<span class="hljs-string">"price属性映射的数据库字段属性 - 列名:"</span> + annotation2.colName() + <span class="hljs-string">",类型:"</span>
            + annotation2.type() + <span class="hljs-string">",长度:"</span> + annotation2.length());
}

}

// 作用于类的注解,用于解析表数据
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
// 表名
String value();
}

// 作用于字段,用于解析表列
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface TableField {
// 列名
String colName();

<span class="hljs-comment">// 列类型</span>
String <span class="hljs-title function_">type</span><span class="hljs-params">()</span>;

<span class="hljs-comment">// 长度</span>
<span class="hljs-type">int</span> <span class="hljs-title function_">length</span><span class="hljs-params">()</span>;

}

@Table("t_book")
class Book {
@TableField(colName = "name", type = "varchar", length = 15)
String bookName;
@TableField(colName = "price", type = "int", length = 10)
int price;
}

// 输出结果
Book类映射的数据库表名:t_book
bookName属性映射的数据库字段属性 - 列名:name,类型:varchar,长度:15
price属性映射的数据库字段属性 - 列名:price,类型:int,长度:10

性能分析

前面我们说过,反射对性能有一定影响。因为反射是一种解释操作,它总是慢于直接执行相同的操作。而且 Method,Field,Constructor 都有 setAccessible() 方法,它的作用是开启或禁用访问安全检查。如果我们程序代码中用到了反射,而且此代码被频繁调用,为了提高反射效率,则最好禁用访问安全检查,即设置为 true。

package com.nobody;

import java.lang.reflect.Method;

public class TestReflectSpeed {

<span class="hljs-comment">// 10亿次</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">int</span> <span class="hljs-variable">times</span> <span class="hljs-operator">=</span> <span class="hljs-number">1000000000</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception {
    test01();
    test02();
    test03();
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test01</span><span class="hljs-params">()</span> {
    <span class="hljs-type">Teacher</span> <span class="hljs-variable">t</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Teacher</span>();
    <span class="hljs-type">long</span> <span class="hljs-variable">start</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; times; i++) {
        t.getName();
    }
    <span class="hljs-type">long</span> <span class="hljs-variable">end</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    System.out.println(<span class="hljs-string">"普通方式执行10亿次消耗:"</span> + (end - start) + <span class="hljs-string">"ms"</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test02</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {
    <span class="hljs-type">Teacher</span> <span class="hljs-variable">teacher</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Teacher</span>();
    Class&lt;?&gt; aClass = Class.forName(<span class="hljs-string">"com.nobody.Teacher"</span>);
    <span class="hljs-type">Method</span> <span class="hljs-variable">getName</span> <span class="hljs-operator">=</span> aClass.getDeclaredMethod(<span class="hljs-string">"getName"</span>);
    <span class="hljs-type">long</span> <span class="hljs-variable">start</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; times; i++) {
        getName.invoke(teacher);
    }
    <span class="hljs-type">long</span> <span class="hljs-variable">end</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    System.out.println(<span class="hljs-string">"反射方式执行10亿次消耗:"</span> + (end - start) + <span class="hljs-string">"ms"</span>);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">test03</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception {
    <span class="hljs-type">Teacher</span> <span class="hljs-variable">teacher</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Teacher</span>();
    Class&lt;?&gt; aClass = Class.forName(<span class="hljs-string">"com.nobody.Teacher"</span>);
    <span class="hljs-type">Method</span> <span class="hljs-variable">getName</span> <span class="hljs-operator">=</span> aClass.getDeclaredMethod(<span class="hljs-string">"getName"</span>);
    getName.setAccessible(<span class="hljs-literal">true</span>);
    <span class="hljs-type">long</span> <span class="hljs-variable">start</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i &lt; times; i++) {
        getName.invoke(teacher);
    }
    <span class="hljs-type">long</span> <span class="hljs-variable">end</span> <span class="hljs-operator">=</span> System.currentTimeMillis();
    System.out.println(<span class="hljs-string">"关闭安全检查反射方式执行10亿次消耗:"</span> + (end - start) + <span class="hljs-string">"ms"</span>);
}

}

class Teacher {

<span class="hljs-keyword">private</span> String name;

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

}

// 输出结果
普通方式执行10亿次消耗:13ms
反射方式执行10亿次消耗:20141ms
关闭安全检查反射方式执行10亿次消耗:8233ms

通过实验可知,反射比直接执行相同的方法慢了很多,特别是当反射的操作被频繁调用时效果更明显,当然通过关闭安全检查可以提高一些速度。所以,放射也不应该泛滥成灾的,而是适度使用才能发挥最大作用。