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

注解与反射-复习

程序员文章站 2022-05-18 18:47:39
[toc] 基于B站秦疆老师的课. 感谢! 注解 @since JDK1.5 不是程序本身, 可以对程序做出解释, 可以被其他程序读取(如 编译器) 内置注解 抑制编译器的警告信息 不鼓励使用被修饰的方法\属性\类, 即将弃用 元注解 meta annotation 给注解作注解 在java.lan ......

基于b站秦疆老师的课. 感谢!

注解

@since jdk1.5

不是程序本身, 可以对程序做出解释, 可以被其他程序读取(如 编译器)

内置注解

  • @override

  • @suppresswarnings(value="all") //"unchecked" ...

    抑制编译器的警告信息

  • @deprecated

    不鼓励使用被修饰的方法\属性\类, 即将弃用

元注解

meta-annotation

给注解作注解

在java.lang.annotation, java定义了4个标准的元注解:

  • @target: 描述注解的使用范围, 如类\方法或构造器等

  • @retention: 描述需要在什么级别保留该注解信息

    source < class < runtime, 框架里很多用runtime

  • @documented: 表示该注解讲被保留在javadoc中

  • @inherited: 表示子类可以继承父类中的该注解

简单代码演示:

@target(value = {elementtype.method, elementtype.type})	// 描述注解可以作用的语句类型
public @interface myannotation{	// 自定义注解的声明语句
    
}

自定义注解

  • 使用@interface 声明自定义注解, 自动继承了java.lang.annotation.annotation接口
  • 其中的成员, 类似于 "方法"
    • 其实是配置参数
    • 方法名是参数名. 如果只有一个参数, 建议参数名为value, 使用时可以免写参数名
    • 返回值类型是参数的类型, 只能为基本类型class, string, enum
    • 定义了参数, 如果没有定义默认值, 则使用时必须赋值
    • default关键字修饰默认值
    • 注解元素必须有值, 哪怕是用空字符串\0作为默认值. 如果定义为-1则表示不存在

反射机制

简介

课程内容梗概

  1. java反射机制概述
  2. 理解class类并获取class实例
  3. 类的加载 (包含classloader)
  4. 创建运行时类的对象
  5. 获取运行时类的完整信息
  6. 调用运行时类

java是准动态语言

因为它有反射机制, 才可以视之为"准动态语言"

从内存角度理解反射

类加载完毕后, 在堆内存的方法区就产生了一个唯一class类型的对象, 其中包含了完整的类的结构信息. 我们可以通过它看到类的完整结构

反射对性能有一定影响

因为反射是一种"解释操作", 总是慢于"直接执行"

class类

对class类的认识

  • class本身也是一个类
  • class对象只能由系统建立
  • 一个类加载到jvm, 只会对应一个class对象
  • 且对应一个.class文件
  • 每个类的实例都会记得自己是由哪个class对象生成的
  • 通过class对象可以完整得到这个类中所有的结构信息
  • class类是relection的根源, 任何想动态加载\运行的类, 只能先获取其class对象
  • 两个元素类型相同的数组, 即使size不同, 实际也是一个对象(hashcode相等. 同维度数组)
// 数组
int[] a = new int[10];
int[] b = new int[100];
// hashcode打印结果相等
460141958
460141958

哪些可以拥有class对象?

  • 普通类(包括内部类)
  • interface
  • []
  • enum
  • annotation
  • primitive type, 基本数据类型
  • void
class<object> c1 = object.class;            // 类
class<comparable> c2 = comparable.class;    // 接口
class<string[]> c3 = string[].class;        // 数组 class [ljava.lang.string;
class<int[][]> c4 = int[][].class;          // 数组 class [[i
class<override> c5 = override.class;        // 注解
class<elementtype> c6 = elementtype.class;  // 枚举
class<integer> c7 = integer.class;          // 基本数据类型
class<integer> c72 = integer.type;          // 基本数据另一种
class<void> c8 = void.class;				// void
class<class> c9 = class.class;				// class
// system out结果
class java.lang.object
interface java.lang.comparable
class [ljava.lang.string;
class [[i
interface java.lang.override
class java.lang.annotation.elementtype
class java.lang.integer
int
void
class java.lang.class

获取class对象的方式

// 已有对象. object超类提供的
public final class getclass();
对象.getclass();
// class类的静态方法
class.forname("包名.类名");
// 已知类名, 最安全可靠, 性能高######################
类名.class();

// 基本数据类型的包装类, 可以直接用类名.type
integer.type;	// int
// 也可以通过classloader

class类的常用方法

static classfornae(string name); // 传入类名, 返回class对象
object newinstance(); // 调用缺省构造器, 返回返回class对象的一个实例
getname(); // 返回此class对象所表示的实体的名称, (如类\接口\数组类\void)
class getsuperclass(); // 返回父类class对象
class[] getinterfaces(); // 获取当前class对象的接口
classloadder getclassloader(); //获取该类的类加载器
constructor[] getconstructors(); // 获取构造器对象们, 以数组保存
method getmethod(string name, class... t);	// 返回一个method对象, 对象形参为paramtype
field[] getdeclaredfields();	// 返回属性对象们, 以数组保存

java内存

内存的基本结构

    • 存放基本类型变量, 包含数值
    • 存放引用对象, 在堆中的地址值
  • 堆 (所有线程共享)
    • 存放new的对象 (包括数组)
  • 方法区 (特殊的堆, 所有线程共享)
    • 存放class对象
    • 存放static修饰的东西

类加载的过程

  1. load

    加载类的过程

    1. 将.class字节码文件内容读取并加载到内存中
    2. static数据转换成方法区的运行时数据
    3. 生成一个代表被加载类的java.lang.class对象
  2. link

    链接过程, 将类的二进制数据合并到jvm的运行状态, static变量赋默认初始化值, 常量赋指定值, 具体为:

    1. 验证: 确保所加载的类信息符合jvm规范

    2. 准备: 为static的数据()分配位于方法区的内存空间, 并设置默认初始 (例子中int m = 0)

    3. 解析: jvm常量池中的符号引用 (常量名) 替换为直接引用 (地址)

      [不太懂, 估计是常量赋指定常量值]

  3. initialize

    执行"类构造器"<clinit>()方法

    另外有说明:

    • 初始化类需要先初始化该类的父类

    • jvm会保证<clinit>()方法在多线程环境中被正确加锁和同步

      <clinit>()方法:

      • 是由编译器自动收集的类中的所有类变量的赋值动作和静态代码块中的语句合并产生的. [个人理解: 所有static修饰的数据]
      • 是构造类信息的"类构造器" [个人理解: 不是constructor构造器!]

类加载过程, 自己理解的小结:

  1. load, 加载 在堆中生成该类对应的class类对象
  2. link, 链接, 静态变量分配内存空间, 赋默认初值(int是0, 常量则是具体常量真实值, 云云...)
  3. initialize, 初始化, 执行static修饰的部分 + 赋程序意图指定的值

什么时候类会初始化?

结论: "类的主动引用", 会初始化类; "类的被动引用", 不会初始化类

  • 主动引用

    • jvm启动, main()所在类

    • new 对象

      注: 如果有父类, 需要先初始化父类

    • 类名直接调用static成员

      注: 不包括static final, 也就是常量

    • 反射, 得到class对象

      : 只有class.forname()是主动引用, 类名.class不是主动引用

  • 被动引用

    • 访问静态field, 声明该field的类才会初始化. (通过子类访问父类的静态属性, 只初始化父类)
    • new以该类为元素类型的数组, 只开辟相应内存空间, 不主动初始化该类
    • 类名直接调用常量. (常量是static final, 这一点与主动引用中类名调用static成员相结合理解)

代码演示:

package com.kuang.reflection;
// 测试类什么时候会初始化
public class test06 {
    static {
        system.out.println("main所在类被加载");
    }
    public static void main(string[] args) throws illegalaccessexception, instantiationexception, classnotfoundexception {
        // 1. new 类
//        son son = new son();
        // 2. 反射
//        class<?> aclass1 = class.forname("com.kuang.reflection.son");   // 只是反射, 就已经全部加载
//        class<son> sonclass = son.class;    // 只加载main
//        class<? extends son> aclass = son.getclass(); // 全部加载
//        aclass.newinstance();
//        sonclass.newinstance();


        // 不会产生类主动引用的操作
//        system.out.println(son.b);  // 只加载main和父

//        son[] arr = new son[5]; // 只加载main, 说明数组只开辟了空间

        system.out.println(son.m);  // 只加载main, 因: 常量池中

        /*
        测试调用静态成员方法
         */
//        system.out.println(son.m);

        /*
        结果猜测: 不过顺序不是本例重点
            main类被加载
            子类被加载
            父类被加载
        正确答案: 猜测错误!
            main
            父类
            子类
         */
    }
}
class father{
    static int b = 2;

    static {
        system.out.println("父类被加载");
    }
}
class son extends father{
    static {
        system.out.println("子类被加载");
        m = 300;
    }
    static int m = 100;
    static final int m = 1;
}

classloader

类加载器的作用

  1. 将*.class源文件内容加载到内存中, 将这些静态数据转换成方法区的运行时数据
  2. 在堆中生成一个代表这个类的java.lang.class对象, 作为方法区中类数据的访问入口

"类缓存"

类加载到类加载器中, 将维持加载(缓存)一段时间, class对象会存在一段时间, 就是类缓存. jvm的gc会回收这些class对象.

jvm规范定义了如下类型的类加载器:

  • bootstap classloader: 引导类加载器, c++编写, 负责java平台核心库rt.jar, 该类加载器无法直接获取

    无法直接获取, 表现为执行.getparent()返回null

  • extension classloader: 扩展类加载器, 负责jre/lib/ext/*.jar

  • system\application classloader: 系统类加载, java -classpath下的.jar, 最常用的加载器

  • 自定义类加载器

补充: rt.jar是什么? java.lang的根目录在哪里?

// 位置:
位于jdk文件, ...jdk1.8.0_71\jre\lib\rt.jar
// 用解压缩软件打开rt.jar:
com, java, javax, jdk, meta-inf, org, sun
// 打开java:
applet, awt, io, lang, math, rio, rmi, security, sql, text, time, util

**代码演示: 获取各类加载器 **

/*获取类加载器*/
public class test07 {
    public static void main(string[] args) {
        // 获取systemclassloader
        classloader systemclassloader = classloader.getsystemclassloader();
        system.out.println(systemclassloader);  // sun.misc.launcher$appclassloader@14dad5dc
        // 获取systemclassloader的父类加载器: extclassloader
        classloader parent = systemclassloader.getparent();
        system.out.println(parent);     // sun.misc.launcher$extclassloader@1b6d3586
        // 获取扩展类加载器的父类加载器: 根加载器 (c++)
        classloader parent1 = parent.getparent();
        system.out.println(parent1);    // null, 可能是java语言层面无法理解, 无法直接获取
        
        
        // 获取当前test07类是哪个类加载器负责的
        class<?> c1 = class.forname("com.kuang.reflection.test07");
        classloader classloader = c1.getclassloader();
        system.out.println(classloader);    // sun.misc.launcher$appclassloader@14dad5dc

        // 获取object类是由哪个类加载器负责加载的
        class<?> c2 = class.forname("java.lang.object");
        classloader classloader2 = c2.getclassloader();
        system.out.println(classloader2);   // null
        
    }
}

双亲委派机制

此机制可以确保, 假如手写的包如果和核心类库包路径\名称雷同时, 不会错误地加载自己手写的包, 从而保证了java核心类库的安全

反射获取运行时类的结构信息

package com.kuang.reflection;
import java.lang.reflect.constructor;
import java.lang.reflect.field;
import java.lang.reflect.method;
/*获得类的信息*/
public class test08 {
    public static void main(string[] args) throws classnotfoundexception, nosuchfieldexception, nosuchmethodexception {
        class<?> c1 = class.forname("com.kuang.reflection.user");

        // 获得类的名字
        system.out.println(c1.getname());       // 获得完整类名
        system.out.println(c1.getsimplename()); // 获得单纯类名

        // 获得类的所有属性
        field[] fields = c1.getfields();    // 只能找到public属性
        for (field field : fields) {
            system.out.println(field);  // 结果猜错了, 无结果
        }

        fields = c1.getdeclaredfields();    // 这个才能获取非public的, 更完整的属性们
        for (field field : fields) {
            system.out.println(field);
        }
        /*
          private java.lang.string com.kuang.reflection.user.name
          private int com.kuang.reflection.user.id
          private int com.kuang.reflection.user.age
         */
        system.out.println("######################################");

        // 获得指定field值
//        field name = c1.getfield("name");   // 报错, 找不到这个field, 因为方法找不到private的
//        system.out.println(name);
        field name2 = c1.getdeclaredfield("name");
        system.out.println(name2);

        // 获得method
        system.out.println("方法: ");
        method[] methods = c1.getmethods(); // 获得本类及父类的全部public方法
        for (method method : methods) {
            system.out.println(method);
        }
        system.out.println("declared方法");   // 获得本类的所有方法
        methods = c1.getdeclaredmethods();
        for (method method : methods) {
            system.out.println(method);
        }

        // 获得指定方法
        system.out.println(c1.getmethod("getname", null));
        system.out.println(c1.getmethod("setname", string.class));
        system.out.println("######################################");

        // 获得所有构造器
        constructor<?>[] constructors = c1.getconstructors();
        for (constructor<?> constructor : constructors) {
            system.out.println(constructor);
        }
        constructors = c1.getdeclaredconstructors();
        for (constructor<?> constructor : constructors) {
            system.out.println(constructor);
        }
        system.out.println("######################################");
        constructor<?> declaredconstructor = c1.getdeclaredconstructor(string.class, int.class, int.class);
        system.out.println(declaredconstructor);
    }
}

动态创建对象

通过反射创建对象 (一般情况)

user (目标类) 的class对象调用.newinstance(), 要求:

  1. 必须确保该类有无参构造器
  2. 必须确保该构造器的访问权限合适
// 获得class对象
class c1 = class.forname("com.kuang.reflection.user");
// 构造一个对象
user user = (user)c1.newinstance();  // 本质是调用了类的无参构造

通过"构造器对象"创建对象 (不存在无参构造)

假如不存在无参构造器, 就不能通过上一条途径, 但还可以通过constructor对象, 传入指定参数构建对象, 步骤如下:

  1. 获得指定某重载构造器对象, .getdeclaredconstructor(class... parametertypes), 参数为目标对象参数类型的class对象string.class, int.class...
  2. 构造器对象.newinstance(目标对象需要具体参数)
constructor constructor = c1.getdeclaredconstructor(string.class, int.class, int.class);
user user2 = (user) constructor.newinstance("zhangsan", 1001, 18);

通过反射调用成员:

method

借助method类完成, 步骤如下:

  1. c1.getdeclaredmethod()\getmethod(), 参数: 方法名, 参数类型.class...
  2. method对象.invoke(对象, 具体参数们...)
// 通过反射调用成员方法
user user3 = (user) c1.newinstance();
// 通过反射获取方法
method setname = c1.getdeclaredmethod("setname", string.class);
setname.invoke(user3, "狂神");    // 调用

field

// 通过反射操作属性
user user4 = (user) c1.newinstance();
field name = c1.ge tdeclaredfield("name");
name.setaccessible(true);   // 操作私有属性之前需要操作
name.set(user4, "李四");
system.out.println(user4);  // 报错, 属性是private的, 不能直接操作,需要setaccessible

注意: 如果目标方法\构造器或属性为private, 则需要提前.setaccessible(true);

setaccessible(),是"访问安全检查的开关"

  • true, 取消访问安全检查, 代码效率也会更高
  • false, 实施访问安全检查

测试: 性能分析

package com.kuang.reflection;

import java.lang.reflect.invocationtargetexception;
import java.lang.reflect.method;

/*分析性能问题*/
public class test10 {
    // 普通方式调用
    public static void test01(){
        user user = new user();
        long starttime = system.currenttimemillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getname();
        }
        long endtime = system.currenttimemillis();
        system.out.println("普通方式执行10亿次:" + (endtime - starttime) + "ms");
    }
    // 反射方式调用
    public static void test02() throws nosuchmethodexception, invocationtargetexception, illegalaccessexception {
        user user = new user();
        class<? extends user> c1 = user.getclass();
        method getname = c1.getdeclaredmethod("getname", null);
        long starttime = system.currenttimemillis();
        for (int i = 0; i < 1000000000; i++) {
            getname.invoke(user, null);
        }
        long endtime = system.currenttimemillis();
        system.out.println("反射1方式执行10亿次:" + (endtime - starttime) + "ms");
    }
    // 关闭访问安全检测
    public static void test03() throws nosuchmethodexception, invocationtargetexception, illegalaccessexception {
        user user = new user();
        class<? extends user> c1 = user.getclass();
        method getname = c1.getdeclaredmethod("getname", null);
        getname.setaccessible(true);
        long starttime = system.currenttimemillis();
        for (int i = 0; i < 1000000000; i++) {
            getname.invoke(user, null);
        }
        long endtime = system.currenttimemillis();
        system.out.println("反射2方式执行10亿次:" + (endtime - starttime) + "ms");
    }
    public static void main(string[] args) throws nosuchmethodexception, illegalaccessexception, invocationtargetexception {
        test01();
        test02();
        test03();
    }

}

结果:

普通方式执行10亿次:4ms
反射1方式执行10亿次:2134ms
反射2方式执行10亿次:1266ms

普通方式执行10亿次:5ms
反射1方式执行10亿次:2183ms
反射2方式执行10亿次:1246ms

普通方式执行10亿次:5ms
反射1方式执行10亿次:2087ms
反射2方式执行10亿次:1252ms   

反射获取泛型数据

代码演示:

package com.kuang.reflection;

import java.lang.reflect.method;
import java.lang.reflect.parameterizedtype;
import java.lang.reflect.type;
import java.util.list;
import java.util.map;

/*通过反射获取泛型*/
public class test11 {
    public void test01 (map<string, user> map, list<user> list, string string, user user) {
        system.out.println("test01");
    }
    public map<string, user> test02(){
        system.out.println("test02");
        return null;
    }
    public static void main(string[] args) throws nosuchmethodexception {
        /*1. 获取方法参数列表中的泛型数据*/
        system.out.println("test01()");
        method methodtest01 = test11.class.getmethod("test01", map.class, list.class, string.class, user.class);
        type[] genericparametertypes = methodtest01.getgenericparametertypes();
        for (type genericparametertype : genericparametertypes) {   // 遍历方法的参数, 还需要取具体泛型
            system.out.println(genericparametertype);
            if (genericparametertype instanceof parameterizedtype) {    // 如果是泛型
                type[] actualtypearguments = ((parameterizedtype) genericparametertype).getactualtypearguments();// 就获取真实类型(actualtype)
                for (type actualtypeargument : actualtypearguments) {
                    system.out.println("方法参数列表中的泛型: " + actualtypeargument);
                }
            }
        }
        /*2. 获取方法返回值中的泛型数据*/
        system.out.println("test02()");
        method test02method = test11.class.getmethod("test02");
        class<?> returntype = test02method.getreturntype(); // 这种方法不能获取其中的泛型部分
        type genericreturntype = test02method.getgenericreturntype();
        system.out.println(returntype);
        system.out.println(genericreturntype);
        if (genericreturntype instanceof parameterizedtype) {
            type[] actualtypearguments = ((parameterizedtype) genericreturntype).getactualtypearguments();
            for (type actualtypeargument : actualtypearguments) {
                system.out.println("方法返回值中的泛型类型:" + actualtypeargument);
            }
        }
    }
}
d:\software\jdk1.8.0_71\bin\java.exe "-javaagent:d:\program files\jetbrains\intellij idea 2019.2\lib\idea_rt.jar=64909:d:\program files\jetbrains\intellij idea 2019.2\bin" -dfile.encoding=utf-8 -classpath d:\software\jdk1.8.0_71\jre\lib\charsets.jar;d:\software\jdk1.8.0_71\jre\lib\deploy.jar;d:\software\jdk1.8.0_71\jre\lib\ext\access-bridge-64.jar;d:\software\jdk1.8.0_71\jre\lib\ext\cldrdata.jar;d:\software\jdk1.8.0_71\jre\lib\ext\dnsns.jar;d:\software\jdk1.8.0_71\jre\lib\ext\jaccess.jar;d:\software\jdk1.8.0_71\jre\lib\ext\jfxrt.jar;d:\software\jdk1.8.0_71\jre\lib\ext\localedata.jar;d:\software\jdk1.8.0_71\jre\lib\ext\nashorn.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunec.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunjce_provider.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunmscapi.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunpkcs11.jar;d:\software\jdk1.8.0_71\jre\lib\ext\zipfs.jar;d:\software\jdk1.8.0_71\jre\lib\javaws.jar;d:\software\jdk1.8.0_71\jre\lib\jce.jar;d:\software\jdk1.8.0_71\jre\lib\jfr.jar;d:\software\jdk1.8.0_71\jre\lib\jfxswt.jar;d:\software\jdk1.8.0_71\jre\lib\jsse.jar;d:\software\jdk1.8.0_71\jre\lib\management-agent.jar;d:\software\jdk1.8.0_71\jre\lib\plugin.jar;d:\software\jdk1.8.0_71\jre\lib\resources.jar;d:\software\jdk1.8.0_71\jre\lib\rt.jar;d:\software-idea\project-qinjiang\javase\out\production\reflectandnotation com.kuang.reflection.test11
test01()
java.util.map<java.lang.string, com.kuang.reflection.user>
方法参数列表中的泛型: class java.lang.string
方法参数列表中的泛型: class com.kuang.reflection.user
java.util.list<com.kuang.reflection.user>
方法参数列表中的泛型: class com.kuang.reflection.user
class java.lang.string
class com.kuang.reflection.user
test02()
interface java.util.map
java.util.map<java.lang.string, com.kuang.reflection.user>
方法返回值中的泛型类型:class java.lang.string
方法返回值中的泛型类型:class com.kuang.reflection.user

涉及到的独特而重要的方法:

  • methodtest01.getgenericparametertypes(), 返回方法对象中所有参数的type[], 包括泛型和泛型参数的
  • genericparametertype instanceof parameterizedtype, 判断这个type对象是否属于"泛型type(参数化类型)"
  • ((parameterizedtype) genericparametertype).getactualtypearguments(), 抽取map<string, user>中的string, user的type对象. 注意需要强转.

反射获取注解数据

代码演示:

package com.kuang.reflection;

import java.lang.annotation.*;
import java.lang.reflect.field;

/*反射获取注解*/
public class test12 {
    public static void main(string[] args) throws classnotfoundexception, nosuchfieldexception {
        /*获取类的student2类的注解的class对象*/
        class<?> c1 = class.forname("com.kuang.reflection.student2");
        annotation[] annotations = c1.getannotations();
        for (annotation annotation : annotations) {
            system.out.println(annotation);
        }
        /*获取注解对象的成员: value*/
        tablekuang tablekuang = c1.getannotation(tablekuang.class);
        string value = tablekuang.value();
        system.out.println(value);

        /*获取类成员的注解*/
        field field = c1.getdeclaredfield("name");
        annotation[] annotations1 = field.getannotations();
        for (annotation annotation : annotations1) {
            system.out.println(annotation);
        }
        fieldkuang annotation = field.getannotation(fieldkuang.class);
        system.out.println(annotation.columnname());
        system.out.println(annotation.type());
        system.out.println(annotation.length());
    }
}

/*声明类名的注解*/
@retention(retentionpolicy.runtime)
@target({elementtype.type})
@interface tablekuang{
    string value();
}
/*声明属性的注解*/
@retention(retentionpolicy.runtime)
@target({elementtype.field})
@interface fieldkuang{

    string columnname();
    string type();
    int length();
}


/*pojo*/
@tablekuang("db_student")
class student2{

    @fieldkuang(columnname="db_name", type="varchar", length = 10)
    private string name;
    @fieldkuang(columnname="db_id", type="int", length = 10)
    private int id;
    @fieldkuang(columnname="db_age", type="int", length = 10)
    private int age;

    public student2() {
    }

    public student2(string name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public string getname() {
        return name;
    }

    public void setname(string name) {
        this.name = name;
    }

    public int getid() {
        return id;
    }

    public void setid(int id) {
        this.id = id;
    }

    public int getage() {
        return age;
    }

    public void setage(int age) {
        this.age = age;
    }

    @override
    public string tostring() {
        return "student2{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

运行结果:

d:\software\jdk1.8.0_71\bin\java.exe "-javaagent:d:\program files\jetbrains\intellij idea 2019.2\lib\idea_rt.jar=64741:d:\program files\jetbrains\intellij idea 2019.2\bin" -dfile.encoding=utf-8 -classpath d:\software\jdk1.8.0_71\jre\lib\charsets.jar;d:\software\jdk1.8.0_71\jre\lib\deploy.jar;d:\software\jdk1.8.0_71\jre\lib\ext\access-bridge-64.jar;d:\software\jdk1.8.0_71\jre\lib\ext\cldrdata.jar;d:\software\jdk1.8.0_71\jre\lib\ext\dnsns.jar;d:\software\jdk1.8.0_71\jre\lib\ext\jaccess.jar;d:\software\jdk1.8.0_71\jre\lib\ext\jfxrt.jar;d:\software\jdk1.8.0_71\jre\lib\ext\localedata.jar;d:\software\jdk1.8.0_71\jre\lib\ext\nashorn.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunec.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunjce_provider.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunmscapi.jar;d:\software\jdk1.8.0_71\jre\lib\ext\sunpkcs11.jar;d:\software\jdk1.8.0_71\jre\lib\ext\zipfs.jar;d:\software\jdk1.8.0_71\jre\lib\javaws.jar;d:\software\jdk1.8.0_71\jre\lib\jce.jar;d:\software\jdk1.8.0_71\jre\lib\jfr.jar;d:\software\jdk1.8.0_71\jre\lib\jfxswt.jar;d:\software\jdk1.8.0_71\jre\lib\jsse.jar;d:\software\jdk1.8.0_71\jre\lib\management-agent.jar;d:\software\jdk1.8.0_71\jre\lib\plugin.jar;d:\software\jdk1.8.0_71\jre\lib\resources.jar;d:\software\jdk1.8.0_71\jre\lib\rt.jar;d:\software-idea\project-qinjiang\javase\out\production\reflectandnotation com.kuang.reflection.test12
@com.kuang.reflection.tablekuang(value=db_student)
db_student
@com.kuang.reflection.fieldkuang(columnname=db_name, type=varchar, length=10)
db_name
varchar
10