注解—可以被编译器读取
- 定义
注解是以“@注解名”在代码中存在的,还可以添加一些参数值
@Override
- 作用
- 用于检测被修饰的方法是否是有效的重写方法,若不是则报编译错误
- 注意
只能标记在方法上
@Deprecated
- 作用
可用于表示被标记的数据已经过时(但还可以正常使用),不建议使用。 - 使用范围
可用于修饰属性、方法、构造、类、包、局部变量、参数 - 注意
(1)若使用的方法是其它类中的方法,则在调用该方法时会出现如下图所示的情况(即调用的方法有删除线),代表不建议使用(即在该方法所在的类中,该方法被标记已过时)
(2)若调用的方法是本类中的方法则不会出现如上图所示的情况,仍正常显示,只是说在你使用时,idea提示出的方法会有删除线,如图一图二所示
@SupperssWarnings
- 作用
抑制编译警告------该注解写在方法上或者类上以此来抑制整个方法体内或者整个类中的编译警告 - 使用范围
可用于修饰类、属性、方法、构造器、局部变量、参数 - 举例说明
在图一中你定义了一个变量one并赋值为100,但你并未使用,所以在该行的右侧下角处会出现黄色警告,且idea页面右上角会有黄色三角形警告,如图所示
该警告只是在说明你所定义的这个变量没有被使用,并不是代码错误,所以此时你可以抑制编译警告,如图所示,此时右上角为绿色对勾,代表程序没有任何问题
JUnit—回归测试框架(framework)
- 作用
JUnit是一个Java语言的测试框架,它可以帮助开发人员编写和运行单元测试。JUnit的作用在于提供一种简单的方法来测试代码的正确性,以确保代码在修改后仍然能够正常工作。通过JUnit测试,可以快速发现代码中的错误,并及时修复它们,从而提高代码的质量和可靠性。 - 架包可以引入Moudle,也可以引入到工程中,但是架包的引入原则是让其影响尽量少的内容
- 使用步骤(此处以day8module为例)
(1)右键day8建一个文件夹,并命名为lib(习惯上架包命名为lib)
(2)将两个架包复制粘贴到lib文件夹下
(3)选中导入的所有架包然后右键—>Add as Library—>OK,此时即可将两个架包展开
常用的JUnit注解
@Test—用于对项目的测试验证
在你建测试类时,测试类需要有main方法才可以运行,假设现在没有main方法,但你还想运行该类中的内容,则可以添加个注解Test(如图一所示),此时即可运行main方法。但是在加上Test注解后,该注解会标红报错,原因是没有导入对应的类库,则导入对应的类库即可,此时注解对应的方法即可运行(如图二所示)
- 注意:使用@Test的另一种方法,该方法可以不导入两个架包
点击@Test然后Alt+Enter–>Add ‘JUnit4’ to classpath—>OK—>然后等待下载结束—>点击@Test然后Alt+Enter—>Import Class即可
@Before
- 作用
带before注解的方法会在每一次带Test注解的方法运行前被调用执行
@After
- 作用
会在每一次带Test注解的方法运行后被调用执行
@BeforeClass
- 作用
会在所有的带Test注解的方法运行前有且仅被调用依次执行
@AfterClass
- 作用
会在所有的带Test注解的方法运行结束后有且仅被调用一次执行
注意
- Test、Before、After三种注解只能用在普通方法上,不能应用于静态方法
- BeforeClass、AfterClass两种注解应用于静态方法
自定义注解
- 一个完整的注解需要有三部分:声明(本次学习只讲解声明)、使用、读取
元注解
- 作用
用于修饰注解的注解
@Target()------用于描述注解的使用范围
类型 | 解释 |
---|---|
ElementType.METHOD | 表示注解可以在方法上使用 |
ElementType.TYPE | 表示注解可以在类、接口、枚举等类型上使用 |
ElementType.FIELD | 表示注解可以在字段或属性上使用 |
ElementType.PACKAGE | 表示注解可以在包上使用 |
ElementType.PARAMETER | 表示注解可以在方法参数上使用 |
ElementType.CONSTRUCTOR | 表示注解可以在构造方法上使用 |
ElementType.LOCAL_VARIABLE | 表示注解可以在局部变量上使用 |
ElementType.ANNOTATION_TYPE | 表示注解可以在注解上使用 |
ElementType.TYPE_PARAMETER | 表示注解可以在泛型参数上使用 |
ElementType.TYPE_USE | 表示注解可以在类型使用上使用 |
@Retention()------用于自定义注解的保留策略
- 类型详解
类型 | 解释 |
---|---|
RetentionPolicy.SOURCE | 表示注解只在源代码中存在,编译时会被忽略。(即在Java源代码中使用了这种注解,但在编译Java源代码时,这种注解并不会被编译进class文件中,也就是说,在编译后的class文件中,这种注解并不存在。这意味着这些注解不会对运行时产生任何影响,只在编译时起作用。) |
RetentionPolicy.CLASS | 表示注解在编译时存在,但是在运行时会被忽略。(即在Java源代码中使用了这种注解,在编译Java源代码时,这种注解会被编译进class文件中,但在运行Java程序时,这种注解不会被虚拟机加载和使用(即运行时不会被加载到内存中)。) |
RetentionPolicy.RUNTIME | 表示注解在运行时存在,可以通过反射机制获取注解信息。(即在Java源代码中使用了这种注解,在编译Java源代码时,这种注解会被编译进class文件中,并且在运行Java程序时,这种注解仍然存在,会被加载到内存中,可以通过反射机制获取注解信息。) |
- 注意
只有RUNTIME阶段才能被反射(即用对象去读取类的信息)读取到
@Documented
- 作用
是一个标记注解,它用于标记其他自定义注解,以指示这些注解应该包含在生成的文档中。 - 示例
import java.lang.annotation.Documented;
@Documented
public @interface MyCustomAnnotation {
String value() default "";
int count() default 0;
}
在上面的例子中,MyCustomAnnotation 注解被标记为 @Documented,这意味着当生成代码文档时,包含了这个注解的类或方法将会在文档中显示注解的信息,包括注解的成员(value 和 count)以及它们的默认值等信息。
@Inherited
- 作用
用于标记自定义注解。它的作用是指示一个注解是否应该由子类继承。具体来说,当一个类被标记了一个带有 @Inherited 注解的自定义注解时,如果子类没有自己的该注解,那么子类将继承父类的该注解。 - 注意
该注解只能用来注解类的自定义注解,不适用于注解接口、方法、字段等其他程序元素。 - 示例
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInheritedAnnotation {
String value() default "";
}
述示例中,MyInheritedAnnotation
是一个自定义注解,它被标记为 @Inherited
。现在,假设有以下类:
@MyInheritedAnnotation("Parent Class")
public class Parent {
}
//@MyInheritedAnnotation("Parent Class")
/*
由于Child类继承自Parent类,而Parent类有个@MyInheritedAnnotation("Parent Class")自定义注解,且该自定义
注解已经被元注解@Inherited标记,所以即使子类Child类没有标记注解,其也会有一个继承自父类Parent类的自定义
注解存在
*/
public class Child extends Parent {
}
在上面的代码中,Parent 类被标记为 @MyInheritedAnnotation("Parent Class")
。由于 @MyInheritedAnnotation
带有 @Inherited
注解,因此当你检查 Child 类是否具有该注解时,即使 Child 类自身没有该注解,它也会继承自父类 Parent 的注解。
由于Child类继承自Parent类,而Parent类有个@MyInheritedAnnotation(“Parent Class”)自定义注解,且该自定义注解已经被元注解@Inherited标记,所以即使子类Child类没有标记注解,其也会有一个继承自父类Parent类的自定义注解存在
可用以下代码测试是否Child类继承了父类Parent类的自定义注解@MyInheritedAnnotation("Parent Class")
。若Chlid继承了父类的自定义注解则会输出该父类自定义注解的值即Parent Class
public class Main {
public static void main(String[] args) {
//获取在 Child 类上标记的 @MyInheritedAnnotation 注解的实例,并将其存储在 annotation 变量中
MyInheritedAnnotation annotation = Child.class.getAnnotation(MyInheritedAnnotation.class);
if (annotation != null) {
//若Child类上有该自定义注解则会输出该自定义注解的属性值,即Parent Class
System.out.println(annotation.value());
} else {
System.out.println("Annotation not found on Child class.");
}
}
}
Child.class
:这部分表示获取 Child 类的 Class 对象,Class 对象包含了有关类的元信息。
.getAnnotation(MyInheritedAnnotation.class)
:通过 Class 对象的 getAnnotation 方法,我们可以尝试获取指定类型的注解。在这里,我们希望获取 Child 类上标记的 @MyInheritedAnnotation 注解的实例。如果该类上没有标记该注解,这个方法会返回 null。
MyInheritedAnnotation annotation
:这部分是声明一个名为 annotation 的变量,并将 getAnnotation 方法的返回值存储在这个变量中。这样,如果 Child 类上存在 @MyInheritedAnnotation 注解,那么 annotation 将引用该注解的实例,否则它将为 null。
自定义元注解
-
步骤
(1)使用‘@interface’ 关键字定义注解类型。在注解类型中定义需要的属性和默认值。
(2)为注解类型指定元注解。元注解是用来描述注解的注解,常用的元注解包括@Retention、@Target、@Documented等。
-
注意
(1)在元注解中定义属性时,属性需要加小括号,否则会报错,且为该属性赋值是在注解时赋值;(注意:当元注解中只有一个属性,且该属性被命名为value,则赋值时可以直接赋值,不用写成属性名=属性值来进行赋值,反之则必须写成属性名=属性值来进行赋值)
(2)定义元注解时,若有多个属性。则在注解时对注解中的属性进行赋值时,需要利用属性名=属性值来进行赋值
例题:定义一个可被继承的自定义注解,然后一个类被标记该自定义注解,判断该类的子类是否继承了该注解 -
自定义注解
@Annotation()
package at.guigu.zidingyizhujie;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)//该自定义注解必须在java运行时一直存在并加载到内存中,否则检测不到子类继承了父类的自定义注解
@Documented
@Inherited
public @interface Annotation {
String[] value() default {"nihao", "qwe"};
}
/*
元注解:用于修饰注解的注解
@Target():描述注解的适用范围
@Retention():用于自定义注解的保留策略
@Documented():用于表示这些注解是否应该包含在生成的文档中
@Inherited():用于表示该注解是否应该被子类继承
*/
/*
自定义元注解:
需要使用@interface来定义
*/
- 被标记自定义注解的Father类
package at.guigu.zidingyizhujie;
@Annotation({"HAOMA","BHAO"})
public class Father {
private String name;
private int age;
public Father() {
}
public Father(String name, int age) {
this.name = name;
this.age = 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;
}
public void play() {
System.out.println("玩耍");
}
}
- Father类的子类Son
package at.guigu.zidingyizhujie;
public class Son extends Father{
private String gender;
public Son() {
super();
}
public Son(String name, int age, String gender) {
super(name, age);
this.gender = gender;
}
}
- 测试是否子类是否继承了父类标记的自定义注解
package at.guigu.zidingyizhujie;
import java.util.Arrays;
public class TestSon {
public static void main(String[] args) {
Annotation ann = Son.class.getAnnotation(Annotation.class);
if(ann != null) {
String[] values = ann.value();
System.out.println(Arrays.toString(values));
}else {
System.out.println("子类Son未继承父类中的自定义注解Annotation()");
}
}
}
异常
- 定义
异常是指程序在运行过程中发生的错误或者异常情况,例如空指针异常、数组越界异常等。Java中的异常是通过Exception类及其子类来表示的。
- 异常报错原理
(1)在出现异常的这行代码处创建该类异常的对象,对象向上抛出异常。
(2)在上述例子中main方法是虚拟机调用的,所以对象的异常会抛出给虚拟机。
注意:
当异常对象抛给虚拟机时,虚拟机会立即终止当前进程,后续进行不会在执行。所以我们要根据异常种类对异常进行处理。
异常体系及分类
Throwable
- 定义
java.lang.Throwable类是java语言中所有错误或异常的超类(爷爷类) - Throwable的子类
Error(不学)和Exception - Error------一般是程序解决不了的错误,非常严重的错误
最常见的就是VirtualMachineError(虚拟内存溢出异常),它有两个经典的子类:StackOverflowError、OutOfMemoryError。例子如下 - Exception
表示普通异常,程序员可以处理的错误
Exception异常分类
- 部分常见的受检异常和非受检异常如下图所示
受检异常------会报编译器错误,必须处理
- 定义
(1)指在编译时就需要处理的异常,也称为已检查异常
(2)是指Exception及其子类中除了RuntimeException及其子类以外的异常 - 注意
受检异常必须在方法签名中声明,或者通过try-catch语句进行处理,否则编译时会报错。
非受检异常------不会报编译器错误,在运行时才会报错
- 定义
指在运行时才会发生的异常,也称为未检查异常 - 注意
Java中的非受检异常是指RuntimeException及其子类中的异常,例如空指针异常、数组越界异常等。非受检异常不需要在方法签名中声明,也不需要在方法中进行处理,如果发生了非受检异常,程序会立即终止,并抛出异常信息。
异常处理
- 异常处理机制详见图
- 异常处理方法
异常处理的五个关键字:try、catch、finally、throw、throws
捕获异常方式一:try…catch
JDK1.7之前
- 语法格式
- try块的作用
监控可能出现异常的代码。若代码出现异常则在出现异常的那行代码处创建该类异常的对象向上抛出。且try一旦发现有异常抛出则会立即通知catch块,告诉catch块抛出异常的类型,同时停止执行try块中剩余的代码。 - catch块的作用
catch块根据自己的参数确定捕获异常的种类并按照参数捕获异常.一旦异常捕获成功则进入catch块执行catch块代码,若捕获失败则异常对象会继续抛出,且不会执行后续代码 - 注意事项
(1)try块不能独立出现,要不必须和catch块或finally块同时出现;要不就是同时和catch块及finall块出现
(2)若try块中没有异常对象抛出则正常执行try块,没有异常则不进入catch块
(3)catch块的数量不受限制,按照编写catch块的顺序依次匹配异常对象,若当前catch块匹配异常成功,则进入当前catch块执行代码。代码执行完毕退出整个try-catch结构,后续catch块不执行。若当前catch块匹配异常不成功则依次匹配剩下的catch块的异常
(4)catch块捕获Exception:由于Exception异常是所有异常对象的总父类,所以catch块中可以写Exception异常,必然会匹配成功
(5)catch块捕获Exception时,只能将其放在最后一个catch块中。若放在前面则所有异常都会被它所匹配,后面的catch块就没有机会执行了。因此,将Exception异常放在最后一个catch块中可以保证在前面的catch块无法匹配异常时,才会被Exception块所匹配,保证程序的正确性和健壮性。 - 代码实现截图
(1)代码
(2)数字异常结果截图
(3)输入格式异常结果截图
注意:以上是在非受检异常中使用了try…catch语句,原因:非受检异常中可以使用try…catch语句来捕获可能导致程序中断或不正常行为的异常,然后采取适当的措施来处理这些异常
JDK1.7之后
- 语法格式
- 代码实现截图
(1)代码
(2)数学异常结果截图
(3)输入格式异常结果截图
捕获异常方式二:try…catch…finally
- 语法格式
与捕获异常方式一try…catch一样 - 注意事项
(1)其使用方式及注意事项与捕获异常方式一一样
(2)其也有JDK1.7之前和JDK1.7之后,与捕获异常方式一一样
(3)无论是否有异常,finally块内的代码均会正常执行,甚至是try块内写了return,finall块仍会正常执行,如图所示
注意: 但如果你在try…catch块内写入结束进程的代码,则才会不执行
(4)finally块常用来释放资源,且finally块中的代码总是会在方法返回之前(即return之前)执行,无论方法是正常返回还是抛出异常返回。
(5)return语句的返回值会返回给调用该方法的代码,也就是方法的调用者。如果方法是被另一个方法调用的,那么返回值就会返回给调用它的方法;如果方法是被程序的入口main方法调用的,那么返回值就会返回给操作系统。在Java中,方法的返回值可以是任何基本数据类型或引用类型。 - try块内有return语句而catch块和finally块中没有return语句
注意:
(1)假如正在执行的try块内没有异常则会一直执行try块内的代码知道执行到代码最后return语句的前一行,此时会转而执行finally块中的代码,待finally代码块中的代码执行完毕后转而回到try块内执行return语句,不会执行try…catch…finally块后的代码;
(2)假如正在执行的try块内有异常则会在出现异常的那行代码处开始停止执行try块内的后续代码,转而执行匹配到异常的catch块代码,待catch内的代码执行完毕后会执行finally中的代码直至所有finally中的代码执行完毕,然后开始执行try…catch…finally块后的代码;
执行顺序: 执行try块代码,若有受检异常则跳到catch块中匹配异常,匹配成功后执行catch块内的代码,执行完catch块中的代码后跳到finally块中执行该块内代码,最后回到try块中执行return语句,最后继续执行try…catch…finally语句块后面的代码
即:try块—>catch块—>finally块—>try…catch…finally块后的代码
package com.atguigu.day09;
import java.util.InputMismatchException;
import java.util.Scanner;
public class tryBlock {
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.print("请输入被除数:");
int a = input.nextInt();
System.out.print("请输入除数:");
int b = input.nextInt();
int result = a / b;
System.out.println(result);
return;
//AritheticException为数学一场
}catch (ArithmeticException | InputMismatchException e){
e.printStackTrace();//打印异常的栈信息
System.out.println("catch块执行");
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("这是finally代码块,为必须执行的代码");
}
System.out.println("程序运行结束");
}
}
此为无异常的运行截图
此为有异常的运行截图
- try块和catch块内均有return语句而finally块中没有return语句
注意: 在finally块中没有return语句的情况下,try块和catch块中的return语句都有可能先执行,取决于程序的执行顺序。
(1)假如正在运行try块内的代码如果没出现异常则不会执行catch块内的代码,此时在执行到try块内的return语句的前一条代码处时会转而执行finally中的所有代码,待finally块中的代码均执行完毕后再返回到try块中执行最后的return语句;
(2)当try块内的语句出现异常时则会匹配catch语句,此时try块从出现异常的那行代码开始就已经不在继续执行try块内的后续代码了,转而执行匹配到异常类型的catch代码块,此时则会执行catch块内的代码,在catch中的代码执行到return语句的前一句时会转而执行finally中的代码(原因:finally块中的代码总是会在方法返回之前(即return之前)执行),当finally中的代码执行完毕后会重新回到catch代码块中执行最后的return语句代码。此时不会再执行try…catch…finally块后的代码。
遇异常执行顺序:
try块—>catch块—>finally块—>catch块中的return语句
package com.atguigu.day09;
import java.util.InputMismatchException;
import java.util.Scanner;
public class tryBlock {
public static void main(String[] args) {
try {
Scanner input = new Scanner(System.in);
System.out.print("请输入被除数:");
int a = input.nextInt();
System.out.print("请输入除数:");
int b = input.nextInt();
int result = a / b;
System.out.println(result);
return;
//AritheticException为数学一场
}catch (ArithmeticException | InputMismatchException e){
e.printStackTrace();//打印异常的栈信息
System.out.println("catch块执行");
return;
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("这是finally代码块,为必须执行的代码");
}
System.out.println("程序运行结束");
}
}
此为无异常的运行截图
此为有异常的运行截图
- try块、catch块和finally块内均有return语句
注意:
在finally块中使用return语句时,finally块中的return语句会覆盖try块和catch块中的return语句,因此方法的返回值将是finally块中return语句的返回值。如果finally块中没有return语句,那么方法的返回值将是try块或catch块中的return语句的返回值。
如上图所示,此时最后一条语句会报错,因为try…catch…finally语句块中一定会执行return,try…catch…finally语句块后的代码一定执行不到,所以报错。
捕获异常方式三:try-with-resources
------学习IO流后即可理解,详见Day16
try-with-resources
是 Java 7 中引入的一个语言特性,用于简化资源管理和异常处理。它是一种用于自动关闭可关闭资源的机制。
在传统的 Java 编程中,如果你使用了一些需要手动关闭的资源 (比如文件流、数据库连接等),你需要在
finally
块中关闭这些资源,以确保资源得到释放,从而避免资源泄露。这样的代码通常会比较冗长且容易出错。
try-with-resources
让资源管理变得更加简单和清晰。它使用了一种更加紧凑的语法结构,而且在 try
代码块结束后,会自动释放相关的资源。
- 语法格式如下:
try (ResourceType resource = new ResourceType()) {
// 使用资源的代码
} catch (Exception e) {
// 异常处理代码
}
try-with-resources
语句的执行流程如下:
Step1:执行 try 块中的代码。
Step2:当 try 块结束时,无论是正常结束还是因为抛出异常结束,都会调用资源对象的close()
方法来释放资源。- 注意
(1)资源对象必须实现AutoCloseable
或Closeable
接口,否则无法在try-with-resources
中使用。
(2)在try-with-resources
语句中,如果资源初始化失败,将不会尝试去关闭未初始化的资源(即此时不会去隐式的释放资源),因为资源对象可能为空或者不可用。 - 代码示例
(1)利用try...catch...finally
语句块来处理字节输出流的异常
public class TestOne {
public static void main(String[] args) {
//字节输出流`FileOutputStream`类的声明必须在main块中,不能在try块中
FileOutputStream fos = null;
try {
fos = new FileOutputStream("hello.txt");
fos.write(97);//97对应小写字母a
fos.write("\r\n".getBytes());
fos.write(98);//98对应小写字母b
fos.write("\r\n".getBytes());
fos.write(99);//99对应小写字母c
fos.write("\r\n".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
等同于
(2)利用 try-with-resources
语句块来处理字节输出流的异常
public class TestOne {
public static void main(String[] args) {
try (FileOutputStream fos = new FileOutputStream("hello.txt");){
fos.write(97);//97对应小写字母a
fos.write("\r\n".getBytes());
fos.write(98);//98对应小写字母b
fos.write("\r\n".getBytes());
fos.write(99);//99对应小写字母c
fos.write("\r\n".getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}
在利用
try-with-resources
语句执行输出流的代码时,若未出现异常则最后会自动隐式的释放资源。
捕获异常方式四:throw和throws
- throw作用
用于在代码块中主动抛出一个异常(抛出手动创建的异常对象)引发异常的发生,以便在程序执行过程中可以捕获和处理这个异常。可以用在任何地方,包括方法中。 - 正确示例代码
训练集
throw抛出的异常对象一般为受检异常(在编译时即会报错,如图所示),所以需要对该报错进行处理
红线代码解释:创建一个Exception对象,并利用构造器进行传参
处理方式一:利用try…catch进行异常处理
- 代码
- 缺点
此处用try…catch处理异常相当于自己将异常处理了,程序并不会因为出错而停止(比如:你输入的stuAge = 15不在要求范围内,此时程序并不会停止执行而是会自己去处理该错误),没有了提醒的效果,所以用try…catch不合适。
处理方式二:利用throws关键字
- 作用
用于声明一个方法可能抛出哪些异常(也就是说,当方法中可能会发生异常,但是该方法不知道如何处理该异常时,可以使用 throws 将异常抛到调用该方法的地方,由上一级的方法来处理异常。),这种方式比较适用于类库和框架,一旦方法声明抛出某类异常则编译器不会强制必须处理该异常,而是让使用者来处理方法可能会抛出的异常情况。 - 特点
throws关键字可以声明方法抛出的多个异常,多个异常之间用逗号分隔 - 注意事项
测试集中的main方法是虚拟机调用的,当main方法中写入年龄时会报错(如图一),此时会有两种解决方法(一是try…catch,二是throws),注意不能用throws,因为处理异常的手段是将异常拦住,不使其传到虚拟机,所以此处不能用throws,则只能使用try…catch(如图二)
- 两种方式使用场景
(1)当想要抛出异常给使用者看到时则使用throws关键字;当想要处理该异常时则使用try…catch
(2)调用方法时一般使用 try-catch 块来处理异常,而不是使用 throws 声明异常。如果在调用方法时使用 throws 声明异常,那么该方法可能会抛出的异常将会被传递到调用该方法的的上一级方法中,如果上一级方法还是使用 throws 声明异常,那么该异常将被传递到更高层级的方法中,直到最高层的方法,如果最高层的方法还是没有进行异常处理,那么程序将会崩溃。因此,在调用方法时使用 try-catch 块进行异常处理,能够更好地控制程序的运行流程,保证程序的稳定性和可靠性。
自定义异常
自定义受检异常
- 语法格式
需要自定义异常类继承自Exception
对自定义异常添加文字信息的两种方法
- 方式一:通过构造器传参来对自定义异常添加文字信息—详见代码
(1)自定义受检异常代码
(2)Student实现类代码
(3)测试类代码及运行结果
- 方式二:通过重写爷爷类的getMessage()方法来对自定义异常添加文字信息—详见代码
(1)getMessage()方法原始面貌
(2)自定义受检异常代码
(3)Student实现类代码
(4)测试类代码及运行结果
- 注意事项
(1)相对于构造器传递异常信息,getMessage()方法拥有更高的优先级
(2)getMessage()方法传递的异常信息是写死的(即固定异常信息),无法改变
(3)两种传递异常信息根据实际情况选择使用
自定义非受检异常
- 语法格式
需要自定义异常类继承自RuntimeException
问题
- 为什么两种自定义异常都必须写一个有参构造器?
Java中的自定义异常必须创建一个带参的构造器是因为异常通常需要包含异常信息,例如异常发生的原因、位置等。通过创建带参的构造器,可以在抛出异常时向调用者传递这些信息。此外,自定义异常还可以继承自Java中的Exception类或其子类,这些类都有带参构造器,因此子类也需要实现相应的带参构造器。否则,如果没有带参构造器,调用者就无法得到有关异常信息的详细信息,这会使得调试和排除错误变得更加困难
- 什么时候自定义受检异常,什么时候使用自定义非受检异常?
(1)自定义受检异常
当方法无法处理某些异常时,需要将检查异常抛出,通知调用者必须要处理该异常。
当需要强制调用者处理某些异常时,可以使用自定义受检异常,这样调用者就必须要捕获该异常或者继续抛出该异常。
(2)自定义非受检异常
当方法出现了无法恢复的错误,并且该异常不需要强制调用者处理时,可以使用自定义非受检异常。
当方法抛出的异常是由程序错误引起的,例如空指针异常等,此时可以使用自定义非受检异常来代替Java自带的异常,以提供更好的错误信息。