注解
1. 注解入门
Annotation是从JDK5.0开始引入的新技术
Annotation的作用:
- 不是程序本身,可以对程序作出解释。(这一点和(comment)没什么区别)
- 可以被其他程序(比如:编译器等)读取
Annotation的格式:
- 注解是以”@注释名“在代码中存在,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")
Annotation在哪里使用
- 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问
2. 内置注解
- @Override:定义在java.lang.Override中,此注释只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
- @Deprecated:定义在java.lang.Deprecated中,此注释可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
- @SuppressWarnings:定义在java.lang.SuppressWarings中,用来抑制编译时的警告信息,与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好了
- @SuppressWarnings("all")
- @SuppressWarnings("unchecked")
- @SuppressWarnings(value={"unchecked","deprecation"})
- 等等
package com.tyrant.annotation; public class Demo01 extends Object { //@Override 重写的注解 @Override public String toString() { return super.toString(); } //@Deprecated 不推荐使用,已过时 @Deprecated public void test01(){ System.out.println("Deprecated"); } //@SuppressWarnings 镇压警告 @SuppressWarnings("all") public void test02(){ System.out.println("@SuppressWarnings"); } public static void main(String[] args) { new Demo01().test01(); } }
3. 元注解
元注解的作用就是负责注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型作说明
这些类型和他们所支持的类在java.lang.annotation包中可以找到(@Targe, @Retention, @Documented, @Inherited)
- @Target:用于描述注解的适用范围(即被描述的注解可以用在什么地方)
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
- (SOURCE < CLASS < RUNTIME)
- @Document:说明该注解将被包含在javadoc中
- @Inherited:说明子类可以继承父类中的该注解
package com.tyrant.annotation; import java.lang.annotation.*; //测试元注解 @MyAnnotation01 public class Demo02 { @MyAnnotation01 public void test(){ } } //定义一个注解 //@Target() 表示注解可以用在哪些地方 @Target(value = {ElementType.METHOD, ElementType.TYPE}) //@Retention() 表示我们的注解到什么地方还有效 @Retention(value = RetentionPolicy.RUNTIME) //@Documented 表示是否将我们的注解生成在JavaDoc中 @Documented //@Inherited 表示子类可以继承父类的注解 @Inherited @interface MyAnnotation01{ }
4. 自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
- @interface用来声明一个注解,格式:public @interface 注解名
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型:Class,String,emum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
package com.tyrant.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //自定义注解 public class Demo03 { //注解可以显示赋值,如果没有默认值,我们就必须给注解赋值 @MyAnnotation02 public void test(){} //使用value可以省略一个value @MyAnnotation03("tyrant") public void test02(){} } @Target(value = {ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation02{ //注解的参数:参数类型 + 参数名 (); String name() default ""; int age() default 0; int id() default -1;//如果默认值为 -1 ,表示不存在 String[] school() default {"hello", "world"}; } @Target(value = {ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation03{ //如果只有一个方法,建议使用value命名 String value(); }
反射
1. Java反射机制概述
1.1 静态语言和动态语言
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
主要动态语言:Object-C、C#、JavaScript、PHP、Python等
静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言,如Java、C、C++
Java不是动态语言,但Java可以称之为”准动态语言“。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活(也降低了安全性)
1.2 Java Reflection
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息,我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象地称之为:反射
正常方式: 引入需要的“包类”名称-->通过new实例化-->取得实例化对象 反射方式: 实例化对象-->getClass()方法-->得到完整的"包类"名称
1.3 Java反射优点和缺点
优点:
- 可以实现动态创建对象和编译,体现出很大的灵活性
缺点:
- 对性能有影响.使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求.这类操作总是慢于直接执行相同的操作
1.4 反射相关的主要API
java.lang.Class//代表一个类 java.lang.reflect.Method//代表类的方法 java.lang.reflect.Field//代表类的成员变量 java.lang.reflect.Constructor//代表类的构造器
2. 理解Class类并获取Class实例
2.1 Class类
在Object类中定义了以下的方法,此方法将被所有子类继承
public final Class getClass(){}
以上的方法返回值是一个Class类, 此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解, 即: 可以通过对象反射求出类的名称
对象照镜子后可以得到的信息, 某个类的属性. 方法. 和构造器, 某个类到底实现了哪些接口, 对于每个类而言, JRE都为其保留一个不变的Class类型的对象, 一个Class对象包含了特定结构(class/interface/enum/annotation/primitive type/void/[])的有关信息
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class刻意完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源, 针对任何你想动态加载, 运行的类. 唯有先获得相应的Class对象
2.2 Class类的常用方法
static ClassforName(String name)
: 返回指定类名name的Class对象
Object newInstance()
: 调用缺省构造函数, 返回Class对象的一个实例
getName()
: 返回此Class对象所表示的实体, (类, 接口, 数组类或void)的名称
Class getSuperClass()
: 返回当前Class对象的父类的Class对象
Class[] getinterfaces()
: 返回该类的类加载器
Constructor[] getConstructors()
: 返回一个包含某些Constructor对象的数组
Method getMothed(String name, Class T)
: 返回一个Method对象, 此对象的形参类型为paramType
Field[] getDeclaredFields()
: 返回Field对象的一个数组
2.3 获取Class类的实例
- 若已知具体的类, 通过类的class属性获取, 该方法最为安全可靠, 程序性能最高
Class clazz = Person.class;
- 已知某个类的实例, 调用该实例的getClass()方法获取Class对象
Class clazz = person.getClass();
- 已知一个类的全类名, 且该类在类路径下, 可通过Class类的静态方法forName()获取, 可能抛出ClassNotFoundException
Class clazz = Class.forName("demo01.Student");
- 内置基本数据类型可以直接用类名.Type
- 可以利用ClassLoader
package com.tyrant.Reflection; //测试Class类的创建方式有哪些 public class Demo02 { public static void main(String[] args) throws ClassNotFoundException { Person01 person = new Student01(); System.out.println("这个人是: " + person.name); //方式1: 通过对象获得class对象 Class c1 = person.getClass(); System.out.println(c1.hashCode()); //方式2: forName获得class对象 Class c2 = Class.forName("com.tyrant.Reflection.Student01"); System.out.println(c2.hashCode()); //方式3: 通过类名获得class对象 Class c3 = Student01.class; System.out.println(c3.hashCode()); //方式4: 基本类型的包装类都有一个Type属性 Class c4 = Integer.TYPE; System.out.println(c4); //方式5: 获得父类类型 Class c5 = c1.getSuperclass(); System.out.println(c5); } } //Person类 class Person01{ public String name; public Person01(){} public Person01(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } //Student类继承Person类 class Student01 extends Person01{ public Student01(){ this.name = "学生"; } } // class Teacher01 extends Person01{ public Teacher01(){ this.name = "老师"; } }
2.4 哪些类型可以有Class对象
class: 外部类, 成员(成员内部类, 静态内部类), 局部内部类, 匿名内部类
interface: 接口
[]: 数组
enum: 枚举
annotation: 注解@interface
primitive type: 基本数据类型
void
package com.tyrant.Reflection; import java.lang.annotation.ElementType; //所有类型的Class对象 public class Demo03 { public static void main(String[] args) { //类 Class c1 = Object.class; //接口 Class c2 = reflectionTest.class; //一维数组 Class c3 = String[].class; //二维数组 Class c4 = int[][].class; //注解 Class c5 =Override.class; //基本数据类型 Class c6 = int.class; //枚举类型 Class c7 = ElementType.class; //空类型 Class c8 = void.class; //Class类 Class c9 = Class.class; System.out.println(c1); System.out.println(c2); System.out.println(c3); System.out.println(c4); System.out.println(c5); System.out.println(c6); System.out.println(c7); System.out.println(c8); System.out.println(c9); //只要元素类型和维度相同则是是一个Class对象 int[] a = new int[10]; int[] b = new int[100]; System.out.println(a.getClass().hashCode()); System.out.println(b.getClass().hashCode()); } } interface reflectionTest{}
3. 类的加载与ClassLoader
3.1 类的加载过程
当程序主动使用某个类时, 如果该类还未被加载到内存中, 则系统会通过如下三个步骤来对类进行初始化
-
类的加载(Load): 将类的class字节码文件读入内存, 并将这些静态数据转换为方法区的运行时数据结构, 然后生成一个java.lang.Class对象, 此过程由类加载器完成
-
类的链接(Link): 将类的二进制数据合并到JRE(JVM的运行状态)中
- 验证: 确保加载的类信息符合JVM规范, 没有安全方面的问题
- 准备: 正式为类变量(static)分配内存并设置类变量默认初始值的阶段, 这些内存都将在方法区中进行分配
- 解析: 虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
-
类的初始化: JVM负责对类进行初始化
- 执行类构造器
<clinit>()
方法的过程, 类构造器<clinit>()
方法时由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的, 不是构造该类对象的构造器) - 当初始化一个类时, 如果发现其父类还没有进行初始化, 则需要先触发其父类的初始化
- 虚拟机会保证每一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步
package com.tyrant.Reflection; public class Demo04 { public static void main(String[] args) { A a = new A(); System.out.println(a.m); /* 1. 加载到内存, 会产生一个类对应Class对象 2. 链接, 链接结束后 m = 0 3. 初始化, <clinit>(){ System.out.println("A类静态代码块初始化"); m = 300; m = 100; } */ } } class A{ static { System.out.println("A静态代码块"); m = 300; } /* m = 300; m = 100; */ public static int m = 100; public A(){ System.out.println("A无参构造方法"); } } - 执行类构造器
3.2 什么时候会发生类的初始化
类的主动引用(一定会发生类似的初始化)
- 当虚拟机启动, 先初始化main方法所在的类
- new 一个类的对象
- 调用类的静态成员(除了final常量) 和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类, 如果其父类没有被初始化, 则会先初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时, 只有真正声明这个域的类才会被初始化, 如: 当通过子类引用父类的静态变量, 不会导致子类初始化
- 通过数组定义类引用, 不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
package com.tyrant.Reflection; //测试类什么时候会初始化 public class Demo05 { static { System.out.println("main类被加载"); } public static void main(String[] args) throws ClassNotFoundException { //1. 主动引用 //Son son = new Son(); //2. 反射也会产生主动引用 //Class.forName("com.tyrant.Reflection.Son"); //不会产生类的引用方法 //System.out.println(Son.b); //Son[] sons = new Son[5]; //System.out.println(Son.M); } } 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; }
3.3 类加载器的作用
类加载的作用: 将class文件字节码内容加载到内存中, 并将这些静态数据转换成方法区的运行时数据结构, 然后在堆中生成一个代表这个类的java.lang.Class对象, 作为方法区中类数据的访问入口
类缓存: 标准的JavaSE类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中, 它将维持加载(缓存)一段时间, 不过JVM垃圾回收机制可以回收这些Class对象
类加载器的作用是用来把类(class)装载进内存的, JVM规范定义了如下类型的类的加载器
-
引导类加载器: 用C++编写的, 是JVM自带的类加载器, 负责Java平台核心库, 用来装载核心类库, 该加载器无法直接获取
-
扩展类加载器: 负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
-
系统类加载器: 负责java -classpath或-D java.class.path所指的目录下的类与jar包装入工作库, 是最常用的加载器
package com.tyrant.Reflection; //类加载器 public class Demo06 { public static void main(String[] args) throws ClassNotFoundException { //获取系统类的加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 //获取系统类加载器的父类加载器-->扩展类加载器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1b6d3586 //获取扩展类的父类加载器-->根加载器 ClassLoader parent1 = parent.getParent(); System.out.println(parent1);//null //获取当前类的类加载器 Class<?> c1 = Class.forName("com.tyrant.Reflection.Demo06"); ClassLoader classLoader = c1.getClassLoader(); System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2 Class<?> c2 = Class.forName("java.lang.Object"); classLoader = c2.getClassLoader(); System.out.println(classLoader);//null //如何获得系统类加载器可以加载的路径 System.out.println(System.getProperty("java.class.path")); /* E:\Environment\java\jdk1.8\jre\lib\charsets.jar; E:\Environment\java\jdk1.8\jre\lib\deploy.jar; E:\Environment\java\jdk1.8\jre\lib\ext\access-bridge-64.jar; E:\Environment\java\jdk1.8\jre\lib\ext\cldrdata.jar; E:\Environment\java\jdk1.8\jre\lib\ext\dnsns.jar; E:\Environment\java\jdk1.8\jre\lib\ext\jaccess.jar; E:\Environment\java\jdk1.8\jre\lib\ext\jfxrt.jar; E:\Environment\java\jdk1.8\jre\lib\ext\localedata.jar; E:\Environment\java\jdk1.8\jre\lib\ext\nashorn.jar; E:\Environment\java\jdk1.8\jre\lib\ext\sunec.jar; E:\Environment\java\jdk1.8\jre\lib\ext\sunjce_provider.jar; E:\Environment\java\jdk1.8\jre\lib\ext\sunmscapi.jar; E:\Environment\java\jdk1.8\jre\lib\ext\sunpkcs11.jar; E:\Environment\java\jdk1.8\jre\lib\ext\zipfs.jar; E:\Environment\java\jdk1.8\jre\lib\javaws.jar; E:\Environment\java\jdk1.8\jre\lib\jce.jar; E:\Environment\java\jdk1.8\jre\lib\jfr.jar; E:\Environment\java\jdk1.8\jre\lib\jfxswt.jar; E:\Environment\java\jdk1.8\jre\lib\jsse.jar; E:\Environment\java\jdk1.8\jre\lib\management-agent.jar; E:\Environment\java\jdk1.8\jre\lib\plugin.jar; E:\Environment\java\jdk1.8\jre\lib\resources.jar; E:\Environment\java\jdk1.8\jre\lib\rt.jar; D:\Code\java\注解和反射\out\production\注解和反射; E:\IDEA\IntelliJ IDEA 2021.2.3\lib\idea_rt.jar */ //双亲委派机制 } }
4. 反射运用
4.1 获取运行时类的完整结构
通过反射获取运行时的完整结构
Field Method Constructor Superclass Interface Annotation
package com.tyrant.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; //获得类的信息 public class Demo07 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class<?> c1 = Class.forName("com.tyrant.reflection.User01"); //User01 user01 = new User01(); //c1 = user01.getClass(); //获得类的名字 System.out.println(c1.getName());//包名加类名 System.out.println(c1.getSimpleName());//类名 System.out.println("================"); //获得类的属性 Field[] fields = c1.getFields();//只能获得public的属性 /* for (Field field : fields){ System.out.println(field); } Field field = c1.getField("name"); System.out.println(field); */ fields = c1.getDeclaredFields();//能获得所有属性 for (Field field : fields){ System.out.println(field); } Field field = c1.getDeclaredField("name"); System.out.println(field); System.out.println("================"); //获得类的方法 Method[] methods = c1.getMethods();//获得本类及其父类的全部public方法 for (Method method : methods){ System.out.println("getMethods: " + method); } methods = c1.getDeclaredMethods();//获得本类的所有方法 for (Method method : methods){ System.out.println("getDeclaredMethods: " + method); } //获得指定方法 Method getName = c1.getDeclaredMethod("getName", null); System.out.println(getName); Method setName = c1.getDeclaredMethod("setName", String.class); System.out.println(setName); System.out.println("================"); //获得构造方法 Constructor<?>[] constructors = c1.getConstructors();//获得本类public构造方法 for (Constructor constructor : constructors){ System.out.println(constructor); } constructors = c1.getDeclaredConstructors();//获得本类的全部构造方法 for (Constructor constructor : constructors){ System.out.println(constructor); } //获得指定构造器 Constructor<?> constructor = c1.getConstructor(null); System.out.println(constructor); } }
4.2 动态创建对象执行方法
package com.tyrant.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //动态地创建对象, 通过反射 public class Demo08 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { //获得Class对象 Class<?> c1 = Class.forName("com.tyrant.reflection.User01"); //构造一个对象 User01 user01 = (User01)c1.newInstance(); System.out.println(user01); //通过构造器创建一个对象 Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class, int.class); User01 user02 = (User01) constructor.newInstance("张三", 1111, 18); System.out.println(user02); //通过反射调用普通方法 User01 user03 = (User01) c1.newInstance(); Method setName = c1.getDeclaredMethod("setName", String.class); //方法.invoke(对象, 方法的值) setName.invoke(user03,"李四");//激活 System.out.println(user03); //通过反射操作属性 User01 user04 = (User01) c1.newInstance(); Field name = c1.getDeclaredField("name"); //不能操作私有属性, 需要关闭程序的安全检测, 使用属性或者方法.setAccessible(true) name.setAccessible(true); name.set(user04, "王五"); System.out.println(user04); } }
4.3 性能对比分析
package com.tyrant.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; //性能分析 public class Demo09 { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { test01(); test02(); test03(); /* 普通方式调用一亿次的时间为: 3ms 反射方式调用一亿次的时间为: 176ms 反射方式(关闭检测)调用一亿次的时间为: 114ms */ } //普通方式调用 public static void test01(){ User01 user01 = new User01(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { user01.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式调用一亿次的时间为: " + (endTime - startTime) + "ms"); } //反射方式调用 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User01 user01 = new User01(); Class c1 = user01.getClass(); Method getName = c1.getDeclaredMethod("getName",null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { getName.invoke(user01,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式调用一亿次的时间为: " + (endTime - startTime) + "ms"); } //反射方式调用 关闭检测 public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User01 user01 = new User01(); Class c1 = user01.getClass(); Method getName = c1.getDeclaredMethod("getName",null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { getName.invoke(user01,null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式(关闭检测)调用一亿次的时间为: " + (endTime - startTime) + "ms"); } }
4.4 反射操作泛型
package com.tyrant.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 Demo10 { public static void main(String[] args) throws NoSuchMethodException { Method m1 = Demo10.class.getDeclaredMethod("test01", Map.class, List.class); Type[] types = m1.getGenericParameterTypes(); for (Type type : types){ System.out.println(type); if (type instanceof ParameterizedType) { Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); for (Type type1 : actualTypeArguments){ System.out.println(type1); } } } Method m2 = Demo10.class.getDeclaredMethod("test02"); Type returnType = m2.getGenericReturnType(); System.out.println(returnType); if (returnType instanceof ParameterizedType){ Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments(); for (Type type01 : actualTypeArguments){ System.out.println(type01); } } } public void test01(Map<String, User01> map, List<User01> list){ System.out.println("test01"); } public Map<String, User01> test02(){ System.out.println("test02"); return null; } }
4.5 反射操作注解
package com.tyrant.reflection; import java.lang.annotation.*; import java.lang.reflect.Field; //练习反射操作注解 public class Demo11 { public static void main(String[] args) throws NoSuchFieldException { Class<Student02> c1 = Student02.class; //通过反射获得注解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations){ System.out.println(annotation); } //获得指定的注解value的值 ClassT classT = c1.getAnnotation(ClassT.class); String value = classT.value(); System.out.println(value); //获得类指定的注解 Field field = c1.getDeclaredField("name"); FieldT annotation = field.getAnnotation(FieldT.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length()); } } @ClassT("student") class Student02{ @FieldT(columnName = "db_name", type = "varchar", length = 10) private String name; @FieldT(columnName = "db_age", type = "int", length = 3) private int age; @FieldT(columnName = "db_id", type = "int", length = 10) private int id; public Student02() { } public Student02(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public String toString() { return "Student02{" + "name='" + name + '\'' + ", age=" + age + ", id=" + id + '}'; } } //类名的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface ClassT{ String value(); } //属性的注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FieldT{ String columnName(); String type(); int length(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现