程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?。
Java提供了更加优秀的解决办法:异常处理机制。
异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。
Java异常
什么是java异常
在引言中已经介绍了什么是java异常,但是看这些可能不太清晰明白,这里说个小故事:
案例小故事(此案例仅教学使用,生活中若有雷同,概不
负责)
举例:今天天气很好,班长想去爬大蜀山,骑哈罗单车
去,去山里呼吸新鲜空气
问题1:山路突然崩塌了,还好班长及时停住,但是过不
去了,这属于严重问题
问题2:班长开着一辆哈罗单车,开到一半突然发现单车
没电了,路途中换一辆开,然后就顺利地
达到大蜀山。这样的问题应该是在出发之前考虑到
的。
问题3:班长正在骑车欣赏风景,发现前面的路不太好
走,都是小石子,旁边有一个平坦的路
他偏偏不走平坦的路,就喜欢走小石子上面,结果车
倒了。
这样的问题是骑车过程中出现了问题。
Java中的异常
1. 严重的问题
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
使用格式
格式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");
}
}
异常处理掌握的方法
- getMessage():获取异常信息,返回字符串
- toString():获取异常类名和异常信息,返回字符串
- printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置,返回值是void
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"); }
}
2.throws
在今后的开发过程中,有些情况我们根本没有权限去做一些处理异常
或者说,我们根本就处理不了,干脆就不处理。
为了解决这样的问题,并且还能保证程序正常运行的情况下
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则是抛出了异常,执行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();
}
}