异常
什么是异常
- 程序在运行过程中发生的意外情况,称之为异常:除数为0,数组小标越界…
- 异常是一种信号,用于调用者传递信息,表示程序发生了意外情况
- 程序运行时一旦出现了异常,将会导致程序立即终止,异常之后的代码都无法继续执行,那么为了保持程序有一个健壮的体系,不会因为一点小错误而终止,就需要对异常进行处理(使用java中的try catch,异常处理机制)
异常入门
先看以下代码:
public class test1 {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a/b);
System.out.println("程序继续执行");
}
}
因为被除数是不能为0的,所以被报一个算术错误ArithmeticException,导致下方的代码无法执行。
那么在现实开发中,一般都是比较大的项目,需要保持一个健壮的体系,不会因为一点小错误而终止。
此时就需要用到java中的异常处理机制:
try{
//放可能会异常的代码
}catch(Exception e){//exception类对象
//通过exception的对象,使用显示错误信息
}
当这样处理异常后,程序虽然还是会继续报错,但是异常之后的代码可以继续执行
public class test1 {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
System.out.println(a/b);
} catch (Exception e) {
System.out.println(e.getMessage());//通过对象打印错误信息
}
System.out.println("程序继续执行");
}
}
运行结果
/ by zero
程序继续执行
异常基本概念
Java中,将程序执行中发生的不正常情况称之为“异常”,(语法错误和逻辑错误不是异常)
执行过程中所发生的异常事件可分为两大类:
- Error(错误):Java虚拟机无法解决的严重问题
如JVM系统内部错误,资源耗尽等严重情况。或者StackOverflowError[栈溢出],OOM(out of memory),Error是严重错误,程序会崩溃
- Exception(异常):其他因编程错误或偶然外在因素导致的一般性问题,可以使用针对性代码处理
比如空指针访问,网络连接中断等等
- Exception又分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]
异常体系图
小结
- 异常分为两大类,运行时异常和编译时异常
- 运行时异常,编译器检查不出来,如:逻辑错误,java.lang.RuntimeException类及它的子类都是运行时异常
- 运行时异常可以不作处理,因为这类异常很普遍,若全处理可能会对可读性和与运行效率有影响
- 编译时异常,是编译器要求必须处置的异常。
五大常见运行时异常
运行时异常,编译通过后,程序在运行时发生的异常
- NullPointerException空指针异常
- ArithmeticException数学运算异常
- ArrayIndexOutOfBoundException数组下标越界异常
- ClassCastException类型转换异常
- NumberFormatException数字格式不正确异常
NullPointerException空指针异常
NullPointerException:
当应用程序试图在需要使用对象的地方发现该引用指向null时,抛出该异常
举例:
public class test1 {
public static void main(String[] args) {
String s = null;
System.out.println(s.length());
CAT Cat = null;
System.out.println(Cat.a);
}
}
class CAT{
int a = 1;
}
以上代码会发现,找不到字符串s的长度。因为它指向null,也无法找到对象Cat的属性a,因为没有分配空间
ArithmeticException数学运算异常
ArithmeticException:
当出现异常的运算条件时,抛出此异常。比如除数为0
ArrayIndexOutOfBoundException数组下标越界异常
ArrayIndexOutOfBoundException:
用非法索引访问数组时抛出的异常,非法索引就是数组中没有的下标。
举例:
public class test1 {
public static void main(String[] args) {
int [] arr = {1,2,3};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);
}
}
}
因为arr数组下标只有 0 ,1,2没有3,而循环中的条件是<=所以允许自增到3.而用3作为下标去访问该数组,就是下标越界
ClassCastException类型转换异常
ClassCastException:
当试图将对象强制转换为不是实例的子类时,抛出该异常
举例:
public class test1 {
public static void main(String[] args) {
A b = new B();//向上转型
B b2 = (B)b;//向下转型
C c = (C)b;//C和B没有直接关系,所以不能转
}
}
class A{}
class B extends A{}
class C extends A{}
NumberFormatException数字格式不正确异常
NumberFormatException:
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
举例:
public class test1 {
public static void main(String[] args) {
String some = "1234";
int name = Integer.parseInt(some);
System.out.println(name);//可以转的类型
String move = "好好学习";
int didi = Integer.parseInt(move);
System.out.println(move);//不可以转的类型
}
}
编译时异常
编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译
常见的编译异常
- SQLException//操作数据库时,查询表可能发生异常
- IOException//操作文件时,发生的异常
- FileNotFoundException//当操作一个不存在的文件时,发生的异常
- ClassNotFoundException//加载类,而该类不存在时,异常
- EOFException//操作文件,到文件末尾,发生的异常
- IIIeglArguementException//参数异常
异常小练习
-
String friends[] = {“tom”,“jack”,“milan”};
for(int i = 0;i<4;i++){
System.out.println(friends[i]);
}
数组下表越界异常ArrayIndexOutOfBoundException,自增到3了,数组只有0,1,2 -
Cat c = new Cat();
cat = null;
System.out.println(cat.name);
空指针异常NullPointerException -
public class AAA{
int x;
public static void main(String[]args){
int y;
AAA a = new AAA();
y =3/a.x;
}
}
运算异常ArithmeticException,除数不能为0 -
class Person{
public static void main(String[]args){
Object obj = new Date();
Person person;
person = (Person)obj;
System.out.println(person);
}
}
类型转换异常ClassCastException,Person和Date没有关系
异常处理机制
异常处理就是在异常发生时,对异常进行的处理
通常对于异常有两种处理方式
- try-catch-finally:程序员在代码中捕获发生的异常,自行处理
- throws:将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
try-catch-finally
语法:
try{
//可能会发生异常的代码
}catch(Exception e){
//1.当异常发生时将异常封装成Exception对象e,传递给catch
//2.得到对象后,程序员在这个代码块中自行处理
//3.如果没有发生异常,catch代码块不执行
}finally{
//不管有没有发生异常,都要执行以下finally代码块里的内容,一般用来放释放内存的代码
}
throws
throws就是将异常抛出,不管这个异常,让调用者去处理。
比如 main方法是JVM调用的,main方法又调用了f1方法,f1方法又调用了f2方法。而f2方法体中又个异常。
此时f2方法有两个选项,第一个使用try-catch-finally自己解决,第二个使用throws关键字抛给f1方法,让它去解决
示意图:
try-catch处理异常
Java提供try和catch来处理异常,try代码块用于包含可能出错的代码,catch代码块用于处理try代码块中的异常
语法:
try{
可疑代码
将异常生产对象传递给catch
}catch(异常){
对异常的处理
}finally{
有没有异常都会执行
}
finally有没有都行,语法也通过
try catch注意事项:
- 如果try代码块中的代码发生了异常,那么异常代码后面的语句都不执行,直接进入catch代码块
- 如果异常没有发生,则顺序执行try的代码块,不会进入catch
- 如果希望无论是否有异常,都执行某一段代码,可以写在finally代码块里
- 当try代码块中有多个异常时,可以使用多个catch代码块传入对于的异常子类,进行分别捕获。但是必须子类在前,父类在后。负责父类会全部捕获掉
- 可以使用try-finally配合使用,不管有没有异常都要执行某一段代码,就算有异常也不对异常进行操作
注意点演示:
第一点:
public class test1 {
public static void main(String[] args) {
try {
int a = 10;
int b = 0;
System.out.println(a/b);
System.out.println("异常后的。。。");
} catch (Exception e) {
System.out.println(e.getMessage());;
System.out.println("异常处理中...");
}
}
}
运行结果:
/ by zero
异常处理中…
可以看到异常代码后的语句没有输出。一有异常就直接进入catch了
第二点:
public class test1 {
public static void main(String[] args) {
try {
int a = 10;
int b = 1;
System.out.println(a/b);
System.out.println("异常后的。。。");
} catch (Exception e) {
System.out.println(e.getMessage());;
System.out.println("异常处理中...");
}
}
}
输出结果:
10
异常后的。。。
catch代码块中的语句没有输出
第三点:
public class test1 {
public static void main(String[] args) {
try {
int a = 10;
int b = 1;
System.out.println(a/b);
System.out.println("异常后的。。。");
} catch (Exception e) {
System.out.println(e.getMessage());;
System.out.println("异常处理中...");
}finally{
System.out.println("嘀嘀嘀");
}
}
}
输出结果:
10
异常后的。。。
嘀嘀嘀
第四点:
public class test1 {
public static void main(String[] args) {
try {
int a = 10;
int b = 01;
System.out.println(a/b);//算术异常
String name = "好好学习";
int nu = Integer.parseInt(name);
System.out.println(nu);//数字格式异常
} catch(ArithmeticException a){
System.out.println(a.getMessage());
System.out.println("算术异常被捕获");
}catch (NumberFormatException c){
System.out.println(c.getMessage());
System.out.println("数字格式异常被捕获");
}catch (Exception e){
System.out.println(e.getMessage());
System.out.println("其他异常被捕获");
}
}
}
这里需要注意,当try同时有多个异常时,使用多个catch去捕获,不是同时捕获,而是类似查漏补缺的意思,比如当没有算术异常,就会跳过catch算术异常的代码块。
或者可以这样理解:由于try代码块中一旦发现异常,后面的语句就不执行了,就算后面还有异常也不执行,那么也就无法同时捕获多个异常,所以多个catch的使用其实就是为不同的异常准备不同的处理方案,而不是一上来就把异常全捕获掉。
第五点:
public class test1 {
public static void main(String[] args) {
try {
String name = "12";
int nu = Integer.parseInt(name);
System.out.println(nu);//数字格式异常
}finally {
System.out.println("管你有没有异常");
}
}
}
try catch练习
一:以下代码会输出什么?
public class Exception01 {
public static int method(){
try {
String [] names = new String[3];
if (names[1].equals("tom")){
System.out.println(names[1]);
}else{
names[3]="yyds";
}
return 1;
}catch (ArrayIndexOutOfBoundsException e){
return 2;
}catch (NullPointerException e){
return 3;
}finally {
return 4;
}
}
public static void main(String[] args) {
System.out.println(method());
}
}
因为还没对数组进行赋值,就使用equals对比。所以会报空指针异常NullPointerException
按道理来说应该会直接返回 3 然后推出,但是这里有个小细节,finally是必须执行的,它的优先级比return更高,所以并不会在 catch空指针异常返回,而是到finally返回,所以最终输出 4
二:以下代码会输出什么?
public class Exception01 {
public static int method(){
int i =1;
try {
i++;
String [] names = new String[3];
if (names[1].equals("tom")){
System.out.println(names[1]);
}else{
names[3]="yyds";
}
return 1;
}catch (ArrayIndexOutOfBoundsException e){
return 2;
}catch (NullPointerException e){
return ++i;
}finally {
return ++i;
}
}
public static void main(String[] args) {
System.out.println(method());
}
}
这题跟上提差不多,发生空指针异常然后进入catch空指针,return ++i虽然不会直接返回,但是还是会执行,因此一共自增了3次。到finally返回,最后输出4
三:以下代码会输出什么?
public class Exception01 {
public static int method(){
int i =1;
try {
i++;
String [] names = new String[3];
if (names[1].equals("tom")){
System.out.println(names[1]);
}else{
names[3]="yyds";
}
return 1;
}catch (ArrayIndexOutOfBoundsException e){
return 2;
}catch (NullPointerException e){
return ++i;
}finally {
++i;
System.out.println("i=" +i);
}
}
public static void main(String[] args) {
System.out.println(method());
}
}
依旧发生空指针到catch空指针那,return ++i;此时i = 3,由于下面还有finally,所以不会立刻返回,但是下面的finally又没有返回语句,所以最后还是会在catch空指针这里返回,那么又得去执行下面finally的代码块,所以这里底层会先用一个临时变量保存此时的i,相当于 indxe = i.此时的i是3.然后再去执行finally的内容将i自增到4.接着返回到catch空指针,返回那个临时变量indxe。也就是 3.最终也输出3.
注意点,最终返回的不是i 是一个临时变量
try-catch-finally执行内容注意点
- 如果try中没有出现异常,则执行try中所有内容,不执行catch的内容。如果有finally,不管有没有异常都要执行finally
- 如果try中出现异常,则try中异常后的内容不执行,直接执行catch中的内容,如果有finally,不管有没有异常都要执行finally
try-catch实践应用
要求客户输入一个整数,如果不是整数,就提示,且让他反复输入,知道输入整数为止(利用异常完成)
import java.util.Scanner;
public class Exception01 {
public static void method(){
try {
System.out.println("请输入一个整数:");
String num = new Scanner(System.in).next();
int a = Integer.parseInt(num);
System.out.println("您输入的整数为:"+a);
} catch (NumberFormatException e) {
System.out.println("输入错误,重新输入!");
method();
}catch (Exception e){
System.out.println("未知错误");
}
}
public static void main(String[] args) {
method();
}
}
throws抛出异常
- 如果一个方法(中的某条语句)可能会生产某种异常,但是不能确定如何处理这种异常,则此方法应显示的声明抛出异常,表明该方法不对这些异常进行处理,而由该方法的调用者处理
- 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
语法:
返回类型 方法名(形参列表) throws 异常列表/类型{
方法体
可能有异常的语句
}
入门案例:
public class Exception01 {
public static void f1(){
try {//在这解决异常
f2();
} catch (Exception e) {
System.out.println("除数不能为0");
}
}
public static void f2() throws ArithmeticException,Exception{//抛出了异常
int a = 12;
int b = 0;
System.out.println(a/b);//异常发生点
}
public static void main(String[] args) {
f1();
}
}
由上案例可以分析以下几点:
1.throws 后面可以写多个可能的异常类型,使用,分开
2.f2发生的异常抛出给调用者f1,f1在方法体中解决
theows使用细节
- 对于编译异常,必须在程序中处理掉,使用try-catch或者throws
- 对于运行时异常,程序中如果没有处理,默认就是throws抛出
- 子类重写父类的方法时,对抛出异常的规定是:子类重写的方法,所排除的异常类型必须和父类抛出的异常类型一致,或者是父类抛出异常的子类型
- 在throws的时候,如果有方法try-catch,就相当于处理异常,可以不用throws,相当二选一。不处理就让别人处理
说明:
第一点
如果抛出的异常是编译异常,那么必须使用try-catch处理掉。
比如f2方法有编译异常,使用throws抛出了,而f1方法调用了f2,那么必须把f1的编译异常使用try-catch处理掉
public class Exception01 {
public static void f1(){
try {
f2();//处理异常点
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void f2() throws FileNotFoundException {//抛出了异常
FileInputStream fis = new FileInputStream("d://aa.txt");// 异常发生点
}
public static void main(String[] args) {
f1();
}
}
可以看到f2的编译异常,被f1处理了。如果不适用try-catch处理,就会报错无法运行
而如果f2抛出的是运行时异常,比如抛出了一个ArithmeticException算术异常,那f1就不强制要求处理了,只是运行时JVM会直接报错退出.这是因为java有默认对于运行时异常的处理机制,第二点会说
第二点:
通过前面的知识点我们知道,throws只是把异常抛给调用者去处理,最高等级的调用者是JVM,它的处理方式就是显示异常然后退出。
所以在调用有运行时异常的方法时,就算这个方法没有显示的有throws,那么它也是默认的有throws的。
就比如main方法就有throws,而JVM会调用main方法。
举例:
public class Exception01 {
public static void f1(){//throws Exception
f2();
}
public static void f2() {//throws Exception//抛出了异常
int a =1;
int b =0;
System.out.println(a/b);// 异常发生点
}
public static void main(String[] args) {//throws Exception
f1();
System.out.println("异常后》。。");
}
}
以上代码虽然没有写throws,但是实际上是f2抛出算术异常,f1调用了f2.就接收了这个异常,然后f1也抛出这个异常,main方法又调用了f1,接收了这个异常,而JVM又调用了main方法,所以最终是由JVM来处理,直接报算术异常然后退出程序
运行可以发现 异常后》。。 这句话没有输出,就是直接发生异常然后退出了
第三点:
子类重写父类的方法抛出的异常只能是父类抛出异常的子类型,比如父类方法抛出的是一个运行时异常:RuntimeException
那么子类重写父类方法后只能抛出RuntimeException的子类,比如算术异常,空指针异常,类型转换异常等等。。
就修饰符差不多:修饰符是子类重写父类的方法,修饰符范围不能变小,只能变大或相等。而子类重写父类抛出的异常是只能变小或相等,不能变大
class A{
public void Father()throws RuntimeException{}
}
class B extends A{
@Override
public void Father() throws ArithmeticException {
}
}
自定义异常
- 当程序出现了某些错误,但该错误信息 并没有在Throwable子类描述处理,这个时候可以自己设计异常类,用于描述错误信息
步骤:
1.定义类:自定义异常类名,继承Exception或RuntimeExcception
2.如果继承Exception就是编译时异常
3.继承RuntimeExcception就是运行时异常
一般情况都是继承RuntimeExcception运行时异常,这样可以使用java默认的机制。
使用方式:
自定义异常类,基础RuntimeExcception或者Exception
然后构造器传入一个参数,调用父类的构造器
接着在使用处,传入你需要的参数进行判断,如果不符合要求,就throw给一个异常类的对象传入错误信息
案例:
接收一个int类型的数字,要求范围在18~120之间,否则抛出一个自定义异常。
public class Exception01 {
public static void main(String[] args) {
int a = 233;
if (!(a>=1&&a<=120)){
//如果不符合要求就
throw new ArgExeception("输入错误,要求在1~120之间");
}
System.out.println("输入正确");
}
}
class ArgExeception extends RuntimeException{
public ArgExeception(String message) {
super(message);
}
}
如果不是运行时异常,是编译异常就还得在方法形参列表后面显示的写上thorws给哪个异常
public class Exception01 {
public static void main(String[] args) throws AgeExeception {
int a = 233;
if (!(a>=1&&a<=120)){
//如果不符合要求就
throw new AgeExeception("输入错误,要求在1~120之间");
}
System.out.println("输入正确");
}
}
class AgeExeception extends Exception{
public AgeExeception(String message) {
super(message);
}
}
throw和throws的区别
关键字 | 意义 | 位置 | 后面跟的东西 |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
异常练习题
一 编程题
- 编写应用程序EcmDef.java,接收命令行的两个参数(整数),计算两数相除
- 计算两个数相除,要求使用方法cal(int n1,int n2)
- 对数据格式不正确,缺少命令行参数,除0 进行异常处理
public class EcmDef {
public static void main(String[] args) {
try {
if (args.length>2){
throw new ArrayIndexOutOfBoundsException(" 参数个数不对");
}
int n1 = Integer.parseInt(args[0]);
int n2 = Integer.parseInt(args[1]);
int n3 = cal(n1,n2);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage());;
} catch (NumberFormatException e) {
System.out.println("参数格式不正确");;
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
}
public static int cal(int n1,int n2){
return n1/n2;
}
}
二 写出运行结果
public class test1 {
public static void func(){
try{
throw new RuntimeException();
}finally {
System.out.println("B");
}
}
public static void main(String[] args) {
try{
func();
System.out.println("A");
}catch (Exception e){
System.out.println("C");
}
System.out.println("D");
}
}
首先调用func方法,执行try,发现抛了一个运行时异常,所以try后面不执行了,直接执行finally 输出 B
然后回到main方法由于func方法有异常,所以try后面不执行,直接去catch执行输出 C
再然后因为异常已经被捕获了,所以会继续输出main方法后面的 D
结果是 B C D