程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?。
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
在引言中已经介绍了什么是java异常,但是看这些可能不太清晰明白,这里说个小故事: 案例小故事(此案例仅教学使用,生活中若有雷同,概不 负责) 举例:今天天气很好,班长想去爬大蜀山,骑哈罗单车 去,去山里呼吸新鲜空气 问题1:山路突然崩塌了,还好班长及时停住,但是过不 去了,这属于严重问题 问题2:班长开着一辆哈罗单车,开到一半突然发现单车 没电了,路途中换一辆开,然后就顺利地 达到大蜀山。这样的问题应该是在出发之前考虑到 的。 问题3:班长正在骑车欣赏风景,发现前面的路不太好 走,都是小石子,旁边有一个平坦的路 他偏偏不走平坦的路,就喜欢走小石子上面,结果车 倒了。 这样的问题是骑车过程中出现了问题。
1. 严重的问题:Error,一般情况下,我们不做这样的处理。 举例今后可能会遇到的这样的问题 OOM,Out Of Memory,内存溢出的问题。 2. 异常:Exception 运行时异常:RuntimeException,这样的问题我们也不处理,因为这样类似的问题一般情况下都是代码不够严谨造成 的。 编译时异常:除了不是RuntimeException,都是编译时期 异常,必须要处理,如果不处理,编译不通过无法运行。 如果程序出现了问题,我们没有做任何出口i,最终JVM会 给出一个默认的处理方式, 把异常类的名称,相关的原因,以及出现问题的相关信息和 位置信息输出在控制台,同时 java程序会结束,后面的代码不会执行。
public class ExpectionDemo1{ public static void main(Stirng[] args){ //这里会输出当前的时间 Data data = new Date(); System.out.println(data); //但是我们想转换一下格式,我们需要把格式转成一般的格式 //在开发中我们经常去实现日期的转换 2021-12-24 10:57:00 //HH大小的代表的是24小时刻度 //hh小写的代表的是12小时刻度 SimpleDataFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System,out.println(s); String s1 = “2021-12-24”; SimpleDateFormat sdfq = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date1 = null; //java.text.ParseException: Unparseable date: "2021-12-24" try{ date1 = sdf1.parse(s1); }catch(ParseExpection e){ e.printStackTrace(); } System.out.println(date1); } }
格式1: try{ 可能会出现的问题的代码 }catch(异常的类名 变量名){ 针对问题的处理 }finally{ 无论报不报错都会执行代码 (一般情况下,这里都是放资源的代码) } 格式2: try{ 可能会出现问题的代码; }catch(异常的类名 变量名){ 针对问题的一些处理; } 格式3:处理多个异常 try{ 可能会出现问题的代码1; 可能会出现问题的代码2; ... }catch(异常的类名1 变量名1){ 针对问题的一些处理; }catch(异常的类名2 变量名2){ 针对问题的一些处理; }
多个catch注意事项:
1、能明确异常类型的时候,尽量明确类型,不要用父类大的做处理 2、catch与catch之间的异常是平级关系,多个catch异常之间没有先后顺序关系,一旦出现了一个 父类继承关系,父必须在最后 3、一旦try里面的代码出现了问题,就会去匹配catch里面的异常, 继续执行程序try...catch...后面的代码,try里面的代码就停在了报错的那一步。
代码演示:
public class ExceptionDemo2{ public static void main(String[] args){ int a =10; int b = 0; if(b==0){ System.out.println("除数不能为0"); }else{ System.out.println(a/b); } } int[] arr = null; //String --Date String s = "2021-12-24 14"; SimpleDateFormat sdf = new SimpleDateDFormat("yyyy-MM-dd HH:mm:ss"); try{ Date date = sdf.parse(s); }catch (ParseException e){ System.out.println("日期转换出错了!!!"); } try{ System.out.println(arr.length); }catch(NullPointerException e){ System.out.println("空指针异常"); } }
JDK1.7之后针对多个异常处理新的处理方式: try{ 可能会出现问题的代码; }catch(异常类名1 | 异常类名2 | ... 变量名){ 处理异常的提示; } 注意事项: 1、处理方式是一致的,这个方法虽然比较简洁,但是不够好,针多种类型的问题, 只给出了一种解决方案 2、catch中多个异常类型之间的关系必须是平级关系,不能存在继承关系
public class ExceptionDemo3{ public static void main(String[] args){ int[] arr = null; //String -- date String s = "2021-12-24 14"; SImplleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try{ Date date = sdf.parse(s); //如果在try里面的代码中间报错了 //会直接匹配catch里面的异常 //try中剩下的代码不会执行 System.out.println(date); System.out.println("world"); System.out.println(arr.length); }catch (ParseException | NullpointerException e){ System.out.println("报错了"); } System.out,println("hello"); } }
public class ExceptionDemo4{ public static void main(String[] args){ int a = 10; int b = 0; //ArithmeticException try{ System.out.println(a/b); }catch(ArithmeticException e){ //ArithmeticException e = new ArithmeticException(); //打印的是出现异常的原因 // System.out.println(e.getMessage()); //java.lang.ArithmeticException: / by zero //异常的类名: 产生问题的原因 // System.out.println(e.toString()); //通过观察发现,和我们之前不做任何处理的时候,JVM自动默认处理的打印异常结果是一样的 //我们不能不处理,如果不处理一旦发生异常,后面的代码不会运行 //我们做了处理之后,后面的代码才会正常运行 e.printStackTrace(); } String s = "2021-12-24 14"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date date = sdf.parse(s); } catch (ParseException e) { e.printStackTrace(); } System.out.println("hello"); } }
在今后的开发过程中,有些情况我们根本没有权限去做一些处理异常 或者说,我们根本就处理不了,干脆就不处理。 为了解决这样的问题,并且还能保证程序正常运行的情况下 Java针对这种情况,提供了另外一个解决异常的方式:throws抛出(跟在方法后面)
throws 异常类名 写在方法小括号后面,大括号之前
注意事项:
1、main方法上个尽量不要进行异常抛出,因为程序会停止,后面代码不会执行 但是,我上课的时候为了方便你们看代码,我就直接抛出, 做开发的时候,不要在main方法。 2、编译时期的异常抛出,方法内部不需要做处理,是由将来调用该方法的调用者处理 3、运行时期异常可以不抛出,但是一旦调用,出错后,后面的代码依旧不会执行 4、最好抛出一个具体的异常类型,也是推荐这么做的,可以抛出多个异常,用逗号隔开
public class ExceptionDemo{ public static void main(String[] args){ //方法中没有处理异常,调用者必须处理 // try { // fun(); // } catch (ParseException e) { // e.printStackTrace(); // } // fun(); try { fun1(); }catch (NullPointerException e){ e.printStackTrace(); } System.out.println("hello"); public static void fun() throws ParseExpection,NullPointerException{ String s = "2021-12-24 14"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = sdf.parse(s); System.out.println(date); } public static void fun() throws NullPointerException{ int[] arr = null; System.out.println(arr.length); } } }
用在方法体内,跟的是 异常对象名 只能抛出一个异常对象名 表示抛出异常,由方法体内的语句处理 throw则是抛出了异常,执行throw则一定抛出了某种异常。
throws: 用在方法的声明后面,跟的是异常的类名 可以跟多个异常类名,用逗号隔开 表示的是可能会发生的异常,抛出给调用者处理,表示的是一种可能性, 不一定会发生这种异常 throw: 用在方法体内,跟的是异常对象名 只能抛出一个异常对象名 表示抛出异常,由方法体内的语句处理 throw则是抛出了异常,执行throw则一定抛出了某种异常
public class ExceptionDemo6 { public static void main(String[] args) { try { fun(); }catch (ArithmeticException e){ //ArithmeticException e = new ArithmeticException() e.printStackTrace(); } System.out.println("hello"); } public static void fun(){ int a = 10; int b = 0; if(b==0){ System.out.println("报错,除数不能为0"); throw new ArithmeticException(); }else { System.out.println(a/b); } } }
finally: 最终的意思 在try...处理异常的时候,末尾通常情况下会添加一个finally 被它控制的语句体,一定会执行,一般情况下,里面放的是与释放资源相关的代码 try...catch...finally
public class ExceptionDemo7 { public static void main(String[] args) { String s = "2021-12-24 14:32:12"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date date = sdf.parse(s); System.out.println(date); }catch (ParseException e){ e.printStackTrace(); }finally { //无论报不报错都会执行 System.out.println("这里的代码一定会执行!!!"); } } }
面试题: final,finally和finalize的区别 final:最终的意思,可以修饰类,成员变量,成员方法 修饰类:类不能被继承 修饰成员变量:变量变常量 修饰成员方法:方法不能被重写 finally:是异常处理的一部分,一般情况下用于释放资源的作用,一般情况下都会执行 特殊情况下: System.exit(0); finalize: 是Object类中的一个方法名,用于手动垃圾回收,但是不一定调用就开始回收。 如果catch里面有return语句,请问finally的代码还会执行吗? 如果会,请问是在return前还是return后。return之间
public class ExceptionDemo8 { public static void main(String[] args) throws ParseException{ // int a = 10; // int b = 0; // int c = 0; // // fun(); // // try { // //让程序停止 System.exit(0); // c = a/b; // }catch (ArithmeticException e){ // e.printStackTrace(); // }finally { // System.out.println("这是finally中的内容"); // } System.out.println(fun()); } public static int fun() { int a = 10; String s = "2021-12-24 14"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { a =30; Date date = sdf.parse(s); System.out.println(date); a = 40; }catch (ParseException e){ a = 50; return a;//50 }finally { // System.out.println(a); a = 70; int b = 80; // return b; // return a; } // System.out.println(a); // return a; } }
异常注意事项: 1、子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类。(父亲坏了,儿子不能比父亲更坏) 2、如果父类抛出了多个异常,子类重写父类时, 只能抛出相同的异常或者是他的子集,子类不能单独抛出父类没有的异常 3、如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常, 如果子类方法内有编译时期异常发生,那么子类只能try,不能throws
class A{ public void fun(){ } } class B extends A{ @Override public void fun(){ String s = "2021-12-24 14"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date date = sdf.parse(s); }catch (ParseException e){ e.printStackTrace(); } } } public class ExceptionDemo9 { public static void main(String[] args) throws NullPointerException,ParseException,ArithmeticException { B b = new B(); b.fun(); } }