1.异常概述
什么是异常
在程序执行过程中发生了不正常的情况。这种不正常的情况就叫异常。
java把异常信息打印到控制台上,供程序员参考,程序员在看到异常之后,可以对程序进行修改,让程序变得更健壮。也使得java更完善。
异常在java中是以类的形式存在的,每一个异常类都可以实例化对象。
在代码中,在遇到异常时,JVM会创建一个异常对象,异常对象的参数就是对不正常的程序的说明,JVM将异常抛出,并对异常的说明打印到控制台上。
看demo
package exception;
public class Exception {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a/b; // 程序运行到这个时候就会存在异常,此时程序中断
// 输出:Exception in thread "main" java.lang.ArithmeticException: / by zero
// at exception.Exception.main(Exception.java:7)
// System.out.println(100/0);
// 在出现相同类型的异常时,JVM还会创建一个对象,但和刚才的对象是两个对象。
}
}
2.异常类的继承结构
首先Object下有Throwable类,意为可抛出的异常。
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支,
1.Exception的直接子类,编译时异常,意为在编译阶段必须对异常进行处理,如果不处理,编译器报错。
2.RuntimeException:运行时异常,在编写程序时可以处理异常也可以不处理。
异常无论是编译时异常还是运行时异常,都只在运行时发生,在编译阶段不会发生只会报错,因为程序只有在运行阶段次会new对象。
编译时异常,比如得知下雨出门就是一个编译异常,出门带一把伞就是对异常的预先处理。
运行时异常,比如出门被花盆砸伤,这种异常发生的概况很低,同时我们没有必要对这样的异常做预处理。
3.异常的两种处理方式
1.在方法的后面用throws关键字,抛出异常,抛给上一级。谁调用该方法,将异常抛给谁,由调用者继续处理异常。
2.使用try-catch语句进行异常的捕捉。
java中异常发生之后如果一直上抛,抛给了main方法,main方法抛给了JVM,那么只有一个结果,就是终止程序的执行。
看下面一段demo
package exception;
public class Exception {
// 我自定义一个方法,这个方法可能会抛出一个ClassNotFoundException异常(编译时异常)
public static void doSome() throws ClassNotFoundException{
}
public static void main(String[] args) {
// 我调用doSome方法,编译器就会报错,原因是我没有处理编译时异常
doSome();
}
}
第一种处理异常的方式
public static void main(String[] args) throws ClassNotFoundException //再上抛,不仅可以抛出当前的异常类,也可以抛出他的父异常类他,throws后面可以有多个异常,中间用逗号隔开
// JVM调用main,最后main函数就会让JVM处理
第二种方式:真正的解决异常
try {
doSome();
// sout //此时这里的代码就不会执行了,至今进入到catch语句块
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 使用try catch语句解决异常
// sout // 此时这里的代码继续执行,前提是异常被解决
关于catch:
1.catch中的参数是一个对象的引用,这个对象是JVM中new出来的异常对象
2.//catch中对象的类型也可以是其父类型(多态)
3.catch可以写多个。
4.catch可以写多个的时候,要从上到下,从小到大,从子到父。
java8的新特性:在catch的参数列表里,可以采用 |(或),写多个异常参数。
4.异常对象的两个方法
1.getMessage()方法,打印出错信息,返回值是String类型的,若想看信息,还需要自己写输出语句。
// 自定义一个异常对象,参数就是异常信息(由于我没有抛出这个异常,所以在运行的时候不会终端)
NullPointerException e = new NullPointerException("空指针异常");
String s = e.getMessage();
System.out.println(s);
// 输出“空指针异常”
2.printStackTrace()方法,打印异常追踪的堆栈信息。(此时会创建一个新的线程)
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Exception {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
e.printStackTrace(); // 调用该方法,在控制台会打印异常追踪的堆栈信息(来源)
}
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("sadf");
}
}
5.finally语句
finally是try catch的子句,在任何情况下都会执行,而且不能单独存在。
一般用于释放资源,或者做出最终的说明。
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Exception {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("sadfsd");
String s = null;
s.toString(); // 在这里程序抛出异常,try语句块下面的语句都不执行,也就不能关闭流
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
} finally {
// 所以我们在这里去关闭流,十分安全
if(fis != null){
try {
fis.close(); // 同时这个地方也要做异常处理
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
try可以直接和finally使用
try {
return;
}finally {
// 就算return了,finally里面的语句还是会执行
System.out.println("finally");
}
// System.out.println("hello word"); 但是这里的代码不会执行了
但是有一种方法可以终止finally
try{
System.exit(0); // 这种方法可以直接退出JVM
}finally{
System.out.println("hello world");
}
6.java中如何自定义异常
自定义异常实际上是自定义类,因为java中异常都已类的形式存在。
1.首先继承Exception类(或其他异常类)。
2.写改类的两个构造方法,有参(类型是字符串类型的)和无参。
3.有参构造方法中调用父级的构造函数,参数为构造方法的参数。
package exception;
// 首先要自己写一个异常类
class MyException extends Exception{ // 1.继承
public MyException(){} // 2.两个重写
public MyException(String s){
super(s);
}
}
public class Exception01 {
public static void main(String[] args) {
MyException e = new MyException("自定义异常");
System.out.println(e.getMessage());
}
}
7.实际开发中异常的使用
实际开发中,异常的作用就是判断程序出现不正确的方法,然后终止程序并且给开发者提示错误信息。以前我们会先输出程序哪里错了,然后return,但这是一种不完整的处理错误的机制,有了异常,我们就可以让以前的处理错误的机制更高级,完整。
比如下面的demo,我们以前是这样处理错误的:
// 假设我们在这里输入x的值为3
int x = 3;
if (x>2||x<1){
// 我们采用这种方式虽然可读性强,但是不能处理更复杂的错误程序。
System.out.println("输入的值有错");
return;
}
switch (x){
case 1: ; break;
case 2: ; break;
}
我们使用异常之后:
package exception;
class InputException extends Exception{
public InputException(){}
public InputException(String s){
super(s);
}
}
public class Exception01 {
public static void main(String[] args) {
try {
m1();
} catch (InputException e) {
System.out.println(e.getMessage());
}
}
private static void m1() throws InputException {
// 假设我们在这里输入x的值为3
int x = 3;
if (x>2||x<1){
// 此时我们只需要这一句语句
throw new InputException("输入的值有误");
}
switch (x){
case 1: ; break;
case 2: ; break;
}
}
}
8.一个关于继承的小问题
重写之后的方法不能比之前的方法抛出更多(更宽泛)的异常,但可以更少
package exception;
class Exception01 {
public void m1() throws RuntimeException{
}
}
class A extends Exception01{
public void m1(){} // 重写的方法可以不抛出异常
}
class B extends Exception01{
public void m1() throws RuntimeException{} // 最好是抛出和父类一样的异常
}
class C extends Exception01{
public void m1() throws Exception{} // 但不能抛出比父类更宽泛的异常,或者更多,这里会报错
}
9.手动抛出异常
throw new Exception()