枚举
自定义枚举类
- 构造器私有化,防止直接new。
2. 本类内部创建一组固定的对象。
3. 对外暴露对象(通过为对象添加 public final static 修饰符)。
4. 可以提供get方法,但是不要提供set方法(防止属性被修改)。public class Season {
private String name;
private Season(String name){ //构造器私有化
this.name = name;
}
public final static Season SPRING = new Season("spring");
public final static Season SUMMER = new Season("spring");
public final static Season AUTUMN = new Season("spring");
public final static Season WINTER = new Season("spring"); //自定义枚举类
public String getName() {
return name;
}
}
enum枚举类
1. 使用关键字 enum 代替 class。
2. 如果有多个对象,使用逗号间隔。public static final Season SPRINNG = new Season("spring"); 替换成 SPRING("spring");
3. 如果使用enum来实现枚举,要求将定义的常量对象写在最前面。public enum Season {
SPRING("spring"),SUMMER("summer"),AUTUMN("autumn"),WINTER("winter");//逗号间隔
//写在最前面
private String name;
private Season(String name){
this.name = name;
}
public String getName() {
return name;
}
}
注意事项
1. 当我们使用 enum 关键字开发一个枚举类时,默认会继承Enum类,而且是一个final类。(可以先用javac工具编译得到类,然后用javap反编译得知)
2. 如果使用无参构造器创造枚举对象,则小括号也可以省略。
3. 当有多个枚举对象时,用逗号间隔,最后一个以分号结尾。public enum Season {
SPRING,SUMMER,AUTUMN,WINTER; //省略小括号
private Season(){}
}
4. 枚举对象必须放在枚举类的行首。
5. 如果输出一个枚举对象,相当于调用了父类的toString方法,而Season类并没有重写toString方法,因此调用的是Enum类的toString方法。
那么问题就来了:Enum类的name到底是怎么定义的?public String toString() {
return name;
}
该方法返回此枚举常量的名称,与在其枚举声明中声明的名称完全相同,也就是说 SPRING的name就是SPRING。public enum Season {
SPRING("spring");
private String name;
Season(String name) {
this.name = name; // 故意又写了一个name
}
public static void main(String[] args) {
System.out.println(SPRING); //输出SPRING
}
}// 很明显,调用toString方法使用Enum类的name,而不是自己定义的
- 使用enum关键字后,就不能再继承其它类了,因为enum会隐式继承Enum,而Java是单继承机制。
7. 枚举类和普通类一样,可以实现接口。
8. 在 Test 类中调用 枚举类常量:enum 类名 implements 接口1,接口2{}
9. 枚举的构造器总是私有的,可以省略private修饰符,如果声明一个enum构造器为 public 或protected,会出现语法错误。public class Test {
public static void main(String[] args) {
Season spring = Season.SPRING; //不用new,类似于调用成员
... //使用 spring 的各种方法
}
}
Enum成员方法
- toString:Enum类重写过,返回的是当前对象名,子类可以重写该方法。
2. name:返回当前对象名,子类中不能重写。
3. ordinal:返回当前对象的位置号,默认从0开始。
4. values:返回当前枚举类中所有的常量(一个数组)。Season[] values = Season.values();
- valueOf:将字符串转换成枚举对象,要求字符串必须为已有的枚举对象名,否则报异常,异常内容如下图所示。
6. compareTo:比较两个枚举常量,比较的是编号。 ```java public enum Season { SPRING,SUMMER,AUTUMN,WINTER; public static void main(String[] args) {Season s = Enum.valueOf(Season.class,"SPRING");
//将s设置为 Season.SPRING,不在枚举类调用时,要用反射表明对应的枚举类
} }System.out.println(SPRING.toString()); //输出 SPRING
System.out.println(SUMMER.name()); //输出 SUMMER
System.out.println(SPRING.ordinal()); //输出 0
System.out.println(SPRING.compareTo(SUMMER)); // 输出 -1 (0-1)
Season[] a = values(); // 返回一个数组
Season b = valueOf("SPRING"); //如果 SPRING 不是枚举常量名则会报错
![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=)
<a name="uy0r6"></a>
# 注解
**注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。**<br />在JavaSE中,注解的使用目的比较简单,**例如标记过时的功能,忽略警告等**。**而JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替Java EE旧版中所遗留的繁冗代码和XML配置等(在框架中经常用到)。**
<a name="oUj2O"></a>
## @Override
限定某个方法是重写父类方法,该注解只能用于方法。**如果写了@Override注解,编译器就会去检查该方法是否真的重写了父类的方法,如果没有重写则报错**。
```java
@Target(ElementType.METHOD) // 限制@Override只能修饰方法 @Retention(RetentionPolicy.SOURCE)
public @interface Override {}
@interface 的说明:不是 interface,是注解类。
1. 即使不写@Override注解,也可以构成重写。
2. @Override只能修饰方法,不能修饰其它类、包、属性等。
3. @Target 是修饰注解的注释,称为元注解。
@Documented
1. @Deprecated 修饰某个元素,表示该元素已经过时。
2. 虽然不推荐使用(有一个删除线),但是仍然可以使用。
3. 可以修饰方法、类、字段、包、参数 等。
4. @Deprecated的作用可以做到新旧版本的兼容和过渡。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
@suppressWarnings
抑制编译器警告(让一些警告不显示),在 {“”} 中,可以写入希望不显示的警告信息。
@SuppressWarnings({"all"})
@SuppressWarnings({"boxing","hiding","finally"})
元注解
@Retention注解
1. RetentionPolicy.SOURCE:编译器使用时生效,然后直接丢弃注解。
2. RetentionPolicy.CLASS:编译器将把注解记录在class文件中,当运行Java程序时,JVM不会保留注解,这是默认值。
3. RetentionPolicy.RUNTIME:编译器把注解记录在class文件中,当运行Java程序时,JVM会保留注解,程序可以通过反射获取该注解。
@Target
@Target(ElementType.METHOD) // 限制@Override只能修饰方法
@Documented
@Inherited
如果没有使用异常处理机制,当抛出异常后,程序就退出了,下面的代码就不再执行。这样就导致一个不算致命的问题就会引起整个系统的崩溃,这使得程序的健壮性很差。因此,Java的设计者提供了一个 异常处理机制来解决这个问题。
如果程序员认为一段代码可能出现异常/问题,可以使用 try-catch 异常处理机制来解决,将该代码块选中 -> 快捷键 ctrl + alt + t -> 选中 try-catch。这样即使出现异常,程序也会继续执行。
异常
基本概念
Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
Error(错误)
Java虚拟机无法解决的严重问题,如:JVM系统内部错误、资源耗尽(StackOverflowError栈溢出,out of memory内存溢出),Error是严重错误,程序会崩溃。
Exception(异常)
其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如空指针访问,试图读取不存在的文件,网络连接中断等等。Exception分为两大类:运行时异常(程序运行时发生的异常)和编译时异常(编程时,编译器检查出的异常)。
异常体系图
- 异常分为两大类,运行时异常和编译时异常。
2. 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
3. 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
4. 编译时异常,是编译器要求 必须处置的异常。
常见的运行时异常
1. NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。public static void main(String[] args) {
String a = null;
System.out.println(a.length()); //抛出异常
}
2. ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常,比如一个整数除以零。public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a/b);
}
3. ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。public static void main(String[] args) {
int[] a = {1,2,3,4};
System.out.println(a[4]); //越界
}
4. ClassCastException 类型转换异常
试图将对象强制转换为不是实例的子类时,抛出该异常。 ```java public class Test { public static void main(String[] args) {
} } class A{} class B extends A{} class C extends A{}A a = new B();
B b = (B)a; // 向下转型,没问题
C c = (C)a; // a实际上是一个B类,而B类和C类没有关系,不能使用向下转型
<a name="Iluhi"></a>
### 5. NumberFormatException 数字格式不正确异常
**当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常—— 使用该异常可以确保输入的是满足条件的数字。**
```java
public static void main(String[] args) {
String name = "青眼白龙";
System.out.println(Integer.parseInt(name));
}
常见的编译时异常
异常处理
异常处理就是当异常发生时,对异常的处理方式。有两种:1. try-catch-finally,程序员在代码中捕获发生的异常,自行处理。 2. throws,将发生的异常抛出,交给调用者来处理,最顶级的处理者就是JVM。
对于编译异常,程序中必须处理,比如 try-catch 或者 throws。对于运行异常,程序中如果没有处理,默认就是throws的方式处理。
1. try-catch-finally
注意事项:
1. 如果异常发生了,则异常发生后面的try代码块不会执行,直接进入到 catch 块。
public static void main(String[] args) {
try{
String a = "青眼白龙";
int b = Integer.parseInt(a);
System.out.println("异常后面的代码"); //这行直接不执行了
}catch(Exception e){
System.out.println("出现异常"); // 出现异常
}
}
- 如果异常没有发生,则顺序执行try的代码块,不会进入到catch。
3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等),则使用finally 语句(finally 可以省略)。
4. 可以有多个 catch 语句,捕获不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前。如果发生异常,只会匹配一个catch。 ```java public static void main(String[] args) {
try {
} catch (NullPointerException e) { // 单独指定的异常,在前Person person = new Person();
person = null;
System.out.println(person.getClass()); // NullPointerException
int n1 = 10;
int n2 = 0;
int res = n1 / n2; // ArithmeticException
}catch (ArithmeticException e){System.out.println("空指针异常");
}catch (Exception e){// 单独指定的异常
System.out.println("算术异常");
}// 父类异常
System.out.println("其他异常");
}
5. ** 可以进行 try - finally 配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉**。应用场景就是执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。<br />**6. 如果 catch 里有 return 语句,执行到 return 时不会立刻执行,而是先去执行finally(因为执行完return程序就结束了,而finally必须执行),如果finally没有return 语句,最后执行catch的return,如果有则执行finally的return,然后程序结束(不执行catch的return),throw语句也是同理。**
<a name="Tgtot"></a>
### 2. throws
1. 如果一个方法可能产生某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,**表明该方法将不对这些异常进行处理,而由该方法的的调用者负责处理。**<br />**2. throws语句可以抛出多个异常,可以是方法中产生的异常类型,也可以是它的父类。**
```java
class A{
public void f1() throws FileNotFoundException,Exception,ArithmeticException{
...
}
}
3. 子类重写父类的方法时,所抛出的异常类型要么与父类一致,要么是父类抛出的异常类型的子类型。
class A{
public void f1() throws FileNotFoundException{}
}
class B extends A{
@Override
public void f1() throws FileNotFoundException {
//相同或者为子类型
super.f1();
}
}
4. 在throws过程中,如果有方法 try-catch,就相当于处理异常,可以不必throws
5. 如果一个方法调用了另外一个带有throws的方法,那么需要进行处理。
public void f1() {
f2(); //f1没有做任何处理,会报错
}
public void f2() throws FileNotFoundException{} //抛出一个编译异常
f2抛出了一个编译异常,这是必须处理的,由于调用者是f1,因此f1需要throws这个异常或者使用 try-catch 语句处理。
public void f1() { // 第一种方法
try {
f2();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void f1() throws FileNotFoundException{ // 第二种方法
f2();
}
如果f2抛出了一个运行异常,因为运行异常并不要求程序员显示处理,有默认处理机制。因此不会报错。
public void f1() {
f2(); //正确
}
public void f2() throws NullPointerException{} //抛出运行异常
自定义异常
当程序中出现了某些“错误”,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
步骤
1. 定义类:自定义异常类名,继承Exception或RuntimeException
2. 如果继承Exception,属于编译异常。
3. 如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException,好处是可以使用默认处理机制,要不然还得在调用方法里加throws)
class AgeException extends RuntimeException{
public AgeException(String message) { //构造器
super(message);
}
}
public class Test {
public static void main(String[] args) {
Scanner a = new Scanner(System.in);
int b = a.nextInt();
if(b<18){
throw new AgeException("未成年"); //抛出异常
}
}
} // throw new ArrayIndexOutOfBoundsException("数组过大")
// 也可以在已有的类型中写参数表示异常信息
throw和throws
一些案例
public static void main(String[] args) {
try {
throw new AgeException("未成年");
} catch(Exception e){
System.out.println("catch方法");
throw new AgeException("未成年");
} finally{
System.out.println("finally方法");
//
return;
}
}
try里throw的异常被catch接收(因此不会输出异常),然后输出”catch方法”,紧接着catch方法又throw了一个异常,但是因为如果执行throw的话,程序就终止了,而finally必须执行,因此先不执行throw方法,执行finally,输出”finally方法”,然后回到throw方法,输出异常。
public static void main(String[] args) {
try {
throw new AgeException("未成年");
} catch(Exception e){
System.out.println("catch方法");
throw new AgeException("未成年");
} finally{
System.out.println("finally方法");
return; // 加上了return
}
}
执行到catch里的throw时,调用finally方法,然后由于finally里有return语句,因此程序结束,直接不输出异常了。 (如果catch里没有return和throw,那么按顺序执行)P458 示例
综合练习 P495