异常体系结构
在Java语言中, 将程序执行中发生的不正常情况称为异常 。(开发过程中的语法错误和逻辑错误不是异常)
异常在Java中被定义为类,当程序发生异常时,会抛出对应异常类的对象,该对象承载了异常信息(万事万物皆对象),异常相关的类被称作异常类。
异常的顶级父类是java.lang.Throwable ,它有两个子类:
1?? java.lang.Error: Java虚拟机无法解决的严重问题。 如: JVM系统内部错误、 资源耗尽等严重情况。
例如:StackOverflowError 和OutOfMemoryError 。 一般不编写针对性的代码进行处理。
2?? java.lang.Exception: 其它因为编程错误或偶然的外在因素导致的一般性问题, 可以使用针对性的代码进行处理。 例如:
-
空指针访问 -
试图读取不存在的文件 -
网络连接中断 -
数组索引越界 等等…
我们一般所指的异常都是针对于Exception ,其又可以分成两类:
-
编译时异常(checked ),编译时可能发生的异常。 -
运行时异常(unchecked /RuntimeException ),编译时无异常,运行时发生的异常。
|--- Throwable
|--- Error
|--- ...
|--- Exception
|--- IOException (checked)
|--- FileNotFoundException
|--- ...
|--- ClassNotFoundException (checked)
|--- ...
|--- RuntimeException (unchecked)
|--- NullPointerException
|--- IndexOutOfBoundsException
|--- ClassCastException
|--- NumberFormatException
|--- InputMismatchExcepton
|--- ArithmeticException
|--- ...
常见异常举例
运行时异常
NullPointerException
int[] arr = null;
System.out.println(arr[0]);
String str = null;
System.out.println(str.charAt(0));
IndexOutOfBoundsException
int[] arr = new int[10];
System.out.println(arr[10]);
String str = "abc";
System.out.println(str.charAt(-1));
ClassCastException
Father sonA = new SonA();
SonB sonB = (Sonb)sonA;
Object obj = new Date();
String str = (String)obj;
NumberFormatException
public void test2(){
String str = "abc";
Integer.parseInt(str);
}
InputMismatchExcepton
Scanner scan = new Scanner(System.in);
int num = Scanner.nextInt();
ArithmeticException
int a = 10;
int b = 0;
System.out.println(a / b);
异常处理机制
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如: 进行x/y 运算时,要检测分母为0 ,数据为空,输入的不是数据而是字符等。 过多的if-else 分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
抓抛模型
抛过程
程序在正常执行的过程中,一旦出现异常,就会在异常代码处出生成一个对应异常类的对象,并将此对象抛出。
一旦抛出对象以后,其后的代码就不再执行。
异常对象的产生方式:
- 系统自动生成的异常对象。
- 手动生成的异常对象,并使用
thorw 抛出。
抓过程
可以理解为异常处理的方式分为:try-catch-finally 和 throws
try-catch-finally
语法格式
try {
} catch(异常类型1 变量名1) {
} catch(异常类型2 变量名2) {
} catch(异常类型3 变量名3) {
}
...
finally {
}
不使用finally的情况
1.利用try 将可能出现异常的代码包装以来,在执行过程中,一旦出现异常,就会生成一个对于异常类的对象,根据此对象的类型,去catch 中进行匹配。
@Test
public void test1(){
String str = "abc";
try{
int num = Integer.parseInt(str);
System.out.println("test1");
} catch (NumberFormatException e){
System.out.println("出现了数值转换异常");
}
System.out.println("test2");
}
2.一旦try中的异常对象匹配到某一个catch 时,就进入catch 中进行异常的处理,一旦处理完成,就跳出当前的try-cathc 结构(没有写finally 的情况),继续执行其后的代码。
3.当抛出的异常对象没有在catch 匹配到异常类型时,则以上会向上抛出,直到main()方法。
@Test
public void test1(){
String str = "abc";
try{
int num = Integer.parseInt(str);
System.out.println("test1");
} catch (NullPointerException e){
System.out.println("出现空指针异常");
}
System.out.println("test2");
}
4.catch 中的异常类型如果存在子父类关系,则要求子类一定声明在父类的上面。
-
在try 结构中声明的变量,出了try{}的作用域不能使用。 -
不建议使用Exeption 去捕获所有的异常,范围太广,不容易定位错误。
catch 中常使用的两个方法
getMessage() : 返回值是String
printStackTrace() : 返回值类型是void
使用finally的情况
finally 的代码块中的代码一定会被执行,即使**catch 中也出现了异常**,或者**try-cathc 中使用了return 语句**等情况,finally 中的代码也一定会被执行。
例如:
- 在
catch 中编写空指针异常的代码。
-
try-catch 中使用了return 语句 -
没有异常时,try 中的return 语句也会在finally 之后执行。 -
如果finally 中也存在return 语句,则会执行finally 的return 语句。
finally的使用场景
向数据库连接、输入输出流。网络编程Socket 等资源,JVM 是不能自动回收的,这就要求我们自己手动的进行资源的释放。这些资源释放的操作,就需要在声明在finally 中。
@Test
public void test(){
FileInputStream fis = null;
try{
File file = new File("hello.txt");
fis = new FileInputStream(file);
int data = fis.read();
while(data != -1) {
System.out.println((char) data);
data = fis.read();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis != null)
{
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结
throws
throws + 异常类型 写在方法的声明处,指明此方法执行时,可能会抛出的异常类型。
权限修饰符 [关键字] 返回值类型 方法名 throws 异常类型1, 异常类型2... () {
}
一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象。此对象满足throws后的异常类型时,就会被抛出。异常代码后的代码不会被执行。
异常会抛给方法的调用者,如果调用者仍不能解决异常则可以继续向上抛出,直到main方法。
重写方法异常抛出的规则
子类重写方法所抛出的异常类型不大于父类被重写方法抛出的异常类型,子类也可以不抛出异常。
两种异常处理的选择
-
如果父类中被重写方法没有throws 则子类重写的方法也不能使用thorws ,这意味着如果子类重写的方法中有异常,必须使用try-catch-finally 方式进行处理。 -
执行的方法A中,先后又调用了另外的几个方法,这几个方法是递进关系执行的,并且都有可能出现异常。那么建议这几个方法使用throws 的方式进行处理。把异常语句写在方法A中,然后方法A可以考虑使用try-catch-finally 方式进行统一处理。 -
如果方法中有必须关闭的资源,如数据库连接,数据流,socket网络资源等,则必须在该方法内部处理掉异常,如果此时仍然向上抛出,可能会导致方法中的资源未关闭。
thorw 手动抛出异常
public class ThrowTest {
public static void main(String[] args) {
Student stu = new Student();
try {
stu.register(-1);
} catch (Exception e) {
System.out.println(e.getMessage());;
}
}
}
class Student {
private int id;
public void register(int id) throws Exception {
if(id > 0) {
this.id = id;
} else {
throw new Exception("您输入的数据非法");
}
}
}
用户自定义异常类
如何自定义异常类?
- 自定义的类继承于现有的异常结构:通常继承于
RuntimeException (unchecked) 、Exception (checked) - 提供全局常量:
serialVersionUID ,可以理解为对异常类的唯一标识。 - 提供重载的构造器(空参和String msg)
public class MyException extends RuntimeException {
static final long serialVersionUID = -731074574532446639L;
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
模拟登录时抛出异常:
public class LoginCheckException extends Exception{
static final long serialVersionUID = -3386666993124229948L;
public LoginCheckException(){
super();
}
public LoginCheckException(String message){
super(message);
}
}
public class UsernameInvalidException extends LoginCheckException{
static final long serialVersionUID = -3386666998884229948L;
public UsernameInvalidException(){
super();
}
public UsernameInvalidException(String message){
super(message);
}
}
public class PasswordInvalidException extends LoginCheckException{
static final long serialVersionUID = -3386666998884229948L;
public PasswordInvalidException(){
super();
}
public PasswordInvalidException(String message){
super(message);
}
}
public class UserInfo {
private final String username;
private final String password;
public UserInfo(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
public class ExceptionTest {
public static final Scanner SCANNER = new Scanner(System.in);
public static UserInfo register() {
System.out.println("*********注册*********");
System.out.print("请输入用户名:");
String username = SCANNER.next();
System.out.print("请输入密码:");
String password = SCANNER.next();
return new UserInfo(username, password);
}
public static void login(UserInfo userInfo) {
System.out.println("*********登录*********");
try {
System.out.print("请输入用户名:");
String username = SCANNER.next();
if (!userInfo.getUsername().equals(username)) throw new UsernameInvalidException("用户名不正确");
System.out.print("请输入密码:");
String password = SCANNER.next();
if (!userInfo.getPassword().equals(password)) throw new LoginCheckException("密码不正确");
System.out.println("登录成功");
} catch (LoginCheckException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) {
login(register());
}
}
|