
程序执行过程中发生了不正常的情况,JVM将异常信息打印到控制台,从而方便调试和增强程序的健壮性。
异常在Java中以类的形式存在,如NullPointerException,每一个异常类都可以创建异常对象。
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
NullPointerException npe = new NullPointerException("空指针异常");
举个例子:
int a = 10;
int b = 0;
int c = a/b;
//当JVM执行到这个地方时,会new异常对象,即new ArithmeticException("/by zero");
//并且JVM将new的异常对象抛出,打印输出信息到控制台
类比于生活:
火灾是一种异常(一种抽象出来的类),而异常对象则是xxx地方发生火灾了。
父类 java.lang.Object<-------java.lang.Throwable类------>子类Error和Exception
插播:
UML,即Unified Modeling Language,统一建模语言。
用来描述类个类之间的关系,程序执行的流程,对象的状态等。
对应的工具有:Rational Rose,startUML

定义

异常的发生就是new对象,而只有运行阶段可以new对象,故运行时异常和编译时异常都是发生在运行阶段。
两种异常的区别
处理思路1:
在调用者方法声明的位置上,使用throws关键字,抛给上一级,即谁调用,抛给谁
public class Exception1 {
public static void main(String[] args) throws ClassNotFoundException {
//这里main方法调用doSome方法,必须对这个异常进行预处理
doSome();
}
//doSome方法的声明位置有throws ClassNotFoundException
//这表示这个方法执过程中很有可能会出现ClassNotFoundException异常
//ClassNotFoundException异常的直接父类是Exception,即它是编译时异常
public static void doSome() throws ClassNotFoundException{
}
}

😉
在Java中,异常发生之后如果一直上抛,最终抛给main方法,main方法继续向上抛到JVM,到JVM,程序会被终止执行。
处理思路2:
使用try……catch语句进行异常的捕捉
public class Exception1 {
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
}
}
try{ //尝试
m1();
}catch(FileNotFoundException e){ //e是一个引用,保存了new出来的那个异常对象的内存地址
System.out.println("文件不存在"); //捕捉到异常以后执行的语句
}
对于运行时异常,如ArithmeticException,可以处理,也可以不处理
try{
System.out.println(100/0);
catch(ArithmeticException e){
System.out.println("算术异常");
}
总结:

几点注意事项:


总结:
😉 catch后,写确切的类型、写其父类型、父父类型也可以
try{
FileInputStream fis = new FileInputStream("D:\\not_exist.java");
//这里写的是FileNotFoundException的父类IO Exception
}catch(IO Exception e){
System.out.println("文件不存在");
}
😉 catch可以写多个,当这些异常父类一样时,直接写一个他们的父类也行,但建议精确catch,方便调试
try{
...
//出现异常时,从上往下匹配catch
}catch(xx e){
...
}catch(xxx e){
...
}catch(xxxx e){
...
}
😉 catch写多个的时候,从上到下,必须遵循从小到大,若父类异常在上,则catch匹配时,父类下的精确子类一定轮不上执行,会报错。
😉 JDK8中,允许使用或"|"来连接多个异常
try{
...
}catch(FileNotFoundException|ArithmeticException|NullPointerException e){
...
}
上抛和捕捉如何选择?
------
如果希望调用者去处理,则用throws抛给调用者,反之,自己处理则try…catch
Exception exception = new Exception();
1)getMessage()方法
//作用:获取异常的简单描述信息
String msg = exception.getMessage();
2)printStackTrace()方法
//作用:打印异常追踪的堆栈信息
exception.printStackTrace();
举例:
public class Exception2 {
public static void main (String[] args){
//异常的发生即new异常对象,那这行代码会导致程序终止运行吗?
//不会,这里只是new了一个异常对象,但并没有将异常对象throws抛出
//因此JVM会认为这是一个普通的Java对象
NullPointerException e = new NullPointerException("发生空指针异常");
String msg = e.getMessage();
//getMessage方法的输出,其实是用有参构造方法传入的那个String
System.out.println(msg);
e.printStackTrace();
System.out.println("Hello World!");
}
}
运行结果:
多次运行可以看到,Hello World一会儿在堆栈信息之上,有时在堆栈信息之下,这是因为Java打印异常堆栈信息追踪信息的时候,采用的是异步线程的方式。

Tip:
printStackTrace打印堆栈信息的结果也为红色,不是编译器出错了,如果是出错了,那下面的代码是不会执行的。错误分析时,从上往下看异常,下面的异常通常是上面的导致的。此外注意看包名,sun公司包后面的信息可以适当跳过。


将上面try语句块中的return;改为:
System.exit(0);
//再运行,则只输出try,finally中的语句不再执行
整理一个finally的坑,或许面试用得上:
public class Exception2 {
public static void main (String[] args){
int result = m();
System.out.println(result); //100
}
public static int m(){
int i = 100;
try{
return i;
}finally{
i++;
}
}
}
-----
//按照刚学的执行顺序,应该是先i++,再return,但这里是特殊
//使用反编译工具可以看到m()方法其实是这样:
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
SUN提供的JDK内置异常不够用,实际的开发中有的异常和业务挂钩,这种异常需要我们自己定义,自定义异常的步骤是:
public class MyException extends Exception{
public MyException(){
}
public MyException(String s){
super(s);
}
}
以下以数组模拟压栈弹栈为改良例子:
//之前的栈满是通过return+println来表达异常
public void push(Object obj){
if(this.index >= this.elements.length-1){
System.out.println("压栈失败,栈满");
return;
}
this.index++;
this.elements[index] = obj;
System.out.println("压栈"+obj+"成功,栈帧指向"+ index);
}
栈满是一种异常,我们用异常机制来改良:
/**
* 改良后的压栈方法
*/
public void push2(Object obj) throws MyStackOperationException {
if(index >= elements.length-1){
//创建异常对象,并将其抛出
MyStackOperationException e = new MyStackOperationException("栈满");
throw e;
//抛出异常后,因为这个自定义异常我写的是编译时异常,故要预处理
//这里选择throws,我定义异常的目的就是把栈满的异常信息传递出去
//自己定义再自己捕捉,一切都没意义了,所以在方法名定义的末尾加上throws
}
this.index++;
this.elements[index] = obj;
System.out.println("压栈"+obj+"成功,栈帧指向"+ index);
}
throw new MyStackOperationException("栈满");
用这种抛异常来代替return + println
注意throw和throws的区别:
覆盖那一章中提到:重写之后的方法,不能比重写之前的方法抛出更多或者更宽泛的异常,可以相等或者更少
class Animal{
public void doSome(){
}
public void doOther() throws Exception{
}
}
class Cat extends Animal{
//抛的更多了,error
public void doSome throws Exception{
}
//抛出更少,√
public void doOther(){
}
//抛出一样的,√
public void doOther2() throws Exception{
}
//抛出范围更小的,√
public void doOther() throws NullPointerException{
}
//抛出运行时异常,√
public void doOther() throws RuntimeException{
}
}