面向对象基础篇 – 程序异常
在计算机程序运行的过程中,总是会出现各种各样的异常,如何处理这些异常
常见的一些错误
空指针异常 NullPointerException
public class Demo {
public static void main(String[] args) {
int[] arr = null ;
System.out.println(arr[0]);
}
}
/*
Exception in thread "main" java.lang.NullPointerException
at Demo.main(Demo.java:4)
*/
角标越界异常 IndexOutOfBoundsException
public class Demo {
public static void main(String[] args) {
int[] arr ={1,2,3} ;
System.out.println(arr[3]);//ArrayIndexOutOfBoundsException
String str = "Hello";
System.out.println(str.charAt(10);//StringIndexOutOfBoundsException
}
}
算数异常 ArithmeticException
public class Demo {
public static void main(String[] args) {
System.out.println(10/0);//ArithmeticException
}
}
数字格式化异常 NumberFormatException
public class Demo {
public static void main(String[] args) {
String str = "dss";
System.out.println(Integer.parseInt(str));//NumberFormatException
}
}
栈溢出 StackOverflowError
public class Demo {
public static void main(String[] args) {
show();
}
private static void show() {
show(); //StackOverflowError
}
}
堆内存溢出 OutOfMemoryError
public class Demo {
public static void main(String[] args) {
show();
}
private static void show() {
int[] arr = new int[100000000];//OutOfMemoryError
show();
}
}
对于一个递归函数来说,即使函数递归次数有限,但也有可能出现栈内存溢出,或者堆内存溢出的情况,对于这种随机出现的错误,是不可避免的。
对于一个健壮的程序,他应该是能够提前预知并处理一些问题的,而不是在遇到错误的时候直接终止
错误预处理的方法
使用错误代码标记
public class Demo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
int index = 10;
int num = getNum(arr ,index);
if (num == -1) {
System.out.println("数组为空");
}else if (num == -2) {
System.out.println("角标越界");
}else {
System.out.println(num);
}
System.out.println("main end ...");
}
private static int getNum(int[] arr, int index) {
if(arr == null) return -1 ; //数组为空
else if(arr.length<=index) return -2 ; //角标越界
return arr[index];//返回对应值
}
}
如上,通过错误代码标记,我们对错误进行处理,且不停止程序
但是,这种方法往往存在很大的弊端:
假如,错误代码与数组存储值相同时,则会产生冲突
异常对象
java是一门面向对象的编程语言,因此,异常其实在java种也可以看作是一个对象
通过throw来抛出异常对象
语法: throw +异常对象
public class Demo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
int index = 10;
int num = getNum(arr ,index);
System.out.println(num);
System.out.println("main end ...");
}
private static int getNum(int[] arr, int index) {
if(arr == null) {
//如果内部无法解决问题,则抛出错误异常交给调用者处理
throw new NullPointerException("数组为空");//数组为空
}
else if(arr.length<=index) {
ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException("角标越界");
throw e ;
}; //角标越界
return arr[index];//返回对应值
}
}
通过throws声明异常
某一个函数内部产生编译时异常对象,但是该函数内部并不会处理该问题,则交由调用者处理
声明 就是告诉调用者 我函数内部可能会出现问题 但是内部不会解决 交由调用者来解决
如果没有任何人来具体处理的话,可能会导致程序中断
throws + 异常类 在函数声明之后
public class Demo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
int index = 10;
int num = getNum(arr ,index);
System.out.println(num);
System.out.println("main end ...");
}
private static int getNum(int[] arr, int index) throws NullPointerException ,ArrayIndexOutOfBoundsException {
return arr[index];//返回对应值
}
}
throw 和 throws 的异同
- 相同点
- 两者在抛出异常时,抛出异常的方法并不负责处理,顾名思义,只管抛出,由调用者负责处理。
- 不同点
- throws用于方法头,表示的只是异常的申明,而throw用于方法内部,抛出的是异常对象
- throws可以一次性抛出多个异常,而tthrow只能一个
- throws抛出异常时,它的上级(调用者)也要申明抛出异常或者捕获,不然编译报错。而throw的话,可以不申明或不捕获(这是非常不负责任的方式)但编译器不会报错。
即使我们不对异常进行声明或者抛出,JVM也会自动生成异常对象
Throwable类
错误的问题多了,那么就可以将共性进行抽取,抽取出来的结果 异常体系
Throwable 类是 Java 语言中所有错误或异常的超类
只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出
类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
实现子类
- Error
程序中出现的一些重大错误,这些错误直接和JVM、内存、硬件有关,
不需要预判如果一旦出现该错误,则不会有挽回的情况,除非重新更改代码
例如:
IOError 输入输出错误
VirtualMachineError 虚拟机错误StackOverflowError
OutOfMemoryError
- Exception
程序中出现的一些普通错误,这些错误可以在编码的时候预先进行预判和处理有挽回的机会
例如:
编译时异常:Exception的其他子类
对于编译时异常而言,在程序编译期间,未运行之前,能够预判到的一些错误
需要预先的处理(声明、捕获)
FileNotFoundException 文件未找到异常
File file = new File(“C:\abc.txt”);
ParseException 解析异常
Date date = dateFormat.parse(“2015-08-08 15:30:31”);运行时异常:Exception中RuntimeException类及其子类 对于运行时异常而言,只有程序在运行的时候才能判断代码是否出错 一旦程序运行了并且出错,这种错误一般不会进行额外的处理(声明、捕获) 这些异常 也一般都是有JVM来进行判断和抛出的 ArithmeticException ClassCastException IndexOutOfBoundsException ArrayIndexOutOfBoundsException, StringIndexOutOfBoundsException NullPointerException
注意
RuntimeException与Error都是没必要进行处理的错误,一旦出错只能认栽
Exception的其他子类 都是需要进行处理的(声明,捕获)
异常捕获
某一个函数内部产生编译时异常对象,但是该函数内部会自己解决,不再交由调用者处理
捕获 就是函数内部来解决出现的问题,不让该问题继续向上层抛出/传递
有人处理了,不会导致程序中断
try{} 尝试执行某一段代码
catch(){} 如果try中的代码出现问题,则有catch中的语句来处理该问题
public class Demo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
int index = 10;
int num = getNum(arr , index);
}
private static int getNum(int[] arr, int index) {
try{
return arr[index];
}catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
System.out.println("出现错误");
return -1 ;
}
}
}
/*
输出:
出现错误
*/
if try中出现异常
走catch代码
else
接着往后直行
try语句在尝试执行时,尽量把范围缩小,不要全部代码都放到try语句中
因为try代码中,某一行一旦发生异常,则try中后续的代码不会执行!!!
一般把和异常无关的内容尽量放到try外面
catch可以一次性捕捉多个异常问题 可以使用多个catch语句块,来对不同的异常进行分别的处理
也可以一次全部捕捉
也可以直接捕获Exception
/*catch (Exception e) {
}*/
finally代码块
无论是否出现异常都会执行的代码片段
一般用于释放资源,关闭资源
public class ExceptionDemo09 {
public static void main(String[] args) {
int[] arr = {1,2,3};
int index = 0;
int result = show(arr, index);
System.out.println(result);
}
private static int show(int[] arr, int index) {
try {
System.out.println("try code...");
int num = arr[index];
System.out.println(num);
//finally代码执行....
return 0; //没有异常 先finally 后return 0;
} catch (Exception e) {
System.out.println("catch code...");
//finally代码执行....
return 2; //有异常时 先finally 后return 2;
} finally {
System.out.println("finally code...");
return 3;
}
// return 1; //如果catch中 和 finally中 有return 此处被屏蔽不可达代码
}
}
/*
try有return 0 catch没有 finally没有 函数末尾有return 1
没有异常 try->finally->return 0
产生异常 try->catch->finally->return 1
try有return 0 catch有return 2 finally没有 函数末尾return 1被屏蔽
没有异常 try->finally->return 0
产生异常 try->catch->finally->return 2
所以会发现 无论是否有异常 要么return 0 要么return 2 所以return 1被屏蔽了
try有return 0 catch有return 2 finally有return 3 函数末尾return 1被屏蔽
无论是否异常 都return 3
*/