枚举

自定义枚举类

  1. 构造器私有化,防止直接new。
    2. 本类内部创建一组固定的对象。
    3. 对外暴露对象(通过为对象添加 public final static 修饰符)。
    4. 可以提供get方法,但是不要提供set方法(防止属性被修改)。
    1. public class Season {
    2. private String name;
    3. private Season(String name){ //构造器私有化
    4. this.name = name;
    5. }
    6. public final static Season SPRING = new Season("spring");
    7. public final static Season SUMMER = new Season("spring");
    8. public final static Season AUTUMN = new Season("spring");
    9. public final static Season WINTER = new Season("spring"); //自定义枚举类
    10. public String getName() {
    11. return name;
    12. }
    13. }

    enum枚举类

    1. 使用关键字 enum 代替 class。
    1. public static final Season SPRINNG = new Season("spring"); 替换成 SPRING("spring");
    2. 如果有多个对象,使用逗号间隔。
    3. 如果使用enum来实现枚举,要求将定义的常量对象写在最前面。
    1. public enum Season {
    2. SPRING("spring"),SUMMER("summer"),AUTUMN("autumn"),WINTER("winter");//逗号间隔
    3. //写在最前面
    4. private String name;
    5. private Season(String name){
    6. this.name = name;
    7. }
    8. public String getName() {
    9. return name;
    10. }
    11. }

    注意事项

    1. 当我们使用 enum 关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类。(可以先用javac工具编译得到类,然后用javap反编译得知)
    image.png
    2. 如果使用无参构造器创造枚举对象,则小括号也可以省略。
    1. public enum Season {
    2. SPRING,SUMMER,AUTUMN,WINTER; //省略小括号
    3. private Season(){}
    4. }
    3. 当有多个枚举对象时,用逗号间隔,最后一个以分号结尾。
    4. 枚举对象必须放在枚举类的行首。
    5. 如果输出一个枚举对象,相当于调用了父类的toString方法,而Season类并没有重写toString方法,因此调用的是Enum类的toString方法
    1. public String toString() {
    2. return name;
    3. }
    那么问题就来了:Enum类的name到底是怎么定义的?
    image.png
    该方法返回此枚举常量的名称,与在其枚举声明中声明的名称完全相同,也就是说 SPRING的name就是SPRING。
    1. public enum Season {
    2. SPRING("spring");
    3. private String name;
    4. Season(String name) {
    5. this.name = name; // 故意又写了一个name
    6. }
    7. public static void main(String[] args) {
    8. System.out.println(SPRING); //输出SPRING
    9. }
    10. }// 很明显,调用toString方法使用Enum类的name,而不是自己定义的
  2. 使用enum关键字后,就不能再继承其它类了,因为enum会隐式继承Enum,而Java是单继承机制。
    7. 枚举类和普通类一样,可以实现接口。
    1. enum 类名 implements 接口1,接口2{}
    8. 在 Test 类中调用 枚举类常量:
    1. public class Test {
    2. public static void main(String[] args) {
    3. Season spring = Season.SPRING; //不用new,类似于调用成员
    4. ... //使用 spring 的各种方法
    5. }
    6. }
    9. 枚举的构造器总是私有的,可以省略private修饰符,如果声明一个enum构造器为 public 或protected,会出现语法错误。

    Enum成员方法

  3. toString:Enum类重写过,返回的是当前对象名,子类可以重写该方法。
    2. name:返回当前对象名,子类中不能重写。
    3. ordinal:返回当前对象的位置号,默认从0开始。
    4. values:返回当前枚举类中所有的常量(一个数组)。
    1. Season[] values = Season.values();
  4. valueOf:将字符串转换成枚举对象,要求字符串必须为已有的枚举对象名,否则报异常,异常内容如下图所示。
    image.png
    1. Season s = Enum.valueOf(Season.class,"SPRING");
    2. //将s设置为 Season.SPRING,不在枚举类调用时,要用反射表明对应的枚举类
    6. compareTo:比较两个枚举常量,比较的是编号。 ```java public enum Season { SPRING,SUMMER,AUTUMN,WINTER; public static void main(String[] args) {
    1. System.out.println(SPRING.toString()); //输出 SPRING
    2. System.out.println(SUMMER.name()); //输出 SUMMER
    3. System.out.println(SPRING.ordinal()); //输出 0
    4. System.out.println(SPRING.compareTo(SUMMER)); // 输出 -1 (0-1)
    5. Season[] a = values(); // 返回一个数组
    6. Season b = valueOf("SPRING"); //如果 SPRING 不是枚举常量名则会报错
    } }
  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/gif/23175776/1641733812123-1fa60536-e744-4196-a15b-2a259ebe7995.gif#clientId=ud73b5b3c-f89c-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u2345cac1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1&originWidth=1&originalType=url&ratio=1&rotation=0&showTitle=false&size=43&status=done&style=none&taskId=ufeccfaff-a129-4d0d-bcec-48d11ce0d8a&title=)
  2. <a name="uy0r6"></a>
  3. # 注解
  4. **注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。**<br />在JavaSE中,注解的使用目的比较简单,**例如标记过时的功能,忽略警告等**。**而JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替Java EE旧版中所遗留的繁冗代码和XML配置等(在框架中经常用到)。**
  5. <a name="oUj2O"></a>
  6. ## @Override
  7. 限定某个方法是重写父类方法,该注解只能用于方法。**如果写了@Override注解,编译器就会去检查该方法是否真的重写了父类的方法,如果没有重写则报错**。
  8. ```java
  9. @Target(ElementType.METHOD) // 限制@Override只能修饰方法 @Retention(RetentionPolicy.SOURCE)
  10. public @interface Override {}

@interface 的说明:不是 interface,是注解类。
1. 即使不写@Override注解,也可以构成重写。
2. @Override只能修饰方法,不能修饰其它类、包、属性等。
3. @Target 是修饰注解的注释,称为元注解。

@Documented

1. @Deprecated 修饰某个元素,表示该元素已经过时。
2. 虽然不推荐使用(有一个删除线),但是仍然可以使用。
3. 可以修饰方法、类、字段、包、参数 等。
4. @Deprecated的作用可以做到新旧版本的兼容和过渡。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }

@suppressWarnings

抑制编译器警告(让一些警告不显示),在 {“”} 中,可以写入希望不显示的警告信息。

  1. @SuppressWarnings({"all"})
  2. @SuppressWarnings({"boxing","hiding","finally"})

image.png
image.png
image.png

元注解

本身作用不大,主要是了解干什么的。
image.png

@Retention注解

image.png
1. RetentionPolicy.SOURCE:编译器使用时生效,然后直接丢弃注解。
2. RetentionPolicy.CLASS:编译器将把注解记录在class文件中,当运行Java程序时,JVM不会保留注解,这是默认值。
3. RetentionPolicy.RUNTIME:编译器把注解记录在class文件中,当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解。

@Target

image.png

  1. @Target(ElementType.METHOD) // 限制@Override只能修饰方法

@Documented

image.png

@Inherited

image.png
如果没有使用异常处理机制,当抛出异常后,程序就退出了,下面的代码就不再执行。这样就导致一个不算致命的问题就会引起整个系统的崩溃,这使得程序的健壮性很差。因此,Java的设计者提供了一个 异常处理机制来解决这个问题。
如果程序员认为一段代码可能出现异常/问题,可以使用 try-catch 异常处理机制来解决,将该代码块选中 -> 快捷键 ctrl + alt + t -> 选中 try-catch。这样即使出现异常,程序也会继续执行。

异常

基本概念

Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常

Error(错误)

Java虚拟机无法解决的严重问题,如:JVM系统内部错误、资源耗尽(StackOverflowError栈溢出,out of memory内存溢出),Error是严重错误,程序会崩溃。

Exception(异常)

其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等。Exception分为两大类:运行时异常(程序运行时发生的异常)和编译时异常(编程时,编译器检查出的异常)。

异常体系图

  1. 异常分为两大类,运行时异常和编译时异常。
    2. 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
    3. 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
    4. 编译时异常,是编译器要求 必须处置的异常。
    image.pngimage.png image.png
    image.png

    常见的运行时异常

    1. NullPointerException 空指针异常

    当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
    1. public static void main(String[] args) {
    2. String a = null;
    3. System.out.println(a.length()); //抛出异常
    4. }

    2. ArithmeticException 数学运算异常

    当出现异常的运算条件时,抛出此异常,比如一个整数除以零。
    1. public static void main(String[] args) {
    2. int a = 10;
    3. int b = 0;
    4. System.out.println(a/b);
    5. }

    3. ArrayIndexOutOfBoundsException 数组下标越界异常

    用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
    1. public static void main(String[] args) {
    2. int[] a = {1,2,3,4};
    3. System.out.println(a[4]); //越界
    4. }

    4. ClassCastException 类型转换异常

    试图将对象强制转换为不是实例的子类时,抛出该异常。 ```java public class Test { public static void main(String[] args) {
    1. A a = new B();
    2. B b = (B)a; // 向下转型,没问题
    3. C c = (C)a; // a实际上是一个B类,而B类和C类没有关系,不能使用向下转型
    } } class A{} class B extends A{} class C extends A{}
  1. <a name="Iluhi"></a>
  2. ### 5. NumberFormatException 数字格式不正确异常
  3. **当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常—— 使用该异常可以确保输入的是满足条件的数字。**
  4. ```java
  5. public static void main(String[] args) {
  6. String name = "青眼白龙";
  7. System.out.println(Integer.parseInt(name));
  8. }

常见的编译时异常

image.png

异常处理

异常处理就是当异常发生时,对异常的处理方式。有两种:1. try-catch-finally,程序员在代码中捕获发生的异常,自行处理。 2. throws,将发生的异常抛出,交给调用者来处理,最顶级的处理者就是JVM。
对于编译异常,程序中必须处理,比如 try-catch 或者 throws。对于运行异常,程序中如果没有处理,默认就是throws的方式处理。

1. try-catch-finally

image.pngimage.png 注意事项:
1. 如果异常发生了,则异常发生后面的try代码块不会执行,直接进入到 catch 块。

  1. public static void main(String[] args) {
  2. try{
  3. String a = "青眼白龙";
  4. int b = Integer.parseInt(a);
  5. System.out.println("异常后面的代码"); //这行直接不执行了
  6. }catch(Exception e){
  7. System.out.println("出现异常"); // 出现异常
  8. }
  9. }
  1. 如果异常没有发生,则顺序执行try的代码块,不会进入到catch。
    3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用finally 语句(finally 可以省略)。
    4. 可以有多个 catch 语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前。如果发生异常,只会匹配一个catch。 ```java public static void main(String[] args) {
    try {
    1. Person person = new Person();
    2. person = null;
    3. System.out.println(person.getClass()); // NullPointerException
    4. int n1 = 10;
    5. int n2 = 0;
    6. int res = n1 / n2; // ArithmeticException
    } catch (NullPointerException e) { // 单独指定的异常,在前
    1. System.out.println("空指针异常");
    }catch (ArithmeticException e){
    1. // 单独指定的异常
    2. System.out.println("算术异常");
    }catch (Exception e){
    1. // 父类异常
    2. System.out.println("其他异常");
    }
    }
  1. 5. ** 可以进行 try - finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉**。应用场景就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。<br />**6. 如果 catch 里有 return 语句,执行到 return 时不会立刻执行,而是先去执行finally(因为执行完return程序就结束了,而finally必须执行),如果finally没有return 语句,最后执行catchreturn,如果有则执行finallyreturn,然后程序结束(不执行catchreturn),throw语句也是同理。**
  2. <a name="Tgtot"></a>
  3. ### 2. throws
  4. 1. 如果一个方法可能产生某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,**表明该方法将不对这些异常进行处理,而由该方法的的调用者负责处理。**<br />**2. throws语句可以抛出多个异常,可以是方法中产生的异常类型,也可以是它的父类。**
  5. ```java
  6. class A{
  7. public void f1() throws FileNotFoundException,Exception,ArithmeticException{
  8. ...
  9. }
  10. }

3. 子类重写父类的方法时,所抛出的异常类型要么与父类一致,要么是父类抛出的异常类型的子类型。

  1. class A{
  2. public void f1() throws FileNotFoundException{}
  3. }
  4. class B extends A{
  5. @Override
  6. public void f1() throws FileNotFoundException {
  7. //相同或者为子类型
  8. super.f1();
  9. }
  10. }

4. 在throws过程中,如果有方法 try-catch,就相当于处理异常,可以不必throws
5. 如果一个方法调用了另外一个带有throws的方法,那么需要进行处理。

  1. public void f1() {
  2. f2(); //f1没有做任何处理,会报错
  3. }
  4. public void f2() throws FileNotFoundException{} //抛出一个编译异常

f2抛出了一个编译异常,这是必须处理的,由于调用者是f1,因此f1需要throws这个异常或者使用 try-catch 语句处理。

  1. public void f1() { // 第一种方法
  2. try {
  3. f2();
  4. } catch (FileNotFoundException e) {
  5. e.printStackTrace();
  6. }
  7. }
  8. public void f1() throws FileNotFoundException{ // 第二种方法
  9. f2();
  10. }

如果f2抛出了一个运行异常,因为运行异常并不要求程序员显示处理,有默认处理机制。因此不会报错。

  1. public void f1() {
  2. f2(); //正确
  3. }
  4. public void f2() throws NullPointerException{} //抛出运行异常

image.pngimage.png

自定义异常

当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。

步骤

1. 定义类:自定义异常类名,继承Exception或RuntimeException
2. 如果继承Exception,属于编译异常。
3. 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException,好处是可以使用默认处理机制,要不然还得在调用方法里加throws)

  1. class AgeException extends RuntimeException{
  2. public AgeException(String message) { //构造器
  3. super(message);
  4. }
  5. }
  6. public class Test {
  7. public static void main(String[] args) {
  8. Scanner a = new Scanner(System.in);
  9. int b = a.nextInt();
  10. if(b<18){
  11. throw new AgeException("未成年"); //抛出异常
  12. }
  13. }
  14. } // throw new ArrayIndexOutOfBoundsException("数组过大")
  15. // 也可以在已有的类型中写参数表示异常信息

image.png

throw和throws

image.png

一些案例

  1. public static void main(String[] args) {
  2. try {
  3. throw new AgeException("未成年");
  4. } catch(Exception e){
  5. System.out.println("catch方法");
  6. throw new AgeException("未成年");
  7. } finally{
  8. System.out.println("finally方法");
  9. //
  10. return;
  11. }
  12. }

image.png
try里throw的异常被catch接收(因此不会输出异常),然后输出”catch方法”,紧接着catch方法又throw了一个异常,但是因为如果执行throw的话,程序就终止了,而finally必须执行,因此先不执行throw方法,执行finally,输出”finally方法”,然后回到throw方法,输出异常。

  1. public static void main(String[] args) {
  2. try {
  3. throw new AgeException("未成年");
  4. } catch(Exception e){
  5. System.out.println("catch方法");
  6. throw new AgeException("未成年");
  7. } finally{
  8. System.out.println("finally方法");
  9. return; // 加上了return
  10. }
  11. }

image.png
执行到catch里的throw时,调用finally方法,然后由于finally里有return语句,因此程序结束,直接不输出异常了。 (如果catch里没有return和throw,那么按顺序执行)P458 示例
综合练习 P495