以下程序执行过程中发生了不正常的情况,这种情况叫做:异常
java把异常信息打印到控制台,供程序员参考,程序员看到异常信息后可以对程序进行修改,让程序更加健壮
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
int c = a / b;
System.out.println(c);
}
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.bjpowernode.exception.ExceptionTest01.main(ExceptionTest01.java:7)
执行过程中由JVM打印异常信息,可以进行修改:
public class ExceptionTest01 {
public static void main(String[] args) {
int a = 10;
int b = 0;
if (b == 0){
System.out.println(" can not by zero ");
return;
}
int c = a / b;
System.out.println(c);
}
}
异常的存在形式
异常在Java中以类的形式存在,每一个异常类都可以创建Java对象
在上例中执行到int c = a / b;
时 JVM会创建一个ArithmeticException异常对象:new ArithmeticException("by zero");
并且JVM将对象抛出 打印输出信息到控制台了
public class ExceptionTest02 {
public static void main(String[] args) {
//通过异常类实例化异常对象
NumberFormatException numberFormatException = new NumberFormatException("数字格式化异常");
System.out.println(numberFormatException);
//自动调用toString方法
/*java.lang.NumberFormatException: 数字格式化异常*/
}
}
异常对应到现实生活中:
火灾:(异常类)
- 2008年8月8日,小明家着火了(异常对象)
- 2009年8月8日,小明家着火了(异常对象)
- 2008年9月8日,小明家着火了(异常对象)
类是模板 对象是实际存在的个体
异常的继承结构
-
Object
-
Throwable:不管是错误还是异常都是可抛出的
-
Error:所有错误一旦发生,Java程序立即终止,错误不可被处理
-
VirtualMachineError
-
IOError
-
-
Exception:Exception是可以处理的
-
ExceptionSubClass:Exception的直接子类,叫做编译时异常 1
-
RunTimeException:所有的RunTimeException及子类属于运行时异常 2
- NullPointerException
-
-
-
[ 2] 运行时异常在编写程序阶段可以选择处理或不处理 也被称为 未受检异常
[ 1] 不是在编译阶段发生的,在编写程序前必须对这种异常进行处理,如果不处理编译器报错 也被称为 受检异常
在编译阶段Java不会运行代码,只会检查语法是否错误,或者做一些性能的优化,例如字符串的拼接
但是如果遇到了索引越界,在编译阶段是不知道的,只能在运行时判断是否越界
编译时异常更多的是提醒程序员检查本地信息
运行时异常是代码出错导致的
作用:
-
查询bug的参考信息
-
作为方法内部一种特殊返回值,以便通知调用者底层的执行情况
异常的处理方式
只要异常没有捕捉 采取上报的方式抛给JVM,此方法的后续代码都不会执行
try语句块中某一块代码出现异常 try内后续代码不能执行
- JVM默认的处理方案
将异常名称、异常原因、异常出现的位置等信息输出在控制台上,发生异常位置之下的代码不会执行。
throws抛出
在方法上将异常向上一级抛出,抛给上一级调用者,调用者需要对异常继续进行处理,如果main继续上抛给JVM,JVM就会终止执行程序:
public static void main(String[] args) {
System.out.println(100 / 0);
System.out.println("Hello World"); /*在上一行代码程序就停止执行了*/
}
public class ExceptionTest04 {
public static void main(String[] args) {
doSome();
}
/**
* doSome方法在方法声明的时候使用了throws,表示代码执行过程中可能会出现ClassNotFoundException
* 这个异常的直接父类是Exception,所以这是个编译时异常
* @throws ClassNotFoundException
*/
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome");
}
}
因为方法声明位置上有 throws ClassNotFoundException;ClassNotFoundException属于Exception的直接子类 也就是编译时异常,在调用时就必须对这种异常进行预先的处理 不处理编译器就会报错
继续上抛:
public class ExceptionTest05 {
public static void main(String[] args) throws ClassNotFoundException {
doSome();
}
public static void doSome() throws ClassNotFoundException{
System.out.println("doSome");
}
}
package exception_test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @author LiuYiBo * @ClassName ExceptionTest06 * @description: TODO
* @date 2023/11/08
* @version: 1.0 */public class ExceptionTest06 {
public static void m3() throws FileNotFoundException {
System.out.println("m3 begin");
//创建一个输入流对象
//构造方法会抛出编译时异常 FileNotFoundException->IOException->Exception 必须进行预先处理
new FileInputStream("\"D:\\新建文件夹\\MarkDown\\NoSQL\\Redis\\RedisOnLinux\\redis安装.doc\"");
System.out.println("m3 over");//出现异常此处就无法执行了
}
public static void m2() throws FileNotFoundException {
System.out.println("m2 begin");
m3();
System.out.println("m2 over");
}
public static void m1() throws FileNotFoundException {
System.out.println("m1 begin");
m2();
System.out.println("m1 over");
}
public static void main(String[] args) {
System.out.println("main begin");
try {
m1();
System.out.println("main over");//此处无法执行
} catch (FileNotFoundException e) {
System.out.println("catch over");
}
}
}
此处抛出异常时因为构造方法中检测到file不合法:
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
@SuppressWarnings("removal")
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
FileCleanable.register(fd); // open set the fd, register the cleanup
}
在第12行 通过 throw new FileNotFoundException("Invalid file path")
创建异常对象抛出
此处通过new创建异常对象就是为了上抛(如果是try-catche就没有创建对象的意义了),所以在方法名上才会throws
try-catch异常捕捉
希望调用者进行处理的 选择该种方式
不建议在main方法中继续上抛异常,上抛到JVM就会导致程序终止
public static void main(String[] args){
try{
//try尝试
System.out.println(100 / 0);
/*出现异常try内后面的代码都无法执行,直接跳转到cache语句块*/
}
catch(ArithmeticException e){
//cache是捕捉异常之后走的分支,在此处处理异常
//这个分支中可以使用e引用 保存的是异常对象的内存地址
System.out.println(e);//重写了toString方法
}
System.out.println("Exception has been cached")
/*try-cache把异常捕捉之后 此处的代码会继续执行 这也就是异常让程序更健壮的原因*/
}
catch到的异常都是在调用的方法中创建出的,JVM将该异常赋值给catch后的变量中
深入try-catch
- catch只能捕捉声明类型的异常
public static void main(String[] args) {
try {
FileInputStream inputStream = new FileInputStream("D:\\新建文件夹\\高级网页设计-大作业.doc");
} catch (NullPointerException e) {
/*如果捕捉的异常不是抛出的 编译器就会报错
必须处理抛出的异常
*/
}
}
- catche可以捕捉声明类型的异常,或者该异常的子类型
try {
FileInputStream inputStream = new FileInputStream("D:\\新建文件夹\\高级网页设计-大作业.doc");
System.out.println("以上出现异常 这里无法执行");
} catch (IOException e) {
System.out.println("文件不存在!");
}
System.out.println("hello world");
}
IOException e = new FileNotFoundException()
- 虽然catch可以捕捉声明异常的子类型, 但是建议写多个catch,精确处理每个异常
try {
FileInputStream inputStream = new FileInputStream("D:\\新建文件夹\\高级网页设计-大作业.doc");
inputStream.read(); //抛出IOException
System.out.println("以上出现异常 这里无法执行");
} catch (FileNotFoundException e) {
System.out.println("文件不存在!");
} catch (IOException e){
System.out.println("IO出错");
}
也可以把抛出的 多个异常 都用cache他们的父类处理:
try {
FileInputStream inputStream = new FileInputStream("D:\\新建文件夹\\高级网页设计-大作业.doc");
inputStream.read();
System.out.println("以上出现异常 这里无法执行");
} catch (IOException e){
}
但是这种处理方式不够精确。
- 不能将父类型异常写在子类型异常上方
try {
FileInputStream inputStream = new FileInputStream("D:\\新建文件夹\\高级网页设计-大作业.doc");
inputStream.read();
System.out.println("以上出现异常 这里无法执行");
}catch (IOException e){
}catch (FileNotFoundException e){
/*此处会报错*/
}
子类异常已经 has already been caught
,出现子类异常后自动从上到下匹配,直接被父类cache捕捉到;也就是子类cache是捕捉不到的
- JDK8的多个异常捕获方式
try{
FileInputStream fis = new FileInputStream("null");
System.out.println(100 / 0); //运行时异常 编写程序时可以不处理
}catch (ArithmeticException | FileNotFoundException e){
}
异常的常用方法
getMessage()
获取异常详细消息字符串
public static void main(String[] args) {
NullPointerException e = new NullPointerException("NullPointerException");
String message = e.getMessage();
System.out.println(message);
}
创建异常时构造方法的参数就是 getMessage()
方法传递的信息
printStackTrace()
public static void main(String[] args) {
NullPointerException e = new NullPointerException("HereIsNullPointerException");
e.printStackTrace();
System.out.println("Hello World");
}
/*
java.lang.NullPointerException: HereIsNullPointerException
at com.bjpowernode.exception.ExceptionMethod.main(ExceptionMethod.java:9)
Hello World
*/
HelloWorld 可能会出现在异常信息的上方,这是因为后台某个线程在做这件事(异步线程)
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("此处的代码不会因为发生异常而宕机");
}
printStackTrace()
以红色字体输出,因为:
底层调用了err的输出方式,使用err方式输出的就是红色字体
toString()
返回此可抛出的简短描述
try-catch-finally
finally子句中的代码是最后执行的,并且是一定会执行的,即使try中的代码出现了异常
try {
//创建输入流对象
FileInputStream fis = new FileInputStream("");
String s = null;
s.toString(); //出现空指针异常
//流使用完需要关闭
fis.close();
}catch (IOException e){
e.printStackTrace();
}
如果在第九行进行关闭 ,在第六行一定会出现空指针异常;进而导致fis关闭失败
如果在catch外关闭:
try {
//创建输入流对象
FileInputStream fis = new FileInputStream("");
String s = null;
s.toString(); //出现空指针异常
}catch (IOException e){
e.printStackTrace();
}
//流使用完需要关闭
fis.close();
如果在fis.close()前出现异常,也无法关闭
但是流是占用资源的,使用完必须关闭。
public static void main(String[] args) {
FileInputStream fis = null;
try {
//创建输入流对象
fis = new FileInputStream("");
String s = null;
s.toString(); //出现空指针异常
}catch (IOException e){
e.printStackTrace();
}catch (NullPointerException e){
e.printStackTrace();
}finally {
if(fis != null){ //避免空指针异常
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在第十四行一定会执行finally语句块,流的关闭放在finally比较安全
try-finally
public static void main(String[] args) {
try{
System.out.println("try...");
return;
}finally {
System.out.println("finally...");
}
/*此处的代码就无法执行了*/
}
/*
try...
finally...
*/
先执行try …
再执行finally …
最后执行 return …
具有返回值方法的try-finally
public static void main(String[] args) {
System.out.println(div(10, 2)); //2147483647
System.out.println(div(10, 2)); //2147483647
}
public static int div(int a, int b){
try{
int c = a / b;
return c;
}catch (Exception e){
e.printStackTrace();
} finally {
return Integer.MAX_VALUE;
}
}
finally语句块一定在return后执行,所以finally的语句块中内容一定会先return
finally面试题
public static void main(String[] args) {
System.out.println(m());
}
public static int m(){
int i = 100;
try{
return i;
}finally {
i++;
}
}
结果是100
Java中规定:方法体中的代码必须自上而下的顺序逐行执行,这是亘古不变的。
return出现在int i = 100
的下方,返回的结果必须是100才会保证这个规则
自定义异常类
-
throws:写在方法定义处,表示声明一个异常告诉调用者,使用本方法可能会有哪些异常(运行时异常可以不处理)
-
throw:写在方法内,结束方法,手动抛出异常对象交给调用者
步骤:
- 继承Exception
- 声明有参和无参构造方法
- 在可能出现异常的位置throw new ;并且在方法声明位置throws
public class MyStackOperationException extends Exception {
public MyStackOperationException() {
}
public MyStackOperationException(String message) {
super(message);
}
}
import java.util.Arrays;
public class Stack {
int[] ints;
int index = -1;
public Stack(int[] ints) {
this.ints = ints;
}
public void push(int i) throws MyStackOperationException {
if (index >= ints.length - 1){
//创建异常对象 手动抛出
throw new MyStackOperationException("error:stack is full");
}
ints[++index] = i;
System.out.println("input succeed " + " index : " + index + " elem : " + i);
}
public void pop() throws MyStackOperationException {
if (index <= -1){
throw new MyStackOperationException("error: stack is empty");
}
int ret = ints[index--];
System.out.println("index : " + (index + 1) + " elem : " + ret + " output succeed");
}
public void printArray(){
System.out.println(Arrays.toString(ints));
}
}
测试:
public class StackTest {
public static void main(String[] args) {
Stack stack = new Stack(new int[3]);
try{
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);}
catch (MyStackOperationException e){
System.out.println(e.getMessage());
} //捕捉到之后程序向下走
stack.printArray();
try{
stack.pop();
stack.pop();
stack.pop();
stack.pop();
}catch (MyStackOperationException e){
System.out.println(e.getMessage());
}
}
}
/*
input succeed index : 0 elem : 1
input succeed index : 1 elem : 2
input succeed index : 2 elem : 3
error:stack is full
[1, 2, 3]
index : 2 elem : 3 output succeed
index : 1 elem : 2 output succeed
index : 0 elem : 1 output succeed
error: stack is empty
*/
方法重写中的异常
之前在[[005-面向对象核心#方法覆盖|方法覆盖]]中提到过,重写之后的方法不能比重写之前的方法抛出更多 / 更宽泛的异常
class Animal{
public void doSome(){
}
}
class Cat extends Animal{
@Override
public void doSome() throws Exception{
super.doSome();
}
}
编译报错;编译器检测到的父类是没有异常抛出的,而在子类执行期间却抛出了编译时异常
但是对于RunTimeException来说:
class Animal{
public void doSome(){
}
}
class Cat extends Animal{
@Override
public void doSome() throws RuntimeException{
super.doSome();
}
}
这是不会报错的,因为不要求对运行时异常进行处理
try-with-resources
Java 7引入了try-with-resources语句,用于自动关闭实现了AutoCloseable接口的资源。在try-with-resources语句中,我们可以在try关键字后面声明一个或多个资源,这些资源将在代码执行完毕后自动关闭。
如果对象是在try-with-resources代码块中声明的, AutoCloseable对象的close()方法会被自动执行。这种构造方式保证了最快的资源释放,避免资源耗尽异常。
try (Resource1 res1 = new Resource1();
Resource2 res2 = new Resource2()) {
// 使用资源的代码
} catch (ExceptionType e) {
// 处理异常
}
try-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class Tester {
public static void main(String[] args) throws IOException {
System.out.println(readData("test"));
}
static String readData(String message) throws IOException {
Reader inputString = new StringReader(message);
BufferedReader br = new BufferedReader(inputString);
try (br) {
return br.readLine();
}
}
}
验证Autocloseable