注解 & 反射
一、注解
1.1、什么是注解
- Annotation 是 JDK 5.0 开始引入的
- Annotation 的作用:
- 不是程序本身,可以对程序作出解释。(这一点和注释(注解)没什么区别)
- 不可以被其他程序(比如:编译器等)读取
- Annotation 的格式:
- 注解是以
@注释名
在代码中存在的,还可以添加一些参数值,例如:@SuppressWarning(value=”unchecked”)
- 注解是以
- Annotation 使用范围(
package
、class
、method
、field
等上面),相当于给它们添加了额外的辅助信息,我们可以通过反射机制编程通过对这些元数据的访问。
1.2、注解分类
1.2.1、内置注解
@Override
:定义在java.lang.Override
中,此注解只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。@Deprecated
:定义在java.lang.Deprecated
中,此注解可用于修辞方法、属性、类,表示不鼓励使用这样的元素,通常因为它存在危险或存在更好的选择。@SupressWarning
:定义在java.lang.SupressWarning
中,用来抑制编译时的警告信息- 与前两种注释有所不同,你需要添加一个参数才能正常使用,这些参数都是已经定义好了的。
@SuppressWarning("all")
@SuppressWarning("unchecked")
@SuppressWarning(value={"unchecked","deprecation"})
- …
- 与前两种注释有所不同,你需要添加一个参数才能正常使用,这些参数都是已经定义好了的。
@SafeVarargs
:当使用可变数量的参数的时候,而参数的类型又是泛型T的话,就会出现警告。 这个时候,就使用@SafeVarargs来去掉这个警告。只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。@FunctionalInterface
:用于约定函数式接口,函数式接口其存在的意义,主要是配合Lambda 表达式 来使用1.2.2、元注解
元注解作用就是负责注解其它注解,Java 定义了 4 个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型作说明
这些类型和她们所支持的类在
java.lang.annotation
包中可以找到。(@Target
,@Retention
,Documented
,Inherited
)@Target
:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)@Retention
:表示需要在什么级别保留注解信息,用于描述注解的生命周期(Source<Clas<Runtime)@Documented
:说明该注解将被包含在 javadoc 中@Inherited
:说明子类可以继承父类中的该注解@Repeatable
:当没有@Repeatable修饰的时候,注解在同一个位置,只能出现一次
@Target
中的属性说明:
ElementType.TYPE:能修饰类、接口或枚举类型
ElementType.FIELD:能修饰成员变量
ElementType.METHOD:能修饰方法
ElementType.PARAMETER:能修饰参数
ElementType.CONSTRUCTOR:能修饰构造器
ElementType.LOCAL_VARIABLE:能修饰局部变量
ElementType.ANNOTATION_TYPE:能修饰注解
ElementType.PACKAGE:能修饰包
@Retention
中的属性说明
RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解。
RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了。@Retention的默认值,即当没有显式指定@Retention的时候,就会是这种类型。
RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息,自定义注解@JDBCConfig 就是这样。
1.3、自定义注解
下面简单自定义一个注解,如果注解中是有一个属性,且属性名是 value,那么在使用注解时刻可以直接填写属性值,不需要再使用 属性名 = 属性值,如果自定了默认值,则可以不需要填写
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation{
private String value() defalut "";
}
二、反射
2.1、什么是反射
- Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflection API 取得任何类的内部信息,并能够直接操作任意对象的内部属性以及方法。
Class c = Class.forName("java.lang.String")
- 加载完类后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射
2.2、Java 反射机制研究及应用
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取范型信息
- 在运行时调用任意一个对象的成员方法和变量
- 在运行时处理注解
- 生成动态代理
- 。。。
2.3、Java 反射优点和缺点
优点:可以实现动态创建对象和编译,灵活性高
缺点:对性能有影。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的需求。这类操作总是慢于直接执行相同的操作。
2.4、反射相关的主要 API
java.lang.Class
:代表一个类java.lang.reflection.Method
:代表类的方法java.lang.reflection.Field
:代表类的成员变量java.lang.reflection.Constructor
:代表类的构造器- 。。。
Java 中,在 Object 类中定义了以下方法,此方法将被所有子类继承 public final native Class<?> getClass();
,此方法返回值类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
2.5、Class 类的常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
T newInstance() | 调用缺省构造函数,返货 Class 对象的一个实例 |
String getName() | 返回此 Class 对象所表示的实体(类,接口,数组类或 void) |
Class getSuperClass() | 返回当前 Class 对象的父类的 Class 对象 |
Class[] getInterfaces() | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Constructor[] getConstructor | 返回一个包含某些 Constructor 对象的数组 |
Method getMethod(String name,Class… T) | 返回一个 Method 对象,此对象的形参类型为 paramType |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
注意,对于 private 修饰的成员,需要先设置关闭安全检验 setAccessible(true)
2.6、获取 Class 对象的实例
1.若已知具体的类,通过该类的 class 属性获取,该方法最为安全可靠,程序性能最高。Class clazz = Person.class;
2.已知某个类的实例,调用该实例的 getClass()
方法获取 Class
对象Class clazz = person.getClass();
3.已知一个全类名,且该类在类路径下,可通过 Class 类的静态方法 forName()
获取,可能抛出 ClassNotFoundExceptionClass clazz = Class.forName("demo01.Person")
4.内置基本数据类型可以直接使用类名.Type
5.还可以利用 ClassLoader
获取类的实例的效率:new > 反射(关闭安全检验)>反射
2.7、类加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤对该类进行初始化。类的加载(Load)
:将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成类链接(Link)
:将类的二进制数据合并到 JRE 中类的初始化(Initialize)
:JVM 负责对类进行初始化
2.8、类的加载与 ClassLoader 的理解
2.8.1、类的加载
- 加载:将 class 文件字节码文件内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class 对象。
- 链接:将 Java 类的二进制代码合并到 JVM 的运行状态中的过程
- 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题
- 准备:正式为类变量(static) 分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器
<clinit>()
方法的过程。类构造器<clinit>()
方法是由编译期自劫收集类中所有类変量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步。
- 执行类构造器
2.8.2、类加载器的作用
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类的类的加载器。自定义类加载器
->System ClassLoader
->Extension ClassLoader
->Bootstap ClassLoader
装载的过程是自底向上检查是否已装载,即从左到右
加载类的顺序是自顶向下
引导类加载器:用 C++ 编写的。是 JVM 自带的类加载器,负责 Java 平台核心库,用来装载核心类库。该加载器无法直接获取
拓展类加载器:负责 jre/lib/ext 目录下的 jar 包或 -D java.ext.dirs 指定目录下的 jar 包装入工作库
系统类加载器:负责 java -classpath 或 -D java.class.path 所指的目录下的类与 jar 包装入工作,是最常用的加载器。
2.9、反射操作泛型
- Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是, 一旦编译完成,所有和泛型有关的类型全部擦除
- 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
ParameterizedType
:表示一种参数化类型,比如Collection<String>
GenericArrayType
:表示一种元素类型是参数化类型或者类型变量的数组类型TypeVariable
:各种类型的公共父接口WildcardType
:代表一种通配符类型表达式
示例
public class ReflectionGetGeneric {
public void test01(Map<String, User> map, List<User> list) {
System.out.println("test01");
}
public Map<String,User> test02() {
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method01 = ReflectionGetGeneric.class.getMethod("test01", Map.class, List.class);
// 获得范型的参数类型
Type[] genericParameterTypes = method01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
// 范型的参数类型是否等于结构化的参数类型
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
Method method02 = ReflectionGetGeneric.class.getMethod("test02", null);
Type genericReturnType = method02.getGenericReturnType();
System.out.println(genericReturnType);
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
输出结果
java.util.Map<java.lang.String, annotation_reflection._04.User>
class java.lang.String
class annotation_reflection._04.User
java.util.List<annotation_reflection._04.User>
class annotation_reflection._04.User
java.util.Map<java.lang.String, annotation_reflection._04.User>
class java.lang.String
class annotation_reflection._04.User
2.10、ORM(Object Relationship Mapping)练习
很多框架中都会使用到注解开发,例如 MybatisPlus,只需要在数据库映射的实体类中,加上@TableName
即可映射表名,@TableField
即可映射表中的字段名
下面通过代码示例
自定义两个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TableName {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TableField {
String columnName();
String type();
int length();
}
定义实体类
@TableName("db_student")
public class Student {
@TableField(columnName = "db_id",type = "int",length = 10)
private int id;
@TableField(columnName = "db_age",type = "int",length = 10)
private int age;
@TableField(columnName = "db_name",type = "varchar",length = 3)
private String name;
public Student() {
}
@Override
public String toString() {
return "Student2{" +
"id=" + id +
", age=" + age +
", 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;
}
public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}
测试
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("annotation_reflection._07.Student2");
// 通过反射获得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 获得注解的具体的值
TableName tableAnnotation = (TableName) c1.getAnnotation(TableName.class);
System.out.println(tableAnnotation.value());
// 获得指定的注解
Field name = c1.getDeclaredField("name");
TableField fieldAnnotation = name.getAnnotation(TableField.class);
System.out.println(fieldAnnotation.columnName());
System.out.println(fieldAnnotation.type());
System.out.println(fieldAnnotation.length());
}
运行结果
@annotation_reflection._07.TableName(value=db_student)
db_student
db_name
varchar
3
本作品采用《CC 协议》,转载必须注明作者和本文链接