【Java】注解
本文为面向对象课程期末作业
简介
- Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
- 注解将补充信息嵌入源文件中,可以对程序作出解释,可以被编译器读取,不会更改程序的语义。
- 注解是以
@注解名
在代码中存在的,例如@Override
是对重写方法的注解;注解中还可以天街一些参数值,例如@SuppressWarnings(valus="unchecked")
。 - Java 语言中的类,构造器,方法,成员变量,参数都可以被注解进行标注,可以通过反射机制通过编程实现对这些元数据的访问。
- 注解 \(\neq\) 注释。
内置注解
例——Override:
Override
表示一个方法声明打算重写超类中的另一个方法声明。- 定义在 java.lang.Override 中,只适用于修饰方法。
public class User {
String name;
int age;
@Override
public String toString() {
return String.format("姓名: %s, 年龄: %s\n", name, age);
}
}
- 如果将
toString
方法改成tostring
,编译器就会报错,这就是Override
的作用。
例——Deprecated:
Deprecated
表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。- 定义在 java.lang.Deprecated 中,可以用于修饰方法、属性、类,
public class User {
String name;
int age;
@Deprecated
public void setAge1 (int age) {
this.age = age;
}
public void setAge2 (int age) {
if (age < 0 || age > 120) {
System.out.println("年龄不合法");
} else {
this.age = age;
}
}
}
- 如果调用
setAge1
则会出现警告“setAge1(int) is deprecated”
。
例——SuppressWarnings:
SuppressWarnings
可以抑制编译时警告信息。- 定义在 java.lang.SuppressWarnings 中。
- 与
@Override
和@Deprecated
不同,需要添加参数才能正确使用:- 可以只添加一个参数,如
@SuppressWarnings("unchecked")
;也可以添加多个参数,@SuppressWarnings("serial", "deprecation")
。 - 更多参数举例:
all
:抑制所有警告boxing
:抑制装箱、拆箱操作时候的警告cast
:抑制映射相关的警告dep-ann
:抑制启用注释的警告deprecation
:抑制过期方法警告fallthrough
:抑制在 switch 中缺失 breaks 的警告finally
:抑制 finally 模块没有返回的警告hiding
:抑制相对于隐藏变量的局部变量的警告incomplete-switch
:忽略不完整的 switch 语句nls
:忽略非 nls 格式的字符null
:忽略对 null 的操作rawtypes
:使用 generics 时忽略没有指定相应的类型restriction
:抑制禁止使用劝阻或禁止引用的警告serial
:忽略在 serializable 类中没有声明 serialVersionUID 变量static-access
:抑制不正确的静态访问方式警告synthetic-access
:抑制子类没有按最优方法访问内部类的警告unchecked
:抑制没有进行类型检查操作的警告unqualified-field-access
:抑制没有权限访问的域的警告unused
:抑制没被使用过的代码的警告
- 可以只添加一个参数,如
import java.util.ArrayList;
import java.util.List;
public class Test {
@SuppressWarnings("all")
public void addItems(String item){
List items = new ArrayList();
items.add(item);
}
}
- 一种在写代码时不看警告的方式:Preferences -> Editor -> Inspections。
元注解
- 元注解的作用是注解其他注解。
- Java 定义了四个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型做说明。
- 这些类型和他们所支持的类在 java.lang.annotation 包。
@Target
:
- 用于描述注解的适用范围,即被描述的注解可以使用在什么地方,如在类使用、在方法使用等。
@Target
注解修饰根据value
(ElementType
枚举常量)的指定的目标进行规定,可选值有:ElementType.TYPE
:类、接口(包括注解类型)或枚举声明。ElementType.FIELD
:字段声明(包括枚举常量)。ElementType.METHOD
:方法声明。ElementType.PARAMETER
:参数声明。ElementType.CONSTRUCTOR
:构造函数声明。ElementType.LOCAL_VARIABLE
:局部变量声明。ElementType.ANNOTATION_TYPE
:注解类型声明。ElementType.PACKAGE
:包声明。ElementType.TYPE_PARAMETER
:1.8 的新特性,类型参数声明,类型参数即Map<String, Integer>
中的String
和Integer
。ElementType.TYPE_USE
:1.8 的新特性,使用类型(好像是可以在任何地方使用的意思,咕咕咕~)。
@Retention
:
- 表示需要在什么级别保存该注解信息,用于描述注解的生命周期。
- 约束注解的生命周期通过指定
@Retention
中的值来实现(值为RetentionPolicy
枚举常量),可选值有“RetentionPolicy.SOURCE
:注解将被编译器丢弃,该类型的注解信息指挥保留在源码中,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class
文件中。RetentionPolicy.CLASS
:注解将由编译器记录在类文件中,但不需要在运行时由 VM 保留。这是默认行为。该类型的注解信息会保留在源码里和class
文件里,在执行的时候,不会加载到虚拟机中。该类型也是未指定@Retention
值时的默认值。RetentionPolicy.RUNTIME
:注解将由编译器记录在类文件中,并在运行时由 VM 保留,因此可以反射性地读取它们。源码,class
文件和执行时 VM 都保留注解的信息。
@Documented
:
- 说明该注解将被包含在 javadoc 中。
@Inherited
:
- 说明子类可以继承父类中的该注解。
例——Target、Retention:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@MyAnnotation
public class Test {
@MyAnnotation
public static void main(String [] args) {
}
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}
MyAnnotation
是一个自定义的注解,它可以修饰类 Test,也可以修饰方法main
。
自定义注解
-
使用
@interface
自定义注解,自动继承了 java.lang.annotation.Annotation 接口。public @interface MyAnnotation
等价于public interface MyAnnotation extends java.lang.annotation.Annotation
。
-
格式:
public @interface 注解名{ 定义内容 }
-
注解中的每一个方法实际是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数类型。
-
在使用注解时,注解显式赋值,没有顺序。
-
如果只有一个参数成员,一般参数名为
value
,这时,使用注解时可以不用显示赋值。 -
可以通过
default
来声明参数的默认值,如果没有默认值,就必须要给注解赋值。 -
注解的参数定义格式:
参数类型 参数名 () default 默认值;
-
例——自定义注解、注解参数的使用:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class Test {
@MyAnnotation1() // MyAnnoatation1 的所有参数都有 default 默认值, 使用时可以不用赋值
public void test1() { }
@MyAnnotation1(id = 1, age = 50) // 可以显示地给需要赋值的参数赋值, 不需要考虑顺序
public void test2() { }
@MyAnnotation2(name = "张三") // MyAnnoatation2 没有 default 默认值, 必须赋值
public void test3() { }
// @MyAnnotation2("张三") // MyAnnoatation2 参数唯一, 参数名 name, 赋值时不能省略 name =
public void test4() { }
@MyAnnotation3("张三") // MyAnnoatation3 参数唯一, 参数名 value, 赋值时可以省略 value =
public void test5() { }
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation1 {
String name() default "";
int age() default 18;
int id() default -1;
String [] hobby() default {""};
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
String name();
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation3 {
String value();
}
通过反射获取注解信息
- 注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期间直接扫描:编译器在编译 Java 代码的时候扫描对于的注解并处理,比如某个方法使用了
@Override
,编译器在编译的时候就会检测当前的方法是否重写了父类对于的方法。 - 运行期间通过反射处理:这个经常在 Spring 框架中看到,例如 Spring 的
@Value
注解,就是通过反射来进行处理的。
- 编译期间直接扫描:编译器在编译 Java 代码的时候扫描对于的注解并处理,比如某个方法使用了
反射
-
反射 (Reflaction) 是 Java 被视为动态语言的关键,反射机制允许程序在执行期间借助于 Reflaction API 获取任何类的内部信息,并能直接操作任意对象的内部属性及方法。
-
加载完类后,在堆内存的方法区中就产生了一个
Class
类型的对象,一个类只有一个Class
对象,这个对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构,这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以我们形象的称之为:反射。Class c = Class.forName("java.lang.String");
Java 反射机制提供的功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类具有的成员变量和方法。
- 在运行时获取反省信息。
- 在运行时调用任意一个对象的成员变量和方法。
- 在运行时处理注解。
- 生成动态代理。
- ......
Java 反射的优缺点:
- 优点:可以实现动态创建对象和编译,体现出很大的灵活性。
- 缺点:对性能有影响。反射基本上是一种解释型操作,我们告诉 JVM,希望它做什么,这类操作慢于直接执行相应的操作。
反射相关的主要 API:
- java.lang.Class:代表一个类。
- java.lang.reflect.Method:代表类的方法。
- java.lang.reflect.Field:代表类的成员变量。
- java.lang.reflect.Constructor:代表类的构造器。
例——获得反射对象:
- User.java
public class User {
private int id;
private int age;
private String name;
// 省略构造方法和 get, set
}
- Test.java
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c1 = Class.forName("User");
System.out.println(c1); // class User
Class<?> c2 = Class.forName("User");
Class<?> c3 = Class.forName("User");
// 三个输出相同, 说明一个类只有一个 Class 对象
System.out.println(c1.hashCode()); // 1956725890
System.out.println(c2.hashCode()); // 1956725890
System.out.println(c3.hashCode()); // 1956725890
}
}
- 输出:
class User
1956725890
1956725890
1956725890
关于 Class 类:
Class
本身是一个类,Class
对象是只能由系统建立的对象。- 一个加载的类在 JVM 中只会有一个 Class 实例。
- 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件。
- 每个类的实例都会记得自己是由哪个 Class 实例所生成。
- 通过 Class 可以完整地得到一个类中的所有被加载的结构。
- Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,应当首先获得响应的 Class 对象。
Class 类的常用方法:
ClassforName(String name)
:返回指定类名name
的Class
对象。newInstance()
:调用缺省构造函数,返回Class
对象的一个实例。getName()
:返回此Class
对象所表示的实体(类、接口、数组类或void
)的名称。getSuperClass()
:返回当前Class
对象的父类的Class
对象。getInterfaces()
:返回此Class
对象的接口。getClassLoader()
:返回该类的加载器。getConstructors()
:返回一个包含某些Constructor
对象的数组。getMethod()
:返回一个Method
对象,此对象的型参类型为paramType
。getDeclaredFields()
:返回Field
对象的一个数组。- ......
通过反射获取注解信息
例:
- User.java
@MyAnnotationType("User")
public class User {
@MyAnnotationField(name = "id", type = "int")
private int id;
@MyAnnotationField(name = "age", type = "int")
private int age;
@MyAnnotationField(name = "name", type = "String")
private String name;
// 省略构造方法和 getter/setter
}
- MyAnnotationType.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotationType {
String value();
}
- MyAnnotationField.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotationField {
String name();
String type();
}
- Test.java
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c = Class.forName("User");
// 通过反射获取注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 通过反射获取注解的值
MyAnnotationType myAnnotationType = c.getAnnotation(MyAnnotationType.class);
System.out.println(myAnnotationType.value());
// 获得类的属性的注解
Field f = c. getDeclaredField("name");
MyAnnotationField myAnnotationField = f.getAnnotation(MyAnnotationField.class);
System.out.println(myAnnotationField.name());
System.out.println(myAnnotationField.type());
}
}
- 输出:
@MyAnnotationType(value=User)
User
name
String
注解使用实例
SpringBoot:
在软工实验中我们学到过
@Controller
:控制层@ResponseBoby
:表示该方法的返回结果直接写入 HTTP response body 中一般在异步获取数据时使用,用于构建 RESTful 的 api。在使用@RequestMapping
后,返回值通常解析为跳转路径,加上@ResponseBody
后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。比如异步获取 Json 数据,加上@Responsebody
后,会直接返回 Json 数据。该注解一半会配合@RequestMapping
一起使用。@RestController
:@ResponseBody
+@Controller
@PostMapping
:通过配置 url 进行访问,限定只能是 Post 请求。@RequestBody
:用于获取请求体数据 (body),Get 没有请求体,故而一般用于 Post 请求。@AutoWirde
:对类成员、变量、方法、构造函数进行标注,完成自动装配的工作。@Entity
:标记为实体类,对应数据库表。@Id
:设置属性为主键,建表必不可少。@GeneratedValue
:设置主键的自动生成方式,通过参数strategy
控制。@Mapper
:标记为数据类,告诉 SpringBoot 这是一个数据层接口。@Service
:标记为服务层接口实体。
Fastjson:
在组队大作业和期末大作业中使用过
- 在实体类中使用
@JSONField
注解,设置响应的标准 getter/setter 方法,可以把实体类的对象与 Json 字符串相互转换。 @JSONField
的参数包括:ordinal
:序列化字段的顺序,默认是 0。name
:用于解决属性名和 key 不一致的情况,当前端传过来的字段名不一样的时候,我们可以在字段名上加上这个注解。format
:用在 Date 属性上,自动格式化日期。serialize
:是否要把这个字段序列化成 Json 字符串,默认是true
。deserialize
:字段是否需要进行反序列化,默认是true
。
@JSONField
也可以配置在 getter/setter 方法或者字段上。