异常
我们在编写java程序的时候,会遇到很多的异常,如:
除以0:
//除以0-异常
System.out.println(10 / 0);
此处是一个算数异常。
数组访问越界:
//数组访问越界-异常
int[] array = {1,2,3};
System.out.println(array[100]);
数组下标越界异常
访问空对象:
public class text1 {
public int num = 10;
public static void main(String[] args) {
//访问空对象
text1 T = null;
System.out.println(T.num);
}
}
我们发现,在java中不同的错误,会有不同的异常。
有些异常的,在报异常之后还会报一个信息,如上述数组越界时报错异常,在报出数组月结之后,还报了一个数字:
这个数字代表的就是,在数组越界这个异常中的,具体的异常,上述的100,表示,这个数组没有下标为 100 的元素,而此时访问了数组 下标为 100 的元素。
下面的 " at text1.main(text1.java:19) " 就是异常信息栈:
这个栈中,可能不会只有一条信息,可能会有多条信息,我们在找错误信息的时候,要从第一行蓝色部分开始依次排查错误,对蓝色部分进行点击,就可以跳到报异常的地方。
那么既然是栈,就是后进先出的,
那么所谓异常就是,在程序在运行时,出现错误时,告知调用者的一种机制。
关键词-运行时:
假如我们在 拼写 System.out.println() 这个函数的时候,拼写错了,那么在编译时期就会报错,这时“编译期”的出错。
而像上述三个例子,他们编译期已经通过,已经得到了class文件了,接下来再由 JVM 执行过程中出错,这就叫运行时出错。
而上述我们也知道了,异常有很多种,如果我们查看文档,可以发现,异常其实是一个类,在这类中实现很多异常的类,而这个类有一个父类- Throwable 类,这个类是java中所有错误和异常的父类:
主要实现了,编译时异常和运行时异常的两个类:
运行时异常,又叫做 非受查异常 ; 编译时异常,有叫做 受查异常。
异常的种类有很多,不同的异常会有不同的处理方法,那么我们不可能都记住,所以我们就要进行防御性编程。
防御性编程
代码中时常出现错误,由此我们就要然程序出现异常的时候,在报运行时错误之前,就告知程序员,那么此处主要有两种方式:
LBYL: Look Before You Leap. 在操作之前就做充分的检查.
EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作,遇到问题再处理。
LBYL风格的代码(不使用异常):
boolean ret = false;
ret = 登陆游戏(); // 登录成功放回 true
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
··················
捕获异常
EAFP风格的代码(使用异常):
使用 try {} catch {} ,来处理异常。
java中处理异常的语法:
try
{
//可能出现异常代码
}catch(参数:异常的类型1 e){
捕获try中可能出现的异常
}catch(参数:异常的类型2 e){
捕获try中可能出现的异常
}catch(参数:异常的类型3 e){
捕获try中可能出现的异常
}
···········
可以用 catch 来捕获 try 中的异常,而捕获上面异常,我们发现catch可以传入参数,这个参数是异常的类型,当然我们也可以处理多个异常,我们在try下调用多个catch来处理多个异常。
如这个例子,在报出异常之后,那么后序的 “hello” 字符串是不会打印的:
那么如果我们想在报出遗产之后,依然执行后面的代码,那么我们就要对这个异常一下处理:
text1 T = null;
try{
System.out.println(T.num);
}catch (NullPointerException e)
{
System.out.println("捕获到了数组越界异常");
}
System.out.println("hello");
上述的参数就是,以异常作为类名,然后创建了 e 这个变量。
这个代码的打印结果是:
我们发现,我们执行了我们写的异常处理代码,后面的 “hello” 也被打印了。
当我们对异常进行处理之后,那么后序代码将会继续执行。
我们上述在catch中传入了一个参数,那么这个参数该如何使用呢?
这个参数是一个对象,那么我们就可以使用这个类中的方法:
text1 T = null;
try{
System.out.println(T.num);
}catch (NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
System.out.println("hello");
调用栈:
方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈。
我们上述使用了 e.printStackTrace();这个方法,我们就可以在屏幕上打印之前报的异常了:
我们来把程序处理异常和不处理异常来进行对比:
但是,处理异常需要对应异常的类型,也就是说,此时是数组越界抛出的异常,那么在catch中就要对这个异常进行捕获,如果在catch中没有对这个异常代码进行捕获异常的话,或者说捕获的异常不是产生的异常的话,那么也会把这个异常交给 JVM 进行处理,此时程序也不会往下执行:
text1 T = null;
try{
System.out.println(T.num);
}catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
System.out.println("hello");
如这个例子,产生的异常是访问空指针,但是没有写这个异常,写的是数组访问越界的异常,那么此时这个异常没有进行处理,也会把这个异常交给 JVM 进行处理,那么此时的 ”hello“ 字符串不会进行打印:
用try catch 捕获异常的时候,需要注意的问题:
- catch块当中,一定要捕获相应的异常,如果程序抛出的异常在catch块当中,不能被捕获,那么就会交给JVM来处理。
- 可以用catch来捕获多个异常
- 可以用Exception 父类来一次捕获多个异常
用 Exception 父类捕获异常:
text1 T = null;
try{
System.out.println(T.num);
}catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}catch(NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到空指针异常");
}catch(Exception e)
{
System.out.println("捕获到Exception异常");
}
System.out.println("hello");
因为catch是从上往下捕获的,所以此时还是报的 空指针异常。
那么现在我们把 Exception 放在第一个catch位置:
我们发现下面的 catch 异常都报错了,一下的异常都不能捕获到了。
这是因为只要是异常,都会被 Exception 给捕获到,那么我们在下面定义的任何异常都没有用了。
我们查看官方文档可以发现,我们之前所调用的异常都是这个 Exception 的子类:
那么此时这些异常就都可以用父类 Exception来实现。
那么如果我们使用 Exception 来捕获异常的话,那么我们后面就不能在使用其他的异常了。
try{
int[] array = {1,2,3};
System.out.println(array[100]);
}catch(Exception e)
{
e.printStackTrace();
System.out.println("捕获到Exception异常");
}
打印:
text1 T = null;
try{
System.out.println(T.num);
}catch(Exception e)
{
e.printStackTrace();
System.out.println("捕获到Exception异常");
}
打印:
由于 Exception 类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常。而且,catch 进行类型匹配的时候, 不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象。如刚才的代码, NullPointerException 和 ArrayIndexOutOfBoundsException 都是 Exception 的子类, 因此都能被捕获到。
但是我们不建议 直接使用 Exception来进行捕获异常,因为不同的异常我们有不同的解决方式,那么我们使用 Exception 来捕获不知道具体是哪一个异常。
但是我们使用上述的多个 catch 来捕获的话,代码会显得很长,如果有多个异常用相同的处理方式的话,那么我们可以这样写:
catch (ArrayIndexOutOfBoundsException | NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
System.out.println("hello");
这样的话,我们就可以使用一个catch 来判断多个异常,比如上述我们可以把 数组越界和 访问空指针 这两个异常进行一个 catch 处理。
我们可以对两个异常进行报错,使用 printStackTrace () 这个方法,同样可以在屏幕上打印 这两种异常,但是需要注意的是,如果两种异常都存在的话,那么他会首先报 第一个 异常,第二个异常不会报:
text1 T = null;
try{
System.out.println(T.num);
int[] array = {1,2,3};
System.out.println(array[100]);
}
catch (ArrayIndexOutOfBoundsException | NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
System.out.println("hello");
输出:
我们发现,他只报了第一个空指针的异常,因为空指针在 数组越界代码的上面,而往下的数组越界的异常并没有报出来。
如果我们使用多个catch也是一样的:
text1 T = null;
try{
System.out.println(T.num);
int[] array = {1,2,3};
System.out.println(array[100]);
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("捕获到了数组越界异常");
}
catch(NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到空指针异常");
}
System.out.println("hello");
输出:
我们之前说过,catch是从上往下执行的,但是此时的 NullPointerException 空指针异常是在 ArrayIndexOutOfBoundsException 数组访问越界的catch 的下一个。也就说,如果两个异常都发生的话,那么先处理的是 try {} 中的第一个异常,而后的异常不会处理。
其实上述的不只是异常,是后序的代码都不会被执行:
text1 T = null;
try{
System.out.println(T.num);
System.out.println("后序代码");
System.out.println("执行后序代码");
}
catch(NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到空指针异常");
}
输出:
我们发现,在上述空指针异常代码之后的代码都没有被执行,屏幕上没有打印 “后序代码” 和 “执行后序代码” 这两个字符串都没有打印。
所以我们在 try {} 代码块中,尽量不要写多的代码,进行少写多判断。
异常的处理方式有很多:
- 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
- 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
- 对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试
finally语法
其实上述不是 try {} 的使用异常的基本语法,以下才是 基本语法:
try{
//有可能出现异常的语句;
}
catch (异常类型 异常对象)
{
//发生对应异常的处理
}
... // 可以有多个catch
finally {
//异常的出口
}
所谓 finally 的意思就是,无论上述的代码有没有抛出异常,在finally 块当中的代码是一定会执行的。而且执行的顺序是,当try {} 当中的代码执行完,如有异常抛出,处理完之后再执行,也就说会在 整个异常处理的代码块中 最后执行。
text1 T = null;
try{
System.out.println(T.num);
System.out.println("后序代码");// 不会执行
}
catch(NullPointerException e)
{
e.printStackTrace();
System.out.println("捕获到空指针异常");
}
finally
{
System.out.println("finally块的代码一定会执行");
}
System.out.println("hello");
输出:
finally的作用主要就是,做一些善后的工作,比如关闭资源等等,比如开了文件对象,那么在使用完文件对象之后要把这个文件给关闭。
例子:
public static int func(){
int[] array = {1,2,3};
try
{
System.out.println(array[100]);
return 1;
}
catch(ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("数组访问越界异常");
}
finally
{
return -1;
}
}
public static void main(String[] args) {
System.out.println(func());//-1
}
我们这例子想实现的是,如果这个代码块抛出异常,那么就返回 -1 ,如果正常执行就返回 1。那么肯定,上述例子的数组是访问越界的,那么结果就返回的是 -1 。
但是,如果我们上述在 try {} 块中的代码没有抛出异常,那么我们的预期是返回1,但是结果返回的是 - 1 :
System.out.println(array[1]); // 2
return 1;
我们发现 2 是打印的数组的 1 号下标的元素,而 -1 是func()方法的返回值,我们发现不是 1 而且 -1。
这也是我们一直在提及的问题:finally{} 块中的代码是一定要被执行的,也就说即使我们在 try{} 块中有 return 类似的结束代码的执行的语句,但是在 finally {} 块当中的也是要被执行的。除了方法返回值,在循环中使用 break ,continue这些也是一样的:
public static void main(String[] args) {
int count = 0;
while(count <= 10)
{
try
{
System.out.println("循环第" + count + "次");
if(count >= 3)
{
break;
}
}
catch (Exception e)
{
e.printStackTrace();
System.out.println("Exception 异常");
}
finally
{
System.out.println("循环第" + count + "次");
}
count++;
}
}
发现 try中的和 finally中的 输出结果次数是一样的。
如果我们不使用 finally {} 打印 ,直接在 try 之后 进行打印:
public static void main(String[] args) {
int count = 0;
while(count <= 10)
{
try
{
System.out.println("循环第" + count + "次");
if(count >= 3)
{
break;
}
}
catch (Exception e)
{
e.printStackTrace();
System.out.println("Exception 异常");
}
System.out.println("循环第" + count + "次");
count++;
}
}
我们发现,当 count = 3 的时候,在try 外打印的次数少了一次;
如左图,第三次少打印一次。
那么同样的,在catch中的return之前,也要执行 finally {}中的代码:
public static int func(){
int[] array = {1,2,3};
try
{
System.out.println(array[1]);
}
catch(ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("数组访问越界异常");
return 1;
}
finally
{
return -1;
}
}
public static void main(String[] args) {
System.out.println(func());//-1
}
我们发现,此时的打印结果还是 -1。
finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return
因为 finally 当中代码是永远会被执行的,当我们不用实现特殊效果的时候,我们一般不在finally当中写return类似的可以终止 代码的 代码。
利用 try 自动回收资源:
我们之前说过,finally{}块当中一般用来回收资源,但是这是手动回收,当我们需要自动回收资源的时候就可以在 try() 的() 当中来写入创建的对象的代码,此时,当try{} 执行完毕之后会自动的调用对应的 close()方法:
如这个例子,当我们在try()中创建了一个 Scanner 的对象用于输入,当try {} 执行完之后,我们再调用这个对象,那么这个对象将不能再被调用:
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
int num = sc.nextInt();
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
int num2 = sc.nextInt();
}
此时报错:无法解析的外部符号 ‘sc'。
这里说的 try执行完,指的就是 try指向完,后面的 catch和finally都无法使用这个 sc:
其实在Idea中,他会自己帮我们判断,我们所书写的代码,是否有更好的形式去书写,比如下面这个代码:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in)
try {
int num = sc.nextInt();
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}finally{
sc.close();
}
}
此时try上有一个 “加深底色”的样式:
此时我们鼠标放到try上,按下: Alt + Enter , 会出现以下界面:
我们点击这个按键,就会自动的给我们修改为更好的代码形式:
如上图就是自己生成的。
其实 try{}块当中的变量也是不能在 try外部来进行访问的:
这是因为 在 try当中定义的是 局部变量(-这些变量属于方法/块/构造函数并在其中声明/定义。这些变量的范围位于方法(或块或构造函数)之内,执行后将被销毁 -)。
因此,当try 块中代码执行完之后,其中的局部变量的生命周期也到了,就不能再使用了。
如果在发生异常的本方法中没有处理异常,那么这个异常就沿着栈向上传递:
public static void func1(){
int[] array = {1,2,3};
System.out.println(array[100]);
}
public static void main(String[] args) {
func1();
}
比如这个代码,在func1 方法中发生了异常,但是没有对这个异常进行处理,那么就会把这个异常抛给调用这个方法的函数中,也就是抛给main函数,如果这个main函数也没有处理,那么就会交给 JVM 栈进行处理,那么如果交给 JVM 栈进行处理,那么后序的代码就不能再执行了。
如果我们要想在执行,就要在func1函数中,或者是在 main函数中来处理这个异常:
public static void func1(){
int[] array = {1,2,3};
System.out.println(array[100]);
}
public static void main(String[] args) {
try
{
func1();
}
catch (ArrayIndexOutOfBoundsException e)
{
e.printStackTrace();
System.out.println("数组访问越界");
}
}
总结-异常处理的流程
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
抛出异常
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作。
public static void main(String[] args) {
int x = 0;
if(x == 0)
{
throw new ArithmeticException("抛出 除0 异常");
}
int num = 10 / x;
System.out.println(num);
}
输出结果:
当我们在使用 throw 关键字的时候,要想抛出一个异常,那么就要使用new关键字去创建这个异常的对象,因为我们前面也讲过,所谓各种异常就是 很多个类。那么我们要想使用这个类,就要创建一个对象。
其中的参数,就会在异常之后打印在屏幕上:
使用throw 需要注意的是:当我们在方法中使用throw 的时候,调用者大概率是不会知道我定义的方法中有一个 throw 抛出异常的,当调用者使用我定义的方法,满足我定义的throw 条件之后,那么就会抛出异常。
那么调用者在查看源代码的时候,如果这个方法实现的行数不是很多,那么他会很容易就发现我定义的throw 抛出异常,但是如果这个方法实现了很多行,那么调用者就不容易发现我实现的 throw 抛出异常。
如这个例子,实现这不知道第二个参数不能传入 0 :
public static int divit(int x,int y)
{
if(y == 0)
{
throw new ArithmeticException("y == 0 除数不能为0");
}
return x/y;
}
public static void main(String[] args) {
int ret = divit(20,0);
System.out.println(ret);
}
抛出异常:
所以,如果我们在类或者是方法中实现某个 throw 抛出异常之后,要在这个的头部使用 throws 关键字来申明一下:
public static int divit(int x,int y) throws ArithmeticException
{
if(y == 0)
{
throw new ArithmeticException("y == 0 除数不能为0");
}
return x/y;
}
向调用者表明这个方法可能会抛出 ArithmeticException这个异常,那么调用者在使用这个方法的时候就要使用 捕获异常的方式来调用 这个方法:
public static void main(String[] args) {
try{
int ret = divit(20,0);
System.out.println(ret);
}catch (ArithmeticException e)
{
e.printStackTrace();
System.out.println("除数不能为0");
}
}
受查异常:
class Person implements Cloneable{
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
此时就发生了受查异常,这种异常是在编译时期产生的,那么我们在程序运行之前必须处理这个异常。
我们知道,要想一个自定义的类实现 clone()复制,那么就要让这个类实现Cloneable接口,然后再重写的 如上一样重写 clone()方法。
然后,当我们在方法中调用这个 clone() 方法的时候,要进行一些处理,之前是说要在 方法后声明这个 异常:
我们在此处,声明这异常代表的是,哪个方法声明了这个异常,哪个方法就去处理这个异常。
但是我们不建议这样做,这样做,如果抛出异常,那么到最后也是交给 JVM 去处理这个异常,后面的代码不会继续执行。
我们要自己捕获异常:
可以手动写,在IDea也可以 Alt + Enter :
点击这个,就会自动的帮我们写:
public static void main(String[] args){
Person person = new Person();
try {
Person person1 = (Person)person.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println("clone 异常");
}
}
Java 异常体系
Java 内置了丰富的异常体系, 用来表示不同情况下的异常.
下图表示 Java 内置的异常类之间的继承关系:
上图,红色的类异常都是 受查异常,蓝色的是 非受查异常。所谓受查异常就是在编译时期 产生异常,是在程序运行时 之前产生的异常,这种异常是必须处理的,不然程序不能运行;而非受查异常就是 在程序运行时产生的异常,这种异常不需要处理,是根据数据或使用不同所产生的异常,程序运行不一定会发生。
- 顶层类 Throwable 派生出两个重要的子类, Error 和 Exception
- 其中 Error 指的是 Java 运行时内部错误和资源耗尽错误. 应用程序不抛出此类异常. 这种内部错误一旦出现,除了告知用户并使程序终止之外, 再无能无力. 这种情况很少出现.
- Exception 是我们程序猿所使用的异常类的父类.
- 其中 Exception 有一个子类称为 RuntimeException , 这里面又派生出很多我们常见的异常类
- NullPointerException , IndexOutOfBoundsException 等
Java语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为 非受查异常, 所有的其他异常称为 受查异常。
对于受查异常,一定要用 try {} catch{} 来捕获异常。一般不建议在本方法中 直接声明这个异常。
自定义异常类
虽然java中已经有很丰富的异常类了,但是有时候我们也需要定义一些 符合我们实际情况的异常类。
因为所谓的异常类,也只是一个类,我们点开 ArithmeticException 异常的源代码,发现这个 异常继承了 RuntimeException 这个父类。而且在这个类中实现了两个构造方法,一个带参数,一个不带参数。
那么我们可以这样来实现一个 自定义异常类:
class MyException extends RuntimeException{
public MyException(){
super();
}
public MyException(String message)
{
super(message);
}
}
这样我们就实现了一个异常类。
然后我们来使用一个这个异常类:
public static void Exception_fun(int num) throws MyException{
if(num == 10)
{
throw new MyException("num = 10");
}
}
public static void main(String[] args) {
try
{
Exception_fun(10);
}
catch(MyException e)
{
e.printStackTrace();
}
}
此处我们向,之前的例子一样,在方法中抛出异常。
输出结果:
我们发现,和之前的是一样的。
我们发现我们这个自定义异常类,继承的是 RuntimeException 类,我们也可以继承 Exception 类,继承这两种类,实现的异常是不一样的:
当我们继承 RuntimeException 类的时候,那我们定义的类是 非受查异常,如上述;
当我们继承 Exception 类的是偶,那么我们定义的类就是 受查异常:
class MyException extends Exception{
public MyException(){
super();
}
public MyException(String message)
{
super(message);
}
}
我们发现此时,满足异常条件,在程序没有运行的时候,IDEA已经报错:
在创建自定义异常类的时候需要注意的是:
- 要继承一个异常父类,这个父类通常是 Exception 或 RuntimeException 。
- 继承自 Exception 的异常默认是受查异常
- 继承自 RuntimeException 的异常默认是非受查异常
例子2:
模拟实现简单用户登录:
假设我们先不用 自定义异常类来实现:
public class Test {
private static String userName = "admin";
private static String password = "123456";
public static void main(String[] args) {
login("admin", "123456");
}
public static void login(String userName, String password) {
if (!Test.userName.equals(userName)) {
// TODO 处理用户名错误
}
if (!Test.password.equals(password)) {
// TODO 处理密码错误
}
System.out.println("登陆成功");
}
}
那么就要用if一个一个来判断。
如果使用 自定义异常类来实现的话:
class UserError extends Exception {
public UserError(String message) {
super(message);
}
}
class PasswordError extends Exception {
public PasswordError(String message) {
super(message);
}
}
public static Text{
private static String userName = "admin";
private static String password = "123456";
public static void main(String[] args) {
try {
login("admin", "123456");
} catch (UserError userError) {
userError.printStackTrace();
} catch (PasswordError passwordError) {
passwordError.printStackTrace();
}
}
public static void login(String userName, String password) throws UserError,
PasswordError {
if (!Test.userName.equals(userName)) {
throw new UserError("用户名错误");
}
if (!Test.password.equals(password)) {
throw new PasswordError("密码错误");
}
System.out.println("登陆成功");
}
}
自定义异常最好是继承于 Exception ,这样的话,就必须去处理。