注解和反射
声明
本文部分内容参考自CSDN博主万里顾—程,仅用作个人学习,特此声明
1.原文链接:https://blog.csdn.net/wpc2018/article/details/115252873
3.java占位符%d,%s等的使用 - 香吧香 - 博客园 (cnblogs.com)
2用于解释1中泛型
3用于解释2程序中的占位符
Java进阶——注解和反射
1、注解
1.1、什么是注解(Annotation)
从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,
注解的作用:
- 不是程序本身,可以对程序作出解释
- 可以在程序编译,类加载,运行时被读取,并执行相应的处理。
注解的格式:
- 注解是以"@注释名"在代码中存在的,还可以添加一些参数值,例如:@SuppersWarnings(valus=“unchecked”)
注解在哪里使用:
- 可以附加在package,class,method,filed等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。
1.2、内置注解
-
1.限定父类重写方法:@Override
当子类重写父类方法时,子类可以加上这个注解,那这有什么什么用?这可以确保子类确实重写了父类的方法,避免出现低级错误
@Override //该注解标识的类,代表的覆盖的是其父类的方法
public boolean equals(Object obj) {
return super.equals(obj);
2.标示已过时:@Deprecated
这个注解用于表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告(删除线,这个见了不少了吧)。
@Deprecated //该注解标识的类,代表该类已经过时
public static void sayHello() {
System.out.println("hello world");
}
3.抑制编译器警告:@SuppressWarnings
被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告,例如修饰一个类,那他的字段,方法都是显示警告
@SuppressWarnings("deprecation")
public static void main(String[] args) {
//这个类是过时的,使用@SuppressWarnings来压制警告
System.runFinalizersOnExit(true);
}
4.“堆污染”警告与@SafeVarargs
想理解这个就要明白什么是堆污染,堆污染是什么?
其实很好理解,就是把不带泛型的对象赋给一个带泛型的对象,为什么不行?很简单,因为不带泛型的话,默认会给泛型设定为object,意思就是什么类型都可以往里面塞,那你一个不带泛型的怎么可能给一个带泛型塞呢。
例如运行如下代码:
List list = new ArrayList(); list.add(20); List ls = list; System.out.println(ls.get(0));
则会抛出堆污染异常Exception in thread “main”java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Test.Test1.main(Test1.java:29)
注意:可变参数更容易引发堆污染异常,因为java不允许创建泛型数组,可变参数恰恰是数组。
抑制这个警告的方法有三个:
1.@SafeVarargs修饰引发该警告的方法或构造器
2.使用@suppressWarnings(“unchecked”)
3.编译时使用-Xlint:varargs
5.函数式接口与@Functionallnterface
什么是函数式?如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法)
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
接口里面不能有私有的方法或变量。
这个注解有什么用?这个注解保证这个接口只有一个抽象方法,注意这个只能修饰接口
1.3、自定义注解和元注解(meta-annotation)
定义注解:
- 第一步,用
@interface
定义注解:
public @interface Report {
}
- 第二步,添加参数、默认值,如果没有默认值,就必须给参数赋值:
public @interface Report {
//参数类型 +参数名()
int type() default 0;
String level() default "info";
String value() default "万里";
}
把最常用的参数定义为value()
,推荐所有参数都尽量设置默认值。
- 第三步,用元注解配置注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
其中,必须设置@Target
和@Retention
,@Retention
一般设置为RUNTIME
,因为我
们自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited
和@Repeatable
。
在使用的时候,如果一个类用到了我们定义的注解,则它的子类也定义该注解
@Report(type=1)
public class Person {
}
public class Student extends Person {
}
元注解:
- 元注解的作用就是负责注解其他注解,Java定义了4个标准的元注解类型,他们被用来提供对其他注解类型作说明
- 这些类型和他们所支持的类在java.lang.annotation包中可以找到
1、@Target:用于描述注解的使用范围
-
类或接口:
ElementType.TYPE
; -
字段:
ElementType.FIELD
; -
方法:
ElementType.METHOD
; -
构造方法:
ElementType.CONSTRUCTOR
; -
方法参数:
ElementType.PARAMETER
。
public class Test{
@MyAnnotation
public void test(){
}
}
//定义一个注解MyAnnotation,再给注解加上一个元注解
@Target(value = ElementType.METHOD)//表示该注解只能用在方法上
//可以设置多个范围:@Target(value ={ElementType.METHOD,ElementType.type})
@interface MyAnnotation{
}
2、@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
-
仅编译期:
RetentionPolicy.SOURCE
; -
仅class文件:
RetentionPolicy.CLASS
; -
运行期:
RetentionPolicy.RUNTIME
。范围:RUNTIME>CLASS>SOURCE
如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)
这个元注解:
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
}
3、@Document:说明该注将被包含在javadoc中
4、@Iherited:使用@Inherited定义子类是否可继承父类定义Annotation。
@Inherited仅针对 @Target(ElementType.TYPE)
类型的annotation有效,并且仅针对
class的继承,对interface的继承无效:
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
2、反射
1、反射和反射机制
反射(Reflection):
Java的反射是指程序在运行期可以拿到一个对象的所有信息。
反射的优点和缺点:
优点:可以实现动态创建对象和编译,灵活性大
缺点:对性能有影响,反射操作总是慢于直接执行相同操作
反射机制:
Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用,操作任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言(在程序运行的时候可以改变其结构)的关键。
2、CLass类
java.lang.Class类,实现反射的核心类
- Class类只能由系统建立对象
- 一个加载的类在内存(JVM)中只有一个Class对象
- 一个类被加载后,类的整个结构都会被封装在Class对象中
- 每个类的实例都会记得自己是由哪个Class实例所生成
- Class类是反射的根源,针对任何你想动态加载,运行的类,唯有获得相应的Class对象
获得Class类实例的五种方式:
//方式一:调用Class类的静态方法 forName(String className)
Class c1 = Class.forName("com.cheng.reflection.User");
//方式二 已知某个类的实例,调用该实例的getClass()方法,getClass是Object类中的方法、因为所有类都继承Object类。
Class c2 = user.getClass();
//方式三 已知具体类,通过类的class属性获取,该方法最安全可靠,程序性能最高
Class c3 = User.class;
//方式四:通过基本内置类型的包装类的TYPE属性获得CLass实例
//以int的包装类Intege类为例 源码:public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
Class<Integer> c4 = Integer.TYPE;
//方式五:通过当前子类的Class对象获得父类的Class对象
Class c5 = c1.getSuperclass();//c1为子类的CLass对象
Class类的常用方法:
方法名 | 功能说明 |
---|---|
static ClassforName(String name) |
返回指定类名name的Class对象 |
Object newInstance() |
调用无参构造函数,返回Class对象的一个实例 |
getName() |
返回此Class对象所表示的实体(类,接口,数组类或void)的名称 |
Class getSuperclass() |
返回当前Class对象的父类的Class对象 |
Class[] getinterfaces() |
返回当前Class对象的接口 |
ClassLoader getClassLoader() |
返回该类的类加载器 |
Method getMethod(name,String .class) |
返回对象Method一个数组,此对象的形参类型为paramType |
Field getDeclaredFields() |
返回对象的Field(属性)一个数组 |
Constructor[] getConstructors() |
返回一个包含某些Constructor(构造器)对象的数组 |
3、哪些类型可以有Class对象
-
class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
Class c1 = Object.class;
-
interface:接口
Class c2 = Comparable.class;
-
[]:数组
Class c3 = String[].class; Class c4 = int[].class;
-
enum:枚举
Class c5 = ElementType.class;
-
annotation:注解@interface
Class c6 = Override.class;
-
primitive type:基本数据类型
Class c7 = Interger.class;
-
void
Class c8 = void.class; Class c9 = Class.class;
4、类加载理解
- 加载:将class字节码文件内容加载到内存中,并将这些数据转换成方法区的运行数据结构,然后生成一个代表这个类的java.lang.Class对象
-
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法去中进行分配
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
-
初始化:
-
执行类构造器
<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块 -
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
-
虚拟机会保证一个类在多线程环境中被正确加锁和同步。
-
什么时候会发生类初始化:
-
类的主动引用(一定会发生类初始化)
-
当虚拟机启动,先初始化mian方法所在的类
-
new一个类的对象
-
调用类的静态成员(处理final常量)和静态方法
-
使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则先初始化其父类
-
-
类的被动引用(不会发生类初始化)
-
当访问一个静态域时,只有正真声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
-
通过数组定义类引用,不会发生类初始化
-
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
-
5、类加载器
1、概念,作用,类型
类加载器:完成类的加载
类加载器的作用:将class字节码文件内容加载到内存中,并将这些数据转换成方法区的运行数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
JVM三种预定义类型类加载器,当JVM启动的时候,Java开始使用如下三种类型的类加载器:
扩展类加载器是系统类加载器的父类加载器,根类加载器是扩展类加载器的父类加载器
-
根/启动(Bootstrap)类加载器
:根类加载器是用本地代码实现的类加载器,它负责将JAVA_HOME/lib下面的核心类库或-Xbootclasspath选项指定的jar包等虚拟机识别的类库加载到内存中。由于根类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到根类加载器的引用。 -
扩展(Extension)类加载器
:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将JAVA_HOME /lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。 -
系统(System)类加载器
:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径)下的类库加载到内存中。开发者可以直接使用系统类加载器。
2、类加载三种机制
- 全盘负责机制:就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
-
双亲委派机制:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。
-
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。
这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效。
3、eg.获取类加载器
- 获取系统类加载器:
ClassLoader.getSystemClassLoader();
- 获取扩展类加载器:通过其子类加载器系统类加载器获取,
systemClassLoader.getParent();
- 获取根类加载器:通过其子类加载器扩展类加载器获取,
parent.getParent();
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取扩展类加载器的父类加载器-->根类加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null
4、类加载的简单使用
//测试当前类是由哪个类加载器加载的
ClassLoader classLoader = Class.forName("com.cheng.annotation.Test01").getClassLoader(); System.out.println(classLoader);
//sun.misc.Launcher$AppClassLoader@18b4aac2 系统类加载器
//测试JDK内置类是由哪个类加载器加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);//null 根类加载器
//获得系统类加载器可以加载的路径
String property = System.getProperty("java.class.path");
System.out.println(property);
总结:
当前自写类由系统加载器加载,JDK内置类由根类加载器加载
6、通过反射动态创建对象
通过构造器创建对象
//通过构造器创建对象
Class c1 = class.forName("com.xy.User");
Constructor constructor = c1.getDeclaredConstructor(String.class,int.class,int.class);
User user = (User)constructor.newInstance("xy",1,18);
System.out.println(user);//USer{name='xy',id=1,age=18}
调用指定方法
Class c1 = class.forName("com.xy.User");
User user = (USer)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName",String.class);
//invoke激活
setName.invoke(user,"xy");
System.out.println(user,getName());//xy
7、用反射操作注解
类名的注解
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableStudnet{
String value();
}
属性的注解
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
String columnName();
String type();
int length();
}
实体类调用注解
@TableStudnet("学生")
class Studnet{
@Filed(columnName = "身份证号",type="基本数字类型",length=10)
private int id;
@Filed(columnName = "年龄",type="基本数字类型",length=10)
private int age;
@Filed(columnName = "姓名",type="字符串类型",length=3)
private String name;
//..省略构造器和封装方法
}
运行(还是不太理解)
//运行
public class demo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获得
Class c1 = Class.forName("com.xy.JavaSE.Reflection.Studnet");//将此路径下的Student类返回给c1
//获取TableStudnet注解的value的值
TableStudnet annotation = (TableStudnet) c1.getAnnotation(TableStudnet.class);//得到Student类的注解TableStudent返回给annotation
String value = annotation.value();//获取注解的value的值
System.out.println(value);
//获得Filed注解上属性的值
//c1.getDeclaredFields("name"); 获取单个属性
Field[] f = c1.getDeclaredFields();//获取属性注解返回给f
//循环获取Field注解上所有属性的值
for (Field field : f) {
Filed fAnnotation = field.getAnnotation(Filed.class);
System.out.println("fAnnotation.columnName="+fAnnotation.columnName());
System.out.println("fAnnotation.type="+fAnnotation.type());
System.out.println("fAnnotation.length="+fAnnotation.length());
}
}
}
结果
总结操作步骤
- 自定义注解:写一个属性注解
- 第一步,用
@interface
定义注解:
//属性的注解
@interface Filed{
}
- 第二步,添加参数、默认值,如果没有默认值,就必须给参数赋值。可以在实体类调用该注解时赋值
//属性的注解
@interface Filed{
String columnName();
String type();
int length();
}
把最常用的参数定义为value()
,推荐所有参数都尽量设置默认值。
- 第三步,用元注解配置注解:
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
String columnName();
String type();
int length();
}
-
用同样的方式写一个类名注解
-
写一个实体类用于调用这两个注解
-
写运行代码去实现对所有注解值的获取
这一步不太理解,回看一下!!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律