Java注解和反射【笔记】
注解和反射
什么是注解?
Annotation:
- 作用:
- 不是程序本身,可以对程序做解释;
- 可以被其他程序读取(比如编译器);
- 格式:
- 以“@注释名“在代码中存在的,还可以添加一些参数值;
- 使用场景:
- 可以附加在package,class,method,field等上,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问;
1、内置注解
- @Override:
- @Deprecated:
- @SuppressWarnings:镇压警告。用于抑制编译时的警告信息,需要添加一个参数才能够使用;
2、元注解
- meta-annotation:
- @Target:用于描述注解的使用范围(即注解可以用在什么地方);
- @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期。(SOURCE<CLASS<RUNTIME)
- @Documented:说明该注解被包含在javadoc中;
- @Inherited:说明子类可以继承父类中的该注解;
自定义元注解:
package com.company.annotaion; import java.lang.annotation.*; /** * 自定义元注解 * 2022年3月8日13:55:00 */ @MyAnnotation//ElementType.TYPE public class Test01 { @MyAnnotation//ElementType.METHOD public void test(){ } } //定义一个注解 //@Target表示我们的元注解能作用的范围 @Target({ElementType.TYPE,ElementType.METHOD}) //Retention 表示我们的注解有效的时期 //RUNTIME>CLASS>SOURCES @Retention(value = RetentionPolicy.RUNTIME) //Documented 表示是否将我们的注解生成在JavaDoc中 @Documented //Inherited 子类可以继承父类的注解 @Inherited @interface MyAnnotation{ }
自定义进阶:
package com.company.annotaion; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class Test02 { @MyAnnotation02 public void test01(){} @MyAnnotation03("如果用value作为变量名,可以直接写值") public void test02(){} } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation02{ //注解的参数:参数类型 + 参数名 + () ; String name() default ""; int age() default 3 ; int id() default -1 ;//如果默认值为-1,代表不存在 String [] School() default {"清华大学"}; } @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation03{ String value(); }
3、反射
- 动态语言:
- 一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗而言就是在运行时代码可以根据某些条件改变自身结构;
- 主要动态语言:Objec-C、C#、JavaScript、PHP、Python等;
- 静态语言:
- 与动态语言相对,运行时不可改变语言的就是静态语言。如Java、C、C++;
- Java不是动态语言,但可以称之为“准动态语言”。即Java拥有一定的动态性,我们可以灵活的利用反射机制获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
测试反射的代码:
package com.company.reflection; /** * 什么是反射 * 2022年3月8日15:35:57 */ public class Test01 { public static void main(String[] args) throws ClassNotFoundException { //通过反射获取类的Class对象 Class c1 = Class.forName("com.company.reflection.User"); System.out.println(c1); Class c2 = Class.forName("com.company.reflection.User"); Class c3 = Class.forName("com.company.reflection.User"); Class c4 = Class.forName("com.company.reflection.User"); //一个类在内存中只有唯一的一个Class对象; //一个类被加载后,类的整个结构都被封装在Class对象中; System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); System.out.println(c4.hashCode()); } } //实体类 : pojo , entity class User{ private String name; private int id; private int age; public User() { } public User(String name, int id, int age) { this.name = name; this.id = id; this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", id=" + id + ", age=" + age + '}'; } public void setName(String name) { this.name = name; } public void setId(int id) { this.id = id; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public int getId() { return id; } public int getAge() { return age; } }
反射机制的利弊:
- 优点:
- 可以实现动态创建对象和编译,体现了很大的灵活性;
- 缺点:
- 对性能有影响。使用反射基本上是一种解释操作,通过JVM满足我们的操作。这类操作总是慢于直接执行相同的操作。
哪些类型可以有Class对象?
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
- interface:接口;
- []:数组
- enum:枚举
- annotation:注解@interface;
- primitive type : 基本数据类型;
- void;
Class c1 = Object.class;//类 Class c2 = Comparable.class;//接口 Class c3 = String[].class;//一维数组 Class c4 = int[][].class;//二维数组 Class c5 = Override.class;//注解 Class c6 = ElementType.class;//枚举 Class c7 = Integer.class;//基本数据类型 Class c8 = void.class;//void Class c9 = Class.class;//Class
只要元素的类型与维度一样,就是同一个class
内存分析
- 方法区(特殊的堆)
- 堆:
- 栈:
类的加载与ClassLoader的理解
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的
java.lang.Class
对象; - 链接:将Java类的二进制代码合并到JVM的运行状态之中。
- 验证:确保加载的类信息符合JVM的规范,没有安全问题;
- 准备:正式为类变量(static)分配内存并设置类变量默认值初始值,这些内存都将在方法区中进行分配;
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程;
- 初始化:
- 执行类构造器
<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器!!); - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需先触发其父类的初始化;
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步!
- 执行类构造器
类的初始化
类的初始化时机:
- 类的主动引用(一定会发生类的初始化)
- 虚拟机启动时,先初始化main方法所在的类;
- new一个类的对象时(实例化);
- 调用类的静态成员(除了final常量)和静态方法;
- 使用
java.lang.reflect
包的方法对类进行反射调用; - 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类;
- 类的被动引用(不会发生类的初始化!!)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化;
- 通过数组定义类引用,不会触发此类的初始化(这种操作只是像内存声明了一片空间);
- 引用常量不会触发此类的初始化(常量在连接阶段就已经存入调用类的常量池中了);
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
通过反射获得类的属性和方法:
package com.company.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 通过反射获得类的信息(属性和方法) */ public class Test02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class<?> c1 = Class.forName("com.company.reflection.User"); User user = new User(); c1 = user.getClass(); //获得类的名字 System.out.println(c1.getName());//包 + 类 名 System.out.println(c1.getSimpleName());//类名 System.out.println("-------------------------------------------------"); //获得类的属性 Field[] fields = c1.getFields();//getFields只能获得public属性 for (Field field : fields) { System.out.println(field); } for (Field declaredField : c1.getDeclaredFields()) {//getDeclaredFields():能够获得所有属性 System.out.println("getDeclaredFields():"+declaredField); } Field name = c1.getDeclaredField("name");//获得指定属性的值 System.out.println(name); System.out.println("-------------------------------------------------"); //获得类的方法 for (Method method : c1.getMethods()) {//获得本类及其父类的public方法 System.out.println(method); } for (Method declaredMethod : c1.getDeclaredMethods()) {//仅仅获得本类的所有方法 System.out.println("declaredMethod:"+declaredMethod); } //获得指定的方法 Method getName = c1.getMethod("getName",null); Method setName = c1.getMethod("setName",String.class);//针对重载机制,需要传参 System.out.println("获得指定的方法:"+getName); System.out.println("获得指定的方法:"+setName); System.out.println("-------------------------------------------------"); //获得指定构造器 for (Constructor<?> constructor : c1.getConstructors()) { System.out.println(constructor); } for (Constructor<?> declaredConstructor : c1.getDeclaredConstructors()) { System.out.println("declaredConstructor:-->"+declaredConstructor); } Constructor<?> constructor = c1.getConstructor(String.class, int.class, int.class); System.out.println("获得指定构造器:"+constructor); } }
注意:
getDeclaredxxxxx
可以获得本类的所有方法getxxxxx
会获得所有public方法(包括本类和父类的);
package com.company.reflection; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 动态创建对象,通过反射 */ public class Test03 { public static void main(String[] args) throws Exception { //获得Class对象 Class<?> c1 = Class.forName("com.company.reflection.User"); //构造一个对象 User user = (User) c1.newInstance();//本质上这里调用了无参构造器!! //在JDK9版本后 class.newInstance()方法被弃用; // 可以使用class.getDeclaredConstructor().newInstance();调用构造器来实例化对象 User user2 = (User)Class.forName("com.company.reflection.User").getDeclaredConstructor(String.class,int.class,int.class).newInstance("有参构造器",001,18); System.out.println(user+"\t"+user2); //通过反射调用普通方法; System.out.println("----------------------------------"); Method setName = c1.getDeclaredMethod("setName", String.class); setName.invoke(user,"user:通过反射方法改变的名字!!");//invoke(对象,参数):激活 System.out.println(user.getName()); //通过反射操作属性 System.out.println("----------------------------------"); User user3 = (User) c1.newInstance(); Field name = c1.getDeclaredField("name"); name.setAccessible(true);//name.setAccessible(true),关闭安全监测,开启访问权限!!访问私有属性 name.set(user3,"反射直接修改属性"); System.out.println(user3.getName()); } }
分析反射机制的效率:
调用10亿次getName()耗时对比:
package com.company.reflection; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 监测不同机制访问速度的区别 */ public class Test04 { 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("普通方式耗时:"+(endTime-startTime)+"ms"); } //通过反射机制 public static void test02() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { User user = (User) Class.forName("com.company.reflection.User").getDeclaredConstructor().newInstance(); Method getName = Class.forName("com.company.reflection.User").getDeclaredMethod("getName", null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user); } long endTime = System.currentTimeMillis(); System.out.println("反射调用方法耗时:"+(endTime-startTime)+"ms"); } //通过反射机制 public static void test03() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { User user = (User) Class.forName("com.company.reflection.User").getDeclaredConstructor().newInstance(); Method getName = Class.forName("com.company.reflection.User").getDeclaredMethod("getName", null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user); } long endTime = System.currentTimeMillis(); System.out.println("关闭安全监测后的反射方法耗时:"+(endTime-startTime)+"ms"); } public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { test01(); test02(); test03(); } }
测试结果:
通过反射获得泛型参数信息
package com.company.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 Test05 { //test01方法 public void test01(Map<String,User> map, List<User> list){ System.out.println("test01"); } //test02方法 public Map<String,User> test02(){ System.out.println("test02"); return null; } public static void main(String[] args) throws NoSuchMethodException { Method method = Test05.class.getMethod("test01", Map.class, List.class); Type[] genericParameterTypes = method.getGenericParameterTypes();//获得参数泛型类型 for (Type genericParameterType : genericParameterTypes) { System.out.println("###"+genericParameterType); if (genericParameterType instanceof ParameterizedType){//判断:如果 泛型参数类型 属于 参数化类型 //把泛型信息转换为真正的泛型类型 for (Type actualTypeArgument : ((ParameterizedType) genericParameterType).getActualTypeArguments()) { System.out.println(actualTypeArgument); } } } } }
通过反射获得注解
-
ORM:
- Object relationship Mapping--->对象关系映射
- 类和表结构对应
- 属性和字段对应
- 对象和记录对应
-
利用注解和反射完成类和表结构的映射关系:
package com.company.reflection; import java.lang.annotation.*; import java.lang.reflect.Field; /** * 练习反射和注解! */ public class Test06 { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class<?> c1 = Class.forName("com.company.reflection.Student"); //通过反射获得注解 for (Annotation annotation : c1.getAnnotations()) { System.out.println(annotation); } //获得注解的value值 MyTable myTable = (MyTable) c1.getAnnotation(MyTable.class); String value = myTable.value(); System.out.println(value); //获得类指定的注解 Field f = c1.getDeclaredField("name"); MyField myField = f.getAnnotation(MyField.class); System.out.println(myField.columnName()); System.out.println(myField.type()); System.out.println(myField.length()); } } @MyTable("db_student") class Student{ @MyField(columnName= "db_id",type="int",length = 10) private int id; @MyField(columnName= "db_age",type="int",length = 10) private int age; @MyField(columnName= "db_name",type="varchar",length = 3)//字符串的注解为:varchar private String name; public Student() {} public Student(int id, int age, String name) { this.id = id; this.age = age; 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; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } } /* 类名的注解 */ @Target(ElementType.TYPE)//作用在类上 @Retention(RetentionPolicy.RUNTIME) @interface MyTable{ String value(); } //属性的注解 @Target(ElementType.FIELD)//作用在属性上 @Retention(RetentionPolicy.RUNTIME) @interface MyField{ String columnName(); String type(); int length(); }
通过反射得到类/属性的注解,
MyTable myTable = (MyTable) c1.getAnnotation(MyTable.class);
将得到返回的注解转型成我们已知的注解类型对象,在通过这个对象就可以的到注解的所有属性值(value);
这在学习框架时经常使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律