目录
五、特殊类
5.1 内部类(熟悉)
基本概念
- 当一个类的定义出现在另一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(outer)
- 类中的内容:成员变量、成员方法、构造方法、静态成员、构造块、静态代码块、内部类
- 实际作用:当一个类存在的价值仅仅是为了某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节,并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法。
分类
- 普通内部类:直接将一个类的定义放在另一个类的类体中
- 静态内部类:使用static关键字修饰的内部类,隶属于类层级
- 局部内部类:直接将一个类放在方法体内部时
- 匿名内部类:就是指没有名字的内部类(开发中用得最多)
5.2 普通内部类(成员内部类)
定义
- 普通内部类隶属于外部类的成员,并且是对象层级
- 语法格式:
访问修饰符 class 外部类的类名 {
访问修饰符 class 内部类的类名 {
内部类的类体;
}
}
/**
* 编程实现普通内部类的定义和使用 - 文档注释
*/
public class NormalOuter {
private int cnt = 1;
// 定义普通内部类,隶属于外部类的成员
public class NormalInner {
private int ia = 2;
// 构造方法体
public NormalInner() {
System.out.println("普通内部类的构造方法体执行到了!");
}
public void show() {
System.out.println("外部变量cnt:" + cnt); // 1
System.out.println("ia = " + ia); // 2
}
}
}
public class NormalOuterTest {
public static void main(String[] args) {
// 1.声明NormalOuter类型的引用指向该类型的对象
NormalOuter no = new NormalOuter();
// 2.声明NormalOuter类中内部类的引用指向内部类的对象
NormalOuter.NormalInner ni = no.new NormalInner();
// 调用内部类中的show方法
ni.show();
}
}
使用方式
- 普通内部类和普通类一样,可以定义成员变量、成员方法、构造方法等
- 普通内部类和普通类一样可以使用final或者abstract关键字修饰
- 普通内部类还可以使用private或者protected或者关键字修饰,也可以不写(默认权限)
- 普通内部类需要使用外部类对象来创建对象
- 如果内部类访问外部类中 与 本类内部 同名 的成员变量或方法时,需要使用this关键字(笔试题)
/**
* 编程实现普通内部类的定义和使用 - 文档注释
*/
public class NormalOuter {
private int cnt = 1;
// 定义普通内部类,隶属于外部类的成员
/*private*/public /*final*/ class NormalInner {
private int ia = 2;
private int cnt = 3;
// 构造方法体
public NormalInner() {
System.out.println("普通内部类的构造方法体执行到了!");
}
public void show() {
System.out.println("外部变量cnt:" + cnt);
System.out.println("ia = " + ia);
}
public void show2(int cnt) { // 实参传递 4
System.out.println("形参变量cnt = " + cnt); // 4 局部变量原则(就近原则)
System.out.println("内部类中cnt = " + this.cnt); // 3
System.out.println("外部类中cnt = " + NormalOuter.this.cnt); // 1
}
}
}
public class NormalOuterTest {
public static void main(String[] args) {
// 1.声明NormalOuter类型的引用指向该类型的对象
NormalOuter no = new NormalOuter();
// 2.声明NormalOuter类中内部类的引用指向内部类的对象
NormalOuter.NormalInner ni = no.new NormalInner();
// 调用内部类中的show方法
ni.show();
ni.show2(4);
}
}
5.3 静态内部类
定义
- 语法格式
访问修饰符 class 外部类的类名 {
访问修饰符 static class 内部类的类名 {
内部类的类体;
}
}
/**
* 实现静态内部类的定义和使用
*/
public class StaticOuter {
private int cnt = 1; // 隶属于对象层级
private static int snt = 2; // 隶属于类层级
/**
* 定义静态内部类 有static关键字修饰,隶属于类层级
*/
public static class StaticInner {
private int ia = 3;
// 无参构造方法
public StaticInner() {
System.out.println("静态内部类的构造方法!");
}
public void show() {
System.out.println("ia = " + ia);
System.out.println("外部类中的snt = " + snt);
//System.out.println("外部类中的cnt = " + cnt); // Error:静态上下文中不能访问非静态成员,因为此时可能还没有创建对象
}
}
}
public class StaticOuterTest {
public static void main(String[] args) {
// 1.声明StaticInner类型的引用指向该类型的对象
StaticOuter.StaticInner si = new StaticOuter.StaticInner();
// 2.调用show方法进行测试
si.show();
}
}
使用方式
- 静态内部类中不能访问外部类的非静态成员
- 静态内部类可以直接创建对象
- 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用 类名. 的方式访问
/**
* 实现静态内部类的定义和使用
*/
public class StaticOuter {
private int cnt = 1; // 隶属于对象层级
private static int snt = 2; // 隶属于类层级
// public static show() ...
public void show() {
System.out.println("外部类中的show方法就在这里!");
}
/**
* 定义静态内部类 有static关键字修饰,隶属于类层级
*/
public static class StaticInner {
private int ia = 3;
private static snt = 4;
// 无参构造方法
public StaticInner() {
System.out.println("静态内部类的构造方法!");
}
public void show() {
System.out.println("ia = " + ia);
System.out.println("外部类中的snt = " + snt);
//System.out.println("外部类中的cnt = " + cnt); // Error:静态上下文中不能访问非静态成员,因为此时可能还没有创建对象
}
public void show2() { // 传入实参 5
System.out.println("snt = " + snt); // 5 就近原则
System.out.println("内部类中的成员snt = " + StaticInner.snt); // 4
System.out.println("外部类中的成员snt = " + StaticOuter.snt); // 2
// StaticOuter.show();
new StaticOuter().show();
}
}
}
public class StaticOuterTest {
public static void main(String[] args) {
// 1.声明StaticInner类型的引用指向该类型的对象
StaticOuter.StaticInner si = new StaticOuter.StaticInner();
// 2.调用show方法进行测试
si.show();
si.show2(5);
}
}
5.4 局部内部类(方法内部类)
定义
- 语法格式
访问修饰符 class 外部类的类名 {
访问修饰符 返回值类型 成员方法名(形参列表) {
class 内部类的类名 { // 注:此处没有访问修饰符
内部类的类体;
}
}
}
/**
* 编程实现局部内部类的定义和使用
*/
public class AreaOuter {
private int cnt = 1;
public void show() {
// 定义局部内部类,只在当前方法体的内部能使用
class AreaInner {
private int ia = 2;
// 构造方法
public AreaInner() {
System.out.println("局部内部类的构造方法!");
}
public void test() {
System.out.println("ia = " + ia); // 2
System.out.println("cnt = " + cnt); // 1
}
}
// 声明局部内部类的引用指向局部内部类的对象
AreaInner ai = new AreaInner();
ai.test();
}
}
public class AreaOuterTest {
public static void main(String[] args) {
// 1.声明外部类类型的引用指向外部类的对象
AreaOuter ao = new AreaOuter();
// 2.通过show方法的调用实现局部内部类的定义和使用
ao.show();
}
}
使用方式
- 局部内部类只能在该方法内部使用
- 局部内部类可以在方法体内部直接创建对象
- 局部内部类不能使用访问控制符和static关键字修饰符
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同(即生效范围不一样)所致(笔试题)
- 针对这道笔试题的例子:
/**
* 编程实现局部内部类的定义和使用
*/
public class AreaOuter {
private int cnt = 1;
public void show() {
// 定义一个局部变量进行测试,从Java8开始默认理解为final关键字修饰的变量
// 虽然可以省略final关键字,但建议还是加上
final int ic = 4;
// 定义局部内部类,只在当前方法体的内部好使 拷贝一份
class AreaInner {
private int ia = 2;
public AreaInner() {
System.out.println("局部内部类的构造方法!");
}
public void test() {
int ib = 3;
System.out.println("ia = " + ia); // 2
System.out.println("cnt = " + cnt); // 1
//ic = 5; Error
System.out.println("ic = " + ic); // 4
}
}
// 声明局部内部类的引用指向局部内部类的对象
AreaInner ai = new AreaInner();
ai.test();
}
}
5.5 回调模式
概念
- 回调模式:如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口的对象;而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的方法)
public interface AnonymousInterface { // 接口
// 自定义抽象方法
public abstract void show();
public class AnonymousInterfaceImpl implements AnonymousInterface {
@Override // 方法重写后,AnonymousInterfaceImpl 不再是抽象类而是接口的实现类
// 可以使用new实例化
public void show() {
System.out.println("这里是接口的实现类!");
}
}
public class AnonymousInterfaceTest {
// 假设已有下面的方法,如何调用下面的方法
// AnonymousInterFace ai = new AnonymousInterfaceImpl();
// 接口类型的引用指向实现类型的对象,形成了多态
public static void test(AnonymousInterface ai) { // 参数是接口类型
// 编译阶段调用父类版本,运行阶段调用实现类重写的版本
ai.show();
}
public static void main(String[] args) {
//AnonymousInterfaceTest.test(new AnonymousInterface()); // Error: 接口不能实例化
// 相当于:AnonymousInterFace ai = new AnonymousInterfaceImpl();
AnonymousInterfaceTest.test(new AnonymousInterfaceImpl()); // 接口的实现类可以实例化
// 方法重写后不再是抽象类
}
}
5.6 匿名内部类(重点)
开发经验分享
- 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
- (1)自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递(即上面5.5 的方法,麻烦)
- (2)使用下面的匿名内部类的语法格式得到接口/类类型的引用即可 (简单)
匿名内部类的语法格式(lamda)
接口/父类类型 引用变量名 = new 接口/父类类型() { 方法重写 }; // 注意这是个语句,尾部有个分号“;”
例子(包含过程的解释):
(1)首先有一个接口
public interface AnonymousInterface {
// 自定义抽象方法
public abstract void show();
}
(2)由于后面需要向某个方法中传递接口类的对象,因此现在创建一个接口类型的实现类,进行方法的重写 (因为接口类型不能实例化):
public class AnonymousInterfaceImpl implements AnonymousInterface {
@Override
public void show() {
System.out.println("这里是接口的实现类!");
}
}
(3)现在在测试类中有个方法的形参是接口/类类型的引用,我们要进行实参的传递(方式二):
public class AnonymousInterfaceTest {
// 假设已有下面的方法,如何调用下面的方法
// AnonymousInterFace ai = new AnonymousInterfaceImpl();
// 接口类型的引用指向实现类型的对象,形成了多态
public static void test(AnonymousInterface ai) {
// 编译阶段运行父类版本,运行调用实现类重写的版本
ai.show();
}
public static void main(String[] args) {
//AnonymousInterfaceTest.test(new AnonymousInterface() ); // Error: 接口不能实例化
AnonymousInterfaceTest.test(new AnonymousInterfaceImpl()); //=========方式一
System.out.println("---------------------------");
// 使用匿名内部类的语法格式来得到接口类型的引用,格式为:接口/父类类型 引用变量名 = 接口父类类型() { 方法的重写 };
//(1)第一步是声明接口类型的引用指向接口类型的对象:
// AnonymousInterface ait = new AnonymousInterface();
// 但是报错了:接口类型不能实例化,需要对接口类型的实现类进行实例化
//(2)将已经创建好的接口类型的实现类拷贝过来放到该语句后面(),作为接口类型的内部类,注意在分号";"前
/*
AnonymousInterface ait = new AnonymousInterface() public class AnonymousInterfaceImpl implements AnonymousInterface {
@Override
public void show() {
System.out.println("这里是接口的实现类!");
}
};*/
//(3)由于接口类型的内部类是匿名的,因此要把类体前面的内容去掉(保留大括号及其里面的内容)
AnonymousInterface ait = new AnonymousInterface() {
@Override
public void show() {
System.out.println("这里是接口的实现类!");
}
};
// AnonymousInterfaceTest.test(ait);
}
}
- 以上方式二的意义在于:调用完方法以后,该接口类型的实现类就失去作用,达到了释放内存的目的
- 但是还有更加简单的方法:lamda(方式二的简化版本)
- 从Java8开始,提出了新特性lamda表达式可以简化上述代码,格式为:
(参数列表) -> {方法体}
如:
// 前面的小括号里面没有内容是因为show()方法没有形参,且只有单个语句时大卡,大括号可以省略
AnonymousInterface ait2 = () -> System.out.println("原来lamda表达式是如此之简单!");
AnonymousInterfaceTest.test(ait2);
5.7 枚举类型Enum(熟悉)—> 看成特殊的类
基本概念和自定义实现
-
基本概念
- 在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型叫做枚举类型
- 一年中的所有季节:春、夏、秋、冬
- 所有性别:男、女
- 键盘上所有方向键:上、下、左、右
-
自定义实现
/**
* 编程实现所有方向的枚举:上、下、左、右
*/
public class Direction {
//private final String desc = "上"; // 显式初始化,值不能改变,很不灵活
private final String desc; // 用于描述方向字符串的成员变量
// 通过构造方法实现成员变量的初始化,更加灵活
// 1.私有化构造方法,此时该构造方法只能在本类内部使用
private Direction(String desc) {
this.desc = desc;
}
// 通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
// 2. 声明本类类型的引用指向本类类型的对象
private static final Direction UP = new Direction("向上"); // 常量
private static final Direction DOWN = new Direction("向下");
private static final Direction LEFT = new Direction("向左");
private static final Direction RIGHT = new Direction("向右");
// 3.提供公有的get方法负责将对象返回
public static Direction getUP() {
return UP;
}
public static Direction getDOWN() {
return DOWN;
}
public static Direction getLEFT() {
return LEFT;
}
public static Direction getRIGHT() {
return RIGHT;
}
}
public class DirectionTest {
public static void main(String[] args) {
/*// 1.声明Direction类型的引用指向该类型的对象并打印特征
Direction d1 = new Direction("向上");
System.out.println("获取到的字符串是:" + d1.getDesc()); // 向上
Direction d2 = new Direction("向下");
System.out.println("获取到的字符串是:" + d2.getDesc()); // 向下
Direction d3 = new Direction("向左");
System.out.println("获取到的字符串是:" + d3.getDesc()); // 向左
Direction d4 = new Direction("向右");
System.out.println("获取到的字符串是:" + d4.getDesc()); // 向右
System.out.println("-------------------------------------");
Direction d5 = new Direction("向前");
System.out.println("获取到的字符串是:" + d5.getDesc()); // 向前*/
Direction UP = Direction.getUP();
Direction DOWN = Direction.getDOWN();
Direction LEFT = Direction.getLEFT();
Direction RIGHT = Direction.getRIGHT();
System.out.println("得到的方向是:" + UP.getDesc());
System.out.println("得到的方向是:" + DOWN.getDesc());
System.out.println("得到的方向是:" + LEFT.getDesc());
System.out.println("得到的方向是:" + RIGHT.getDesc());
}
}
枚举类型的定义
- 使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型
- 枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用 枚举类型. 的方式调用
/**
* 编程实现所有方向的枚举:上、下、左、右
* 枚举类型要求所有枚举值必须放在枚举类型的最前面
*/
public enum DirectionEnum {
// 2. 声明本类类型的引用指向本类类型的对象 ,枚举类型要求所有枚举值必须放在枚举类型的最前面
// UP是枚举引用常量的名称,“向上”则是枚举常量UP内部一个成员变量的值
UP("向上"), DOWN("向下"), LEFT("向左"), RIGHT("向右");
//private final String desc = "上"; // 显式初始化,值不能改变,很不灵活
private final String desc; // 用于描述方向字符串的成员变量
// 通过构造方法实现成员变量的初始化,更加灵活
// 1.私有化构造方法,此时该构造方法只能在本类内部使用
private DirectionEnum(String desc) {
this.desc = desc;
}
// 通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
}
public class DirectionTest {
public static void main(String[] args) {
// 使用一下Java5开始使用的枚举类型
DirectionEnum de = DirectionEnum.DOWN;
System.out.println("得到的方向是:" + de.getDesc()); // 向下
}
}
自定义类和枚举类在switch中的使用区别
public class DirectionUseTest {
// 自定义静态方法实现根据参数指定的字符串内容来打印具体的方向信息
public static void test1(String str) {
switch (str) { // 传入的是字符串
case "向上":
System.out.println("抬头望明月!"); break;
case "向下":
System.out.println("低头思故乡!"); break;
case "向左":
System.out.println("左牵黄"); break;
case "向右":
System.out.println("右擎苍"); break;
default:
System.out.println("没有这样的方向哦!");
}
}
// 自定义静态方法实现根据参数指定的枚举类型来打印具体的方向信息
public static void test2(DirectionEnum de) {
switch (de) { // 传入的是对象
case UP:
System.out.println("抬头望明月!"); break;
case DOWN:
System.out.println("低头思故乡!"); break;
case LEFT:
System.out.println("左牵黄"); break;
case RIGHT:
System.out.println("右擎苍"); break;
default:
System.out.println("没有这样的方法哦!");
}
}
public static void main(String[] args) {
DirectionUseTest.test1(Direction.UP.getDesc());
// 也可以传入其他字符串
DirectionUseTest.test1("今天是个好日子!"); // 没有这样的方法哦!
System.out.println("--------------------------------------------");
DirectionUseTest.test2(DirectionEnum.DOWN);
//DirectionUseTest.test2("今天是个好日子!"); Error:类型不匹配,减少了出错的可能性
}
}
Enum类的概念和方法
- 所有的枚举类都继承自java.lang.Enum类,常用方法如下:
- 常用方法举例:
/**
* 编程实现方向枚举类的测试,调用从Enum类中继承下来的方法
*/
public class DirectionEnumTest {
public static void main(String[] args) {
// 1. 获取DirectionEnum类型中的所有对象
DirectionEnum[] arr = DirectionEnum.values();
// 2.打印每个枚举对象在枚举类型中的名称和索引位置
for(int i=0; i<arr.length; i++) {
System.out.println("获取到的枚举对象名称是:" + arr[i].toString()); // 下标从0开始
System.out.println("获取到的枚举对象名称是:" + arr[i]); // 当打印引用变量时,会自动调用toString方法
// 以上两个打印结果相同
System.out.println("获取到的枚举对象在枚举类中的索引位置是:" + arr[i].ordinal());
}
System.out.println("-----------------------");
// 3.根据参数指定的字符串得到枚举类型的对象,也就是将字符串转换为对象
//DirectionEnum de = DirectionEnum.valueOf("向下"); // 编译ok,运行发生异常:IllegalArgumentException非法参数异常
/**
* 现在一共出现过五种类型的异常,分别是
* 1.算术异常 ArithmeticException
* 2.空指针异常 NullPointerException
* 3.数组越界异常 ArrayIndexOutOfBoundsException
* 4.类型转换异常 ClassCastException
* 5.非法参数异常 IllegalArgumentException
*/
DirectionEnum de = DirectionEnum.valueOf("DOWN"); // 要求字符串名称必须在枚举对象中存在
System.out.println("转换出来的枚举对象名称是:" + de.toString());
System.out.println("转换出来的枚举对象名称是:" + de); // 当打印引用变量时,会自动调用toString方法
// 以上两个打印结果相同
System.out.println("-----------------------");
// 4.使用获取到的枚举对象与枚举类中已有的对象进行先后比较
for(int i=0; i<arr.length; i++) {
System.out.println("调用对象与数组中对象比较的先后顺序结果是:" + de.compareTo(arr[i]));
// 顺序: 0.UP 1.DOWN 2.LEFT 3.RIGHT 与调用对象DOWN进行比较,相当于下标相减
// 结果:1(1-0) 0(1-1) -1(1-2) -2(1-3)
}
}
}
枚举类实现接口的方式
- 枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:重写一个,或者每个对象都重写
public interface DirectionInterface {
// 自定义抽象方法
public abstract void show();
}
/**
* 编程实现所有方向的枚举:上、下、左、右
* 枚举类型要求所有枚举值必须放在枚举类型的最前面
*/
public enum DirectionEnum implements DirectionInterface {
// 2. 声明本类类型的引用指向本类类型的对象 ,枚举类型要求所有枚举值必须放在枚举类型的最前面
//UP("向上"), DOWN("向下"), LEFT("向左"), RIGHT("向右");
UP("向上"){
@Override
public void show() {
System.out.println("贪吃蛇向上移动");
}
}, DOWN("向下"){
@Override
public void show() {
System.out.println("贪吃蛇向下移动");
}
}, LEFT("向左"){
@Override
public void show() {
System.out.println("贪吃蛇向左移动");
}
}, RIGHT("向右"){
@Override
public void show() {
System.out.println("贪吃蛇向右移动");
}
};
//private final String desc = "上"; // 显式初始化,值不能改变,很不灵活
private final String desc; // 用于描述方向字符串的成员变量
// 通过构造方法实现成员变量的初始化,更加灵活
// 1.私有化构造方法,此时该构造方法只能在本类内部使用
private DirectionEnum(String desc) {
this.desc = desc;
}
// 通过公有的get方法可以在本类的外部访问该类成员变量的数值
public String getDesc() {
return desc;
}
// 方法重写方式一:整个枚举类型只重写一次,所有对象调用同一个
/*@Override
public void show() {
System.out.println("现在可以实现接口中抽象方法的重写了!");
}*/
// 方法重写方式二:
// 看上面代码中的每个枚举类型对象的重写
}
/**
* 编程实现方向枚举类的测试,调用从Enum类中继承下来的方法
*/
public class DirectionEnumTest {
public static void main(String[] args) {
// 1. 获取DirectionEnum类型中的所有对象
DirectionEnum[] arr = DirectionEnum.values();
// 2.打印每个枚举对象在枚举类型中的名称和索引位置
for(int i=0; i<arr.length; i++) {
System.out.println("获取到的枚举对象名称是:" + arr[i].toString()); // 下标从0开始
System.out.println("获取到的枚举对象名称是:" + arr[i]); // 当打印引用变量时,会自动调用toString方法
// 以上两个打印结果相同
System.out.println("获取到的枚举对象在枚举类中的索引位置是:" + arr[i].ordinal());
}
System.out.println("-----------------------");
// 3.根据参数指定的字符串得到枚举类型的对象,也就是将字符串转换为对象
//DirectionEnum de = DirectionEnum.valueOf("向下"); // 编译ok,运行发生异常:IllegalArgumentException非法参数异常
/**
* 现在一共出现过五种类型的异常,分别是
* 1.算术异常 ArithmeticException
* 2.空指针异常 NullPointerException
* 3.数组越界异常 ArraysOutOfBoundsException
* 4.类型转换异常 ClassCastException
* 5.非法参数异常 IllegalArgumentException
*/
DirectionEnum de = DirectionEnum.valueOf("DOWN"); // 要求字符串名称必须在枚举对象中存在
System.out.println("转换出来的枚举对象名称是:" + de.toString());
System.out.println("转换出来的枚举对象名称是:" + de); // 当打印引用变量时,会自动调用toString方法
// 以上两个打印结果相同
System.out.println("-----------------------");
// 4.使用获取到的枚举对象与枚举类中已有的对象进行先后比较
for(int i=0; i<arr.length; i++) {
System.out.println("调用对象与数组中对象比较的先后顺序结果是:" + de.compareTo(arr[i]));
// 顺序: 0.UP 1.DOWN 2.LEFT 3.RIGHT 与调用对象DOWN进行比较,相当于下标相减
// 结果:1(1-0) 0(1-1) -1(1-2) -2(1-3)
}
System.out.println("-----------------------");
// 5.使用数组中每个DirectionEnum的每个元素都去调用show方法
for(int i=0; i<arr.length; i++) {
arr[i].show();
}
}
}
5.6 注解(重点)—> 看成特殊的接口
概念
- 注解(Annotation)又叫标注(可以理解为标签),是从Java 5开始增加的一种引用数据类型
- 注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理
定义和使用
- 语法格式
访问修饰符 @interface 注解名称 {
注解成员;
}
// 自定义注解自动继承java.lang.annotation.Annotation接口
// 通过 @注解名称 的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等
- 使用方式
- 注解体中只有成员变量,而没有成员方法,二注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的类型
- 如果注解中只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型以及Annotation类型
// 若一个注解中没有任何的成员,则这样的注解叫做 标记注解/标识注解
public @interface MyAnnotation {
public String value(); // 声明一个String类型的成员变量,名字叫做value,类型有要求
public String value2(); // 一般这些成员变量value1,value2在使用时要赋值,但也可以不赋值(通过提前给默认值的方法)
public String value3() default "1234"; // 使用默认值以后,使用时可以不给值
public String value4();
}
// 表示将标签MyAnnotation贴在Person类的代码中,使用注解时采用 成员参数名1 = 成员参数值, 成员参数名2 = ...
@MyAnnotation(value = "hello", value2 = "world", value4 = "LanceMai") // 一个注解中没有任何的成员时,直接是 @MyAnnotation
public class Person {
private String name;
private int age;
}
元注解的概念
- 特殊注解:
- 元注解:可以注解到注解上的注解或者说元注解是一种基本注解,但是它能够应用到其他的注解上面
- 元注解主要有:
- @Retation - 描述注解的有效范围或生命周期 (retation是保持的意思)
- @Documented - 描述注解是否在文档注释中体现
- @Target - 描述注解到底可以修饰哪些内容
- @Inherited - 描述注解是否可以被继承到我所标记的那个类的子类中
- @Repeatable - 描述注解是否可以重复
@Retention的使用
- 应用到一个住街上用于说明该注解的生命周期,取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽略
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM 中,运行阶段被丢弃,默认方式(即注解中如果不指定Retention的值,则使用默认值)
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM 中,所以在程序运行时可以获取到它们。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//@Retention(RetentionPolicy.SOURCE)
//@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)
// 若一个注解中没有任何的成员,则这样的注解叫做 标记注解/标识注解
public @interface MyAnnotation {
public String value(); // 声明一个String类型的成员变量,名字叫做value,类型有要求
public String value2(); // 一般这些成员变量value1,value2在使用时要赋值,但也可以不赋值(通过提前给默认值的方法)
public String value3() default "1234"; // 使用默认值以后,使用时可以不给值
public String value4();
}
@Documented的使用(很少提取文档,即很少使用)
- 使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容
- @Documented用于指定被该注解将被javadoc工具提取成文档。
- 定义为@Documented的注解必须设置Retention值为RUNTIME
@Target和@Inherited的使用
- @Target
- @Target用于指定被修饰的注解能用于哪些元素的修饰,取值如下:
- 从Java8开始对元注解**@Target**的参数类型ElementType枚举值增加了两个:
- 其中ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型
- 其中ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
- @Inherited
- @Inherited并不是说注解本身可以继承,而是说如果一个超类(父类)被该注解(即@Inherited)标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解
@Repeatable的使用
- @Repeatable表示自然可重复的含义,从Java8开始增加的新特性
以下的方法均依赖于ManTypes中数组的创建
————————————————————————————————————————————
- Java8之前处理多个注解的办法(目的是让ManType能够重复使用)
import java.lang.annotation.Repeatable;
/**
* 自定义注解用来描述任务的角色
*/
public @interface ManType {
String value() default "";
}
/**
* 自定义注解里面可以描述多种角色
*/
public @interface ManTypes {
ManType[] value();
}
// 在Java8以前处理多个注解的方式
@ManTypes({@ManType(value = "职工"),@ManType(value = "超人")})
public class Man {
}
————————————————————————————————————————————
- Java8以后处理多个注解的办法(目的是让ManType能够重复使用)
import java.lang.annotation.Repeatable;
/**
* 自定义注解用来描述任务的角色
*/
@Repeatable(value = ManTypes.class)
public @interface ManType {
String value() default "";
}
/**
* 自定义注解里面可以描述多种角色
*/
public @interface ManTypes {
ManType[] value();
}
@ManType(value = "职工")
@ManType(value = "超人")
public class Man {
}
常见的预制注解
- 预制注解就是Java语言自身提供的注解,具体如下:
- 常用的预制注解如下:
- 关于 @Deprecated
- 关于@SupressWarnings
- 去除警告信息,一般IDEA很少有警告,Eclipse很多警告