一、异常概述
- 异常:程序中发生的不正常情况('开发过程中的语法错误与逻辑错误不是异常)
- 异常可分为两类
Error: Java虚拟机无法解决的严重问题,不编写代码进行处理
Eg. JVM内部错误、资源损耗严重、内存溢出…(StackOverFlowError、OOM…)
Exception: 其他因为编程错误或偶然的外在因素导致的一般问题,可以使用针对性代码进行处理
- 编译时异常 (受检异常):执行javac.exe命名时,可能出现的异常
IOExecption (FileNotFoundException)
ClassNotFoundException
- 运行时异常 (非受检异常) RuntimeException:执行java.exe命名时,可能出现的异常
NullPointerException 空指针
ArrayIndexOutOfBoundsException 角标越界
ClassCastException 类型转换异常
NumberFormatException 数值转换异常
InputMismatchException 输入不匹配
ArithmaticException…
public class ErrorTest1 {
public static void main(String[] args) {
// java.lang.StackOverflowError
main(args);
// java.lang.OutOfMemoryError: Java heap space
Integer[] arr = new Integer[1024 * 1024 * 1024];
}
}
public class ExeptionTest1 {
//编译时异常 --> 执行后不会生成字节码
public void test() {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file); //Unhandled exception type FileNotFoundException
int data = fis.read();
while(data != -1) {
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
//运行时异常 --> 编译会正常编译、生成字节码文件
public void classCastExceptionTest() {
String str1 = new Date(); // 编译时报错
Object obj = new Date(); // 运行时异常
String str2 = (String) obj;
}
public void numberFormatExceptionTest() {
String str1 = "123";
Integer.parseInt(str1); // OK
String str2 = "abc";
Integer.parseInt(str2); // NumberFormatException
}
public void inputMismatchExceptionTest() {
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt(); // 输入abc InputMismatchException
}
public void arithmeticExceptionTest() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
}
二、异常处理机制
- 过程一:抛出异常
程序在正常执行过程中,一旦出现异常,就会在异常代码处生成对应异常类的对象,并将此对象抛出,一旦抛出异常,其后的代码不再执行
关于异常的产生: 1. 系统自动生成的异常对象
2. 手动生成异常对象,并抛出(throw) - 过程二:抓异常(处理机制: try-catch-finally 、throws)
- try-catch-finally 真正的将异常处理了
throws的方式只是将异常抛给了方法的调入者,并没有真正将异常处理掉
// throw 表示抛出一个异常类的对象,生成异常对象的过程,声明在方法体内
// throws 属于异常处理的一种方式,声明在方法的声明处
(一)try-catch-finally
try-catch
- 使用try将可能出现异常的代码包起来,在执行过程中出现异常,会生成异常类的对象,根据异常对象的类型,去catch中进行匹配
- 一旦try中对象匹配到某一个catch时,就进入catch进行处理,一旦处理完成便退出当前try-catch结构,继续执行其后的代码。
- 如果catch中的异常类型没有父子关系,则位置先后无所谓
如果carch中的异常类型存在父子关系,则子类catch必须写在父类之前
try {
// 可能出现异常的代码
} catch(异常类型1 变量1) {
//处理异常1的代码
} catch (异常类型1 变量2) {
//处理异常2的代码
}
...
finally {
//一定会去执行的代码 (选择性写finally)
}
public void numberFormatExceptionTest() {
String str1 = "123";
Integer.parseInt(str1); // OK
String str2 = "abc";
try {
Integer.parseInt(str2); // NumberFormatException
System.out.println("inTry"); // 不会执行
} catch (NumberFormatException e) {
System.out.println("NumberFormatException");
}
System.out.println("out of try-catch"); //会执行
}
- 常用异常对象的处理方式:
1)String getMessage()
2)printStackTrace();
try {
...
} catch (Exception e) {
System.out.println(e.getMessage());
//or
e.printStackTrace();
}
- 在try{…}中定义的变量,在try{…}结构外不能被调用
public void test() {
try {
int num = 20;
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(num);// num cannot be resolved to a variable
}
public void test() {
int num = 10;
try {
num = 20;
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println(num); // 20
}
finally
- finally结构时可选的
- finally中声明的是一定会被执行的代码,即使catch中出现了异常、try中出现return语句、catch中有return语句等情况下一定会被执行。
public void test1() {
try {
int a = 10;
int b = 0;
System.out.println(a / b);
} catch (ArithmeticException e) {
e.printStackTrace();
Object obj = new Date();
String str2 = (String) obj;
} finally {
System.out.println("抓住了!"); // 执行
}
System.out.println("抓住了!!"); // 未执行
}
public int test2() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 2;
} finally {
System.out.println("一定会被执行");
}
}
- 像数据库连接、输入输出流、网络编程Socket资源,JVM是不能自动回收的,需要我们自己手动的进行资源释放,资源释放操作需要声明在finally中。
- try-catch结构可以嵌套
public void test() {
FileInputStream fis = null;
try {
File file = new File("hello.txt");
fis = new FileInputStream(file); // Unhandled exception type FileNotFoundException
int data = fis.read();
while (data != -1) {
System.out.print((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();
}
}
}
- 使用try-catch处理编译时异常,使得程序在编译时不报错,在运行时仍可能报错
-> 将编译时异常延迟到运行时出现 - 运行时异常一般不用try-catch处理,编译时异常需考虑try-catch异常处理
(二)throws + 异常类型
- “thorws +异常类型” 写在方法声明处,指明此方法执行时,可能会抛出的异常类型
一旦方法执行时出现异常,会在出现异常的代码处生成一个异常类的对象,此对象满足throws后的异常类型时,就会被抛出
public class ExceptionTest2 {
public static void main(String[] args) {
ExceptionTest2 test = new ExceptionTest2();
try {
test.method2();
} catch (FileNotFoundException e) {
System.out.println("处理FileNotFoundException的方法");
} catch (IOException e) {
System.out.println("处理IOException的方法");
}
test.method3();
}
public void method3() {
try {
method2();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void method2() throws FileNotFoundException, IOException {
method1();
}
public void method1() throws FileNotFoundException, IOException {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file); // Unhandled exception type FileNotFoundException
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
fis.close();
}
}
(三)throw 手动生成一个异常对象
public class ThrowTest {
public static void main(String[] args) {
Student s = new Student();
s.register(-1001);
System.out.println(s);
}
}
class Student {
private int id;
public void register(int id) {
if (id > 0) {
this.id = id;
} else {
throw new RuntimeException("输入的学号非法");
}
}
}
public class ThrowTest {
public static void main(String[] args) {
try {
Student s = new Student();
s.register(-1001);
System.out.println(s);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
class Student {
private int id;
public void register(int id) throws Exception { // throws体现了异常处理
if (id > 0) {
this.id = id;
} else {
throw new Exception("输入的学号非法"); // throw体现了生成一个异常对象
}
}
}
三、使用场景
- 在方法的重写中,子类重写方法抛出异常类型不能大于父类的方法抛出异常的类型
开发中如果父类中方法没有throws方法抛出异常,则子类方法中不能使用throws
如果子类重写方法有异常,只能使用try-catch-finally方式处理异常 - 执行的方法a中,先后又调用了几个有递进关系的代码,我们建议这几个有递进关系的代码中使用throws,而在方法a中使用try-catch结构进行处理
public class OverrideTest {
public static void main(String[] args) {
OverrideTest test = new OverrideTest();
test.display(new SubClass());
}
public void display(SuperClass s) {
try {
s.method();
} catch (IOException e) { //多态时若子类对象抛出异常大于父类异常,则无法被catch,造成错误
e.printStackTrace();
}
}
}
class SuperClass {
public void method() throws IOException {
}
}
class SubClass extends SuperClass {
public void method() throws FileNotFoundException {
}
}
四、用户自定义异常类
- 继承现有的异常结构:RuntimeException、Exception…
- 提供全局常量:serialVersionUID
- 提供重载构造器
public class MyException extends RuntimeException {
static final long serialVersionUID = -7034897190745766939L;
public MyException() {
}
public MyException(String msg) {
super(msg);
}
}
class Student {
private int id;
public void register(int id) { //若MyException继承自Exception类 需在此处throws Exception
if (id > 0) {
this.id = id;
} else {
throw new MyException("输入的学号非法");
}
}
}
五、练习
public class RuntimeExceptionDemo {
public static void main(String[] args) {
try {
methodA();
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
methodB();
}
static void methodA() {
try {
System.out.println("进入方法A");
throw new RuntimeException("制造异常");
} finally {
System.out.println("使用方法A的final");
}
}
static void methodB() {
try {
System.out.println("进入方法B");
return;
} finally {
System.out.println("使用方法B的final");
}
}
}
进入方法A
使用方法A的final
制造异常
进入方法B
使用方法B的final
- 接受命令行参数:右键 -> RunConfigurations -> Arguments -> 在Program arguments中填入参数
public class EcmDef {
public static void main(String[] args) {
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int result = ecm(a, b);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("数据类型错误");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("分母为0异常");
} catch (EcDef e) {
System.out.println(e.getMessage());
}
}
public static int ecm(int a, int b) throws EcDef {
if (a < 0 || b < 0) {
throw new EcDef("分子or分母为负数");
}
return a / b;
}
}
class EcDef extends Exception {
static final long serialVersionUID = -7034897190745766939L;
public EcDef() {
}
public EcDef(String msg) {
super(msg);
}
}