LBYL
: Look Before You Leap. 在操作之前就做充分的检查. 即:事前防御型boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
}
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return;
}
缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。
EAFP
: It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理. 即:事后认错型try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
......
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码,异常处理的核心思想就是 EAFP。
在Java中,异常处理主要的5个关键字:throw、try、catch、final、throws。
在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:throw new XXXException("异常产生的原因");
比如我们自己定义一个空指针异常
public class Test {
public static void exception(int[] arr){
if(arr == null){
throw new NullPointerException("指向数组的引用为空");
}
}
public static void main(String[] args) {
exception(null);
}
}
刚刚的异常是系统自带的空指针异常
我们也可以自己自定义一个异常然后抛出
public class Test {
public static int getElement(int[] array, int index) {
if (null == array) {
throw new NullPointerException("传递的数组为null");
}
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
getElement(array, 3);
}
}
异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。
throws
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
比如
public class Test {
public static int getElement(int[] array, int index)throws NullPointerException,ArrayIndexOutOfBoundsException {
if (null == array) {
throw new NullPointerException("传递的数组为null");
}
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args)throws NullPointerException,ArrayIndexOutOfBoundsException {
int[] array = {1, 2, 3};
getElement(array, 3);
System.out.println("这行代码并没有被执行");
}
}
注意,这个例子中异常我们是处理了的,但是没有解决,程序执行到这里发现异常,抛出异常之后,会在这里直接中断,后续代码是不会执行的,所有本例子里面没有打印“这行代码并没有被执行”这行字。
在本例子中,这个越剧异常getElement
方法没有解决,抛出异常,交给调用者main
方法,main
方法也没有解决,依旧抛出最后交给了JVM当我们没有解决这个异常的时候,这个异常就交给JVM,一旦交给JVM,程序就崩溃了。
throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行
处理,就需要try-catch。
语法格式:
try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类
时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){
// 对异常进行处理
}finally{
// 此处代码一定会被执行到
}]
// 后序代码
// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行
// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行
注意:
1. []中表示可选项,可以添加,也可以不用添加
2. try中的代码可能会抛出异常,也可能不会
举个例子说明
public class Test {
public static int getElement(int[] array, int index) {
return array[index];
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
try {
getElement(array, 3);//try中写可能抛出异常的代码
} catch (NullPointerException e) {//捕捉异常
//catch里面捕获异常之后需要的处理
System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑");
e.printStackTrace();//打印栈上的信息,也就是异常出现的一些信息,就会出现红字。
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑");
e.printStackTrace();
}
System.out.println("异常后面的代码正常执行");//之前的throws抛出异常后,后面的代码是直接中断不执行的,所以并不能称之为处理异常,而这里后面的代码继续执行,说明这个异常被处理了。
}
}
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.
注意:
public class Test {
public static int getElement(int[] array, int index) {
return array[index];
System.out.println("这段代码也不会执行");
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
try {
System.out.println("这行代码会执行");
getElement(array, 3);
System.out.println("这行代码不会执行");//try里面可能发生异常代码之后的代码不执行
} catch (NullPointerException e) {//正在的异常并没有被捕获,此时会直接抛出异常,异常最终交给JVM,程序中断,后续代码也不会执行
System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑");
e.printStackTrace();
} /*catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑");
e.printStackTrace();
}*/
System.out.println("这行代码也不会执行");
}
}
但是注意发生异常的中断,异常的中断并不是以一行代码为单位的,换句话说并不是说一行代码里面有两个异常就会抛出两个异常,而是只会抛出一个异常,因为另外一个还没有执行就被中断了,所以“try中可能会抛出多个不同的异常对象”并不是指会同时抛出,而是在不同条件下(比如if else下)分别抛出。如下面这个例子
public class Test {
public static int getElement(int[] array, int index) {
int[] arr = {1,2,3};
//一行代码里有越界有空指针
arr[4] = array[index];//arr[4]也不会执行,前面就发生异常中断了
System.out.println("这行代码不执行");//不会执行
return arr[4];//不会执行
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
try {
int arr = getElement(null, 3);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑");
e.printStackTrace();
} catch (NullPointerException e) {//率先被捕获
System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑");
e.printStackTrace();
}
System.out.println("这行代码会执行");
}
}
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
如下代码就不会报错(没有下划线 )
当try中存在多个异常时,谁先抛出异常就先捕获并处理哪一个异常,catch不影响异常捕获的顺序,同时,子类异常抛出后,父类异常不会再抛出。
public class Test {
public static int getElement(int[] array, int index) {
int ret = array[index];
System.out.println("这行代码不执行");
return ret;
}
public static void main(String[] args) {
int[] array = {1, 2, 3};
try {
int arr = getElement(null, 3);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("捕获到ArrayIndexOutOfBoundsException 异常,进行处理异常逻辑");
e.printStackTrace();
} catch (NullPointerException e) {//率先被捕获
System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑");
e.printStackTrace();
}catch (Exception e) {//父类异常不会被抛出
System.out.println("捕获全部异常");
e.printStackTrace();
}
System.out.println("这行代码会执行");
}
}
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库
连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能
导致有些语句执行不到,finally就是用来解决这个问题的。
不管try是否会抛出异常,finally中代码一定会被执行。
语法格式:
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
如下面这个例子
import java.util.Scanner;
public class Test {
public static int getElement(int[] array, int index) {
return array[index];
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);//scanner本身就是一个资源,使用完需要调用close方法关闭
int[] array = {1, 2, 3};
try {
int a = scanner.nextInt();
array[2] = a;
int arr = getElement(null, 3);
} catch (NullPointerException e) {
System.out.println("捕获到 NullPointerException 异常,进行处理异常逻辑");
e.printStackTrace();
}
finally{
scanner.close();
System.out.println("finally执行了");
}
System.out.println("这行代码会执行");
}
}
public class Test {
public static int func() {
try{
int a = 10;
return a;
}catch(NullPointerException e){
return 1;
}finally {
return 9;
}
}
public static void main(String[] args) {
System.out.println(func());
}
}
try里面有return,finally里面有return,这样到返回哪个值呢?
finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果
finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return (被编译器当做一个警告)
public static void main(String[] args) {//0
try {//1
func();//2
} catch (ArrayIndexOutOfBoundsException e) {//3
e.printStackTrace();//4
}
System.out.println("after try catch");//5
}
public static void func() {//6
int[] arr = {1, 2, 3};//7
System.out.println(arr[100]);//8
}
}
执行顺序是012678345
此时我们在处理用户名密码错误的时候可能就需要抛出两种异常. 我们可以基于已有的异常类进行扩展(继承), 创建
和我们业务相关的异常类.
具体方式:
如下面这个例子
我们定义一个NameExcepetion
类继承Exception
并且调用参数的构造方法用于提示造成异常的原因
public class NameExcepetion extends Exception{
//自定义的错误必须继承Exceptiion或者RuntimeException,
// 前者是所以异常的父类,可以用以表示编译时异常,后者可以专门用以表示允许时异常
public NameExcepetion(String message) {
super(message);
}
}
再定义一个PasswordException
类继承Exception
,并且调用参数的构造方法用于提示造成异常的原因
public class PasswordException extends Exception{
public PasswordException(String message) {
super(message);
}
}
然后定义一个登录类用于登录实现
import java.util.Scanner;
public class Login {
private static String name = "baixian";
private static String passworld = "123456";
public static void login(String inputName, String inputPassword) throws NameExcepetion, PasswordException {
//用throws将异常抛出,交给调用方法main处理
if (!name.equals(inputName)) {
throw new NameExcepetion("用户名输入错误");//调用带参数的构造方法实例化一个NameExcepetion,也就是提出一个ameExcepetion异常
}
if (!passworld.equals(inputName)) {
throw new PasswordException("密码输入错误");
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try {
System.out.println("请输入用户名:");
String inputName = scanner.nextLine();
System.out.println("请输入密码:");
String inputPassword = scanner.next();
login(inputName, inputPassword);
} catch (NameExcepetion nameExcepetion) {//捕获NameExcepetion异常
nameExcepetion.printStackTrace();//打印异常调用栈
System.out.println("处理用户名异常");//main函数处理异常
} catch (PasswordException passwordException) {
passwordException.printStackTrace();
System.out.println("处理密码异常");
}
finally {
scanner.close();
}
}
}