2023年将会持续于B站、优快云等各大平台更新,可加入粉丝群与博主交流:838681355,为了老板大G共同努力。
【商务合作请私信或进群联系群主】
九、注释与注解
9.1 JAR文件
程序打包,可以提供一个单独的文件,JAVA归档(JAR)文件就说为此目的而设计的,一个JAR文件即可以包含类文件,也可以包含诸如图像的声音等其它类型文件,可以将jar文件比喻成zip压缩格式。
9.1.1 创建JAR文件
jar工具制作JAR文件(默认JDK安装中,工具位于jdk/bin目录下)
命令: jar cvf jarFileName file1 file2 ...
-c (create)创建新档案
-t 列出档案目录
-x 从档案中提取指定的 (或所有) 文件
-u (update)更新现有档案
-v 在标准输出中生成详细输出
-f 指定档案文件名
-m 包含指定清单文件中的清单信息
-n 创建新档案后执行 Pack200 规范化
-e 为捆绑到可执行 jar 文件的独立应用程序
指定应用程序入口点
-0 仅存储; 不使用任何 ZIP 压缩
-P 保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件
-M 不创建条目的清单文件
-i 为指定的 jar 文件生成索引信息
-C 更改为指定的目录并包含以下文件
9.1.2 清单文件
每个JAR还包含一个清单文件(manifest),用于描述归档文件的特殊特性。
清单文件名为MANIFEST.MF,它是位于JAR文件的一个特殊的META-INF子目录中。
复杂的清单文件可能包含更多条目,浙西而清单条目被分为很多节,第一节被称为主节(main section),它作用域整个JAR文件。随后的条目用来指定命名实体的属性,如单个文件、包或者URL,它们都必须以一个Name条目开始。节与节之间用空行分开。
想要编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中,运行:jar cfm jarFileName manifestFileNmae...
例如创建一个包含清单文件的JAR文件,应该运行:jar cfm MyArchive.jar manifest.mf com/mycompany/mypkg
9.1.3 执行JAR文件
1. 可以使用jar命令中的e选项指定程序入口点,即通常需要在调用java程序启动器时指定的类:
jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add
2. 或者可以在清单文件中指定程序的主类,包括以下形式语句:
Main-Class: com.mycompany.mypkg.MainAppClass
3. 或者简单通过命令启动程序: java -jar MyProgram.jar
9.1.4 文档注释
JDK包含一个工具名为javadoc,它可以由源文件生成一个HTML文档。
9.1.4.1 注释的插入
javadoc实用工具从下面几项中抽取信息:
* 模块;
* 包;
* 公共类与接口;
* 公共的和受保护的字段;
* 公共的和受保护的构造器即方法;
9.1.4.2 类注释
类注释必须放在import语句之后,类定义之前。
例如:
public class Card
{
...
}
3.5.4.3 方法注释
每一个方法注释必须放在所描述的方法之前。除了通用标记之外,还可以使用下面的标记:
* @param variable description:这个标记将给当前方法的"parameters"(参数)部分添加一个条目。这个描述可以占据多行,并且可以使用HTML标记。一个方法的所有@param标记必须放在一起。
* @return description:这个标记将给当前方法添加"returns"(返回)部分。这个描述可以跨多行,并且可以使用HTML标记
* @throws class description:这个标记将添加一个注释,表示这个方法由可能抛出异常。
9.1.4.4 字段注释
只需要对公共字段(通常指的时静态常量)建立文档。例如:
public static final int HEARTS = 1;
9.1.4.5 通用注释
标记@since text会建立一个"since"(始于)条目。text(文本)可以是引入这个特性版本的任何描述。例如:@since 1.7.1
* @author name:这个标记会产生一个"author"(作者)条目。
* @version text:这个标记将产生一个"version"(版本)条目。
9.1.4.6 包注释
可以直接将类、方法和变量的注释放置在java源文件中,只要用 文档注释界定就可以了。
9.1.4.7 注释抽取
假设HTML文件将放在名为docDirectory的目录下,执行以下步骤:
1. 切换到包含想要生成文档的源文件的目录。如果由嵌套的包要生成文档,例如com.horstmann.corejava,就必须切换到包含子目录com的目录。
2.
如果是一个包,应该运行命令:javadoc -d docDirectory nameOfPackage
或者,如果要为多个包生成文档:javadoc -d docDirectory nameOfPackage1 nameOfPackage2...
如果文件在无名的包中,就应该运行:javadoc -d docDirectory *.java
9.2 注解
注解理解为代码里的特殊标记,这些标记可以在编译、类加载和运行时被读取,并执行相应的处理。
注解可以元数据这个词来描述,即一种描述数据的数据。所以可以说注解就是源代码的元数据。例如以下代码:
@Override
public String toString() {
return "C语言中文网Java教程";
}
重写了 Object 类的 toString() 方法并使用了 @Override 注解。如果不使用 @Override 注解标记代码,程序也能够正常执行。那么这么写有什么好处吗?事实上,使用 @Override 注解就相当于告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。这样可以防止不小心拼写错误造成麻烦。
注解常见的作用有以下几种:
1. 生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see、@param 和 @return 等;
2. 跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
3. 在编译时进行格式检查。如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。
基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。
9.2.1 @Override注解
Java 中 @Override 注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
使用 @Override 注解示例代码如下:
public class Person {
private String name = "";
private int age;
...
@Override
public String t0String() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
如果 toString() 不小心写成了 t0String(),那么程序会发生编译错误。会有如下的代码提示:
类型为 Person 的方法t0String()必须覆盖或实现超类型方法
所以 @Override 的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。这样可以帮助程序员避免一些低级错误。
9.2.2 @Deprecated注解
Java 中 @Deprecated 可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。
使用 @Deprecated 注解示例代码如下:
@Deprecated
public class Person {
@Deprecated
protected String name;
private int age;
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;
}
@Deprecated
public void setNameAndAge(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
9.2.3 @SuppressWarningns:抑制编译器警告
Java 中的 @SuppressWarnings 注解指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。
如果你确认程序中的警告没有问题,可以不用理会。通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 注解消除这些警告。
注解的使用有以下三种:
1. 抑制单类型的警告:@SuppressWarnings("unchecked")
2. 抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes")
3. 抑制所有类型的警告:@SuppressWarnings("unchecked")
关键字 | 用途 |
---|
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 | 抑制没被使用过的代码的警告 |
示例:
使用 @SuppressWarnings 注解示例代码如下:
public class HelloWorld {
@SuppressWarnings({ "deprecation" })
public static void main(String[] args) {
Person p = new Person();
p.setNameAndAge("C语言中文网", 20);
p.name = "Java教程";
}
}
9.8.4 @SafeVarargs注解
可用 @SafeVarargs 注解抑制编译器警告,代码如下:
public class HelloWorld {
public static void main(String[] args) {
display(10, 20, 30);
display("10", 20, 30);
}
@SafeVarargs
public static <T> void display(T... array) {
for (T arg : array) {
System.out.println(arg.getClass().getName() + ":" + arg);
}
}
}
上述代码在可变参数 display 前添加了 @SafeVarargs 注解,当然也可以使用 @SuppressWarnings("unchecked") 注解,但是两者相比较来说 @SafeVarargs 注解更适合。
注意:@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。
9.8.5 @FunctionalInterface注解
如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
函数式接口就是为 Java 8 的 Lambda 表达式准备的,Java 8 允许使用 Lambda 表达式创建函数式接口的实例,因此 Java 8 专门增加了 @FunctionalInterface。
使用 @FunctionalInterface 修饰了函数式接口:
@FunctionalInterface
public interface FunInterface {
static void print() {
System.out.println("C语言中文网");
}
default void show() {
System.out.println("我正在学习C语言中文网Java教程");
}
void test();
}
编译上面程序,可能丝毫看不出程序中的 @FunctionalInterface 有何作用,因为 @FunctionalInterface 注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。
@FunctionalInterface 注解主要是帮助程序员避免一些低级错误,例如,在上面的 FunInterface 接口中再增加一个抽象方法 abc(),编译程序时将出现如下错误提示:
“@FunctionInterface”批注无效;FunInterface不是functional接口
9.8.6 Java元注解作用及使用
元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。Java 5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。Java 8 又增加了 @Repeatable 和 @Native 两个注解。这些注解都可以在 java.lang.annotation 包中找到。
9.8.6.1 @Documented
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
下面通过示例来了解它的用法,代码如下所示。
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyDocumented {
public String value() default "这是@Documented注解";
}
测试类:
@MyDocumented
public class DocumentedTest {
@MyDocumented
public String Test() {
return "C语言中文网Java教程";
}
}
打开 Java 文件所在的目录,分别输入如下两条命令行:
javac MyDocumented.java DocumentedTest.java
javadoc -d doc MyDocumented.java DocumentedTest.java
运行成功后,打开生成的帮助文档,可以看到在类和方法上都保留了 MyDocument 的注解信息。
9.8.6.2 @Target
@Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。
名称 | 说明 |
---|
CONSTRUCTOR | 用于构造方法 |
FIELD | 用于成员变量(包括枚举常量) |
LOCAL_VARIABLE | 用于局部变量 |
METHOD | 用于方法 |
PACKAGE | 用于包 |
PARAMETER | 用于类型参数(JDK 1.8新增) |
TYPE | 用于类、接口(包括注解类型)或 enum 声明 |
示例:
自定义一个 MyTarget 注解,使用范围为方法,代码如下所示。
@Target({ ElementType.METHOD })
public @interface MyTarget {
}
class Test {
@MyTarget
String name;
}
如上代码第 6 行会编译错误,错误信息为:
The annotation @MyTarget is disallowed for this location
提示此位置不允许使用注解 @MyDocumented,@MyTarget 不能修饰成员变量,只能修饰方法。
9.8.6.3 @Retention
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。
SOURCE:在源文件中有效(即源文件保留)
CLASS:在 class 文件中有效(即 class 保留)
RUNTIME:在运行时有效(即运行时保留)
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
9.8.6.4 @Inherited
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
示例:
创建一个自定义注解,代码如下所示:
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherited {
}
测试类代码如下:
@MyInherited
public class TestA {
public static void main(String[] args) {
System.out.println(TestA.class.getAnnotation(MyInherited.class));
System.out.println(TestB.class.getAnnotation(MyInherited.class));
System.out.println(TestC.class.getAnnotation(MyInherited.class));
}
}
class TestB extends TestA {
}
class TestC extends TestB {
}
9.8.6.5 @Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
示例:
Java 8 之前的做法:
public @interface Roles {
Role[] roles();
}
public @interface Roles {
Role[] value();
}
public class RoleTest {
@Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
public String doString(){
return "这是C语言中国网Java教程";
}
}
Java 8 之后增加了重复注解,使用方式如下:
public @interface Roles {
Role[] value();
}
@Repeatable(Roles.class)
public @interface Role {
String roleName();
}
public class RoleTest {
@Role(roleName = "role1")
@Role(roleName = "role2")
public String doString(){
return "这是C语言中文网Java教程";
}
}
不同的地方是,创建重复注解 Role 时加上了 @Repeatable 注解,指向存储注解 Roles,这样在使用时就可以直接重复使用 Role 注解。从上面例子看出,使用 @Repeatable 注解更符合常规思维,可读性强一点。
两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象,多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。
9.8.6.6 @Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
9.8.7 自定义注解
声明自定义注解使用 @interface 关键字(interface 关键字前加 @ 符号)实现。定义注解与定义接口非常像,如下代码可定义一个简单形式的注解类型。
public @interface Test {
}
定义注解和定义类相似,注解前面的访问修饰符和类一样有两种,分别是公有访问权限(public)和默认访问权限(默认不写)。一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解。且源程序文件命名和公有访问权限的注解名一致。
不包含任何成员变量的注解称为标记注解,例如上面声明的 Test 注解以及基本注解中的 @Override 注解都属于标记注解。根据需要,注解中可以定义成员变量,成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。代码如下所示:
public @interface MyTag {
String name();
int age();
}
以上代码中声明了一个 MyTag 注解,定义了两个成员变量,分别是 name 和 age。成员变量也可以有访问权限修饰符,但是只能有公有权限和默认权限。
如果在注解里定义了成员变量,那么使用该注解时就应该为它的成员变量指定值,如下代码所示。
public class Test {
@MyTag(name="xx", age=6)
public void info() {
...
}
...
}
注解中的成员变量也可以有默认值,可使用 default 关键字。如下代码定义了 @MyTag 注解,该注解里包含了 name 和 age 两个成员变量。
public @interface MyTag {
String name() default "C语言中文网";
int age() default 7;
}
如果为注解的成员变量指定了默认值,那么使用该注解时就可以不为这些成员变量赋值,而是直接使用默认值。
public class Test {
@MyTag
public void info() {
...
}
...
}
当然也可以在使用 MyTag 注解时为成员变量指定值,如果为 MyTag 的成员变量指定了值,则默认值不会起作用。
根据注解是否包含成员变量,可以分为如下两类:
1. 标记注解:没有定义成员变量的注解类型被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面介绍的 @Override、@Test 等都是标记注解。
2. 元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。