目录

  • 五、特殊类
    • 5.1 内部类(熟悉)
      • 基本概念
      • 分类
    • 5.2 普通内部类(成员内部类)
      • 定义
      • 使用方式
    • 5.3 静态内部类
      • 定义
      • 使用方式
    • 5.4 局部内部类(方法内部类)
      • 定义
      • 使用方式
    • 5.5 回调模式
      • 概念
    • 5.6 匿名内部类(重点)
      • 开发经验分享
      • 匿名内部类的语法格式(lamda)
    • 5.7 枚举类型Enum(熟悉)---> 看成特殊的类
      • 基本概念和自定义实现
      • 枚举类型的定义
      • 自定义类和枚举类在switch中的使用区别
      • Enum类的概念和方法
      • 枚举类实现接口的方式
    • 5.6 注解(重点)---> 看成特殊的接口
      • 概念
      • 定义和使用
      • 元注解的概念
      • @Retention的使用
      • @Documented的使用(很少提取文档,即很少使用)
      • @Target和@Inherited的使用
      • @Repeatable的使用
      • 常见的预制注解

五、特殊类

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); // 1System.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); // 3System.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() { // 传入实参 5System.out.println("snt = " + snt);     // 5 就近原则System.out.println("内部类中的成员snt = " + StaticInner.snt); // 4System.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);   // 2System.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); // 2System.out.println("cnt = " + cnt); // 1//ic = 5;  ErrorSystem.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 {@Overridepublic 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 {@Overridepublic void show() {System.out.println("这里是接口的实现类!");}
};*///(3)由于接口类型的内部类是匿名的,因此要把类体前面的内容去掉(保留大括号及其里面的内容)AnonymousInterface ait = new AnonymousInterface() {@Overridepublic 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("向上"){@Overridepublic void show() {System.out.println("贪吃蛇向上移动");}}, DOWN("向下"){@Overridepublic void show() {System.out.println("贪吃蛇向下移动");}}, LEFT("向左"){@Overridepublic void show() {System.out.println("贪吃蛇向左移动");}}, RIGHT("向右"){@Overridepublic 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;}// 方法重写方式一:整个枚举类型只重写一次,所有对象调用同一个/*@Overridepublic 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很多警告

Java面向对象编程(五)特殊类相关推荐

  1. 八、Java面向对象编程(类、对象、方法、重载、可变参数、作用域、构造器、this本质)

    文章目录 Java面向对象编程(类.对象.方法.重载.可变参数.作用域.构造器.this本质) 一.类与对象 1. 类与对象的引出 2. 使用现有技术解决 3. 现有技术解决的缺点分析 4. 类与对象 ...

  2. 孙卫琴:我为什么要写《Java面向对象编程》

    孙卫琴:我为什么要写<Java面向对象编程> 特约作者:孙卫琴 策划 & 设计 & 制作:李大微 当<精通Struts>和<精通Hibernate> ...

  3. java面向对象编程基础

    java面向对象编程基础 前言:什么是java 是咖啡飘香的清晨 - 是斯坦福校园意浓情深 - 是James的思想睿智 是剁手党双十一挥舞的利刃 是大数据云计算驰骋的平台 - 是ATM上吐出的钞票 - ...

  4. java面向对象编程精讲

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.包 1.1导入包中的类 1.2静态导入 1.3将类放到包中 1.4包的访问权限控制 1.5常见的系统包 二.继承 ...

  5. JAVA 面向对象编程练习(一个回合制的战斗游戏:战士,骑兵,法师,电脑互相攻击)

    文章目录 一. 小编有话说 二. 游戏的要求 三. 编程前的准备 四. 代码及详细注释 五. 结果 一. 小编有话说 前面一篇文章写了JAVA面向对象编程的思想,本文主要展示关于面向对象编程的实例(一 ...

  6. Java面向对象编程(第2版)_学习记录

    <Java面向对象编程(第2版)> 孙卫琴 编著 文章目录 一.介绍 (一)平台与开发环境 (二)一些知识 (三)数组 二.类的生命周期 (一)类的加载 1. 加载 2. 连接 3. 初始 ...

  7. java面向对象编程知识点总结

    一:今天完成 上午详细了解了java面向对象编程的一些细节,记录如下. 1)类 是一种引用类型,包含一个签名和一个主体,主体是放在花括号里面的成员,成员包括字段和方法,还有构造方法.初始化程序和嵌套类 ...

  8. Java面向对象编程篇6——注解与反射

    Java面向对象编程篇6--注解与反射 1.注解概述 Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制 Java 语言中的类.方法.变量.参数和包等都可 ...

  9. Java面向对象编程篇5——枚举

    Java面向对象编程篇5--枚举 1.枚举的概念 在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事 物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型 2.枚举的定义 使用 ...

  10. Java面向对象编程篇4——内部类

    Java面向对象编程篇4--内部类 1.内部类的概念 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类 (Inner),而这个内部类所在的类叫做外部类(Outer). 类中的内容:成员变 ...

最新文章

  1. [Android应用]《花界》V1.0 正式版隆重发布!
  2. springmuvc如何设置jsp的input跳转_如何扩大私域流量?「上线了」跳转小程序来帮你...
  3. 在ubuntu14.04中安装及测试OpenCV
  4. matlab 动画_MATLAB的动画制作和视频录制
  5. Eclipse的自动编译和手动编译
  6. php并发数据库操作,数据库的并发操作
  7. Android开发的消消乐游戏
  8. 什么是数据分析方法论
  9. 如何比较两种方法的灵敏度和特异度
  10. Nginx服务优化与防盗链
  11. python seo 外链_用python实现超级外链发布系统
  12. vba-msgbox用法详解
  13. 20210526一日总结
  14. 【附源码】计算机毕业设计java中小学在线考试系统设计与实现
  15. DIY微信朋友圈截图制作生成小程序源码下载
  16. 【软件测试】POST请求包含哪些参数
  17. php输出甲子年,甲子年是哪一年?甲子年生人今年多大?
  18. 凉心的比赛(一)补题
  19. Python 图片数据MYSQL存取(BASE64编码解码)
  20. vue 实现tab切换动态加载不同的组件

热门文章

  1. Elasticsearch 分布式搜索引擎 -- 数据聚合(聚合的种类、DSL实现聚合、RestAPI实现聚合)
  2. 一个记账易app开发
  3. kvm 虚拟机与宿主机通信
  4. 电脑怎么批量修改图片大小kb?
  5. bad assignment报错
  6. 知名互联网公司面试题
  7. 2013中国Linux内核开发者大会亮点汇总
  8. thinkphp集成系列之阿里云oss
  9. prisma2.0和nexus搭建graphql后端(1)—prisma2.0
  10. android 壁纸存储位置,手机图片去了哪?教你理清照片存放路径(全文)