您的位置:首页 > 职场人生

java程序员从笨鸟到菜鸟之(三十三)异常之浅谈

2017-11-22 11:38 197 查看
本节讲解异常的分类,异常的处理机制(原理),异常的解决方案

由于本节是I/O流的前奏,会详细讲解相关内容

异常的引出

举例

 有一个人骑车去西藏旅游,可能会发生如下情况:

  1)在骑行的过程中,发现前方的山路坍塌了,导致无法骑行了

  2)在骑行,发现轮胎没气了

  3)在骑行的过程中,发现轮胎没气了

  4)山路两边是绝壁,中间是平坦的大道,但是这个人就喜欢在边上骑行,导致gameOver

分析:第一种情况属于不可抗力的因素,管不了;

            第二种情况在未出发时出现问题(异常),提醒我们去处理:骑行前应该处理问题;

            第三种情况最坑爹,问题没考虑全面:以后还是随身带个打气筒吧!!!

            第四种情况,no zuo no die!!!;设置防护机制(快到边上了,自动提醒吧!!!)

异常的分类

 异常的超类:Throwable

 两个具体实现的子类:Error、Exception

分析:

           第一种情况所属范畴:Error-----开发中:内存溢出! 

           举例:假设加载大量图片的时候---ImageLoader

           解决方案:开发团队,集体研究这个问题!

           第二种情况所属范畴:编译时异常 

       编译时期异常:只要不是RuntimeException中的异常都属于编译时期异常

       举例:IOException(IO流中的),ParseException(解析异常)

       出现的原因:要么语法有问题或者是Sun公司提供的一些类的中的方法(这个方法本身调用的时候就会异常存在),调用者必须处理,不处理编译不通过

       思考:类中方法的编译时期的异常,可能是历史遗留问题(不管出错不出错都会给予警示,意识问题)  ,按照我的想法是如果出错了, 给予异常提醒,不出错就正常编译;一个例子有助于理解这个问题:驾驶员遇到交警时(异常),驾驶员本身没错,只需要处理这个异常(接受交警的检查),异常处理后继续上路

         第三、四种情况:运行时异常            

    运行时期异常:RuntimeException

     出现原因:可能由于我们代码的逻辑不够严谨导致的问题,不够健壮!!!

     举例:NullPointerException---空指针异常!

    解决方案:考虑全面;这里需要个对象进行非空判断,来防止该问题的出现!

程序健壮性:程序在多数情况下能够正常运行,返回预期的正确结果;如果偶尔遇到异常情况,程序也能采取周到的解决措施。

传统异常处理方式与java异常处理方式比较

传统异常(面向过程)处理缺点

1)异常处理的能力有限,单靠方法的返回值难以表达异常情况的所有信息;eg:对于上班迟到(异常),相关的信息:迟到时间和原因不可知

2)异常流程的代码和正常流程的代码在一起,影响程序的可读性,容易增加程序结构的复杂性

3)系统规模不断扩大,这种传统的方式成为创建大型可维护应用程序的障碍

java异常处理机制优点

1)由于面向对象的思维,把不同情况的异常分类,得到异常类,可以发挥类的可扩展和可重用优势

2)异常流程的代码和正常流程的代码分离,提高程序可读性,简化了程序结构

3)灵活处理异常,如果当前方法有能力处理异常,就捕获并处理;否则就抛出异常,由方法调用者本身来处理

异常的处理方式1

 try-catch语句捕获异常的几种方式

方式1:程序中只有一种异常方式

try{
//try""{}方法体内容:可能出现异常情况的代码块
}catch (Exception e) {
// catch"()"中的内容:应给出执行异常代码块出现的异常类(声明)
// catch"{}"方法体内容:应该是处理异常的解决方案
}


实例1

package org.westos_02;

public class ExceptionDemo2 {

public static void main(String[] args) {

int a = 10 ;
int b = 0 ;

try {
System.out.println(a/b);
} catch (Exception e) {
//e.printStackTrace();
System.out.println("除数不能为0");
}

System.out.println("over");
}
}


方式2:如果有多个异常存在,如何
4000
处理呢?

处理方式(1):针对多个异常分别的try......catch

try {
//可能出现问题代码
} catch (ArithmeticException e) {
//异常处理
}
try{
//可能出现问题代码

}catch(ArrayIndexOutOfBoundsException e){
//异常处理

}
......


说明:在实际开发中,这种做法比较麻烦

处理方式(2):try...catch...catch

try{
//可能会出现问题的多个语句
}catch(异常类名1 变量名){
//输出语句处理
}catch(异常类名2 变量名){
//输出语句处理
......
}catch(异常类名n 变量名){
//输出语句处理
}
特点:类似switch-case语句,一旦与其中某一个异常类匹配就执行这个catch语句,就不再执行其它catch语句了

实例2

package org.westos_02;

public class ExceptionDemo3 {

public static void main(String[] args) {
method2();

}

private static void method2() {

int a = 10 ;
int b = 0 ;
int[] arr = {1,2,3} ;

try{
//可能出现问题的代码
System.out.println(arr[3]);
System.out.println(a/b);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("您访问了数组中不存在的索引");
}catch(ArithmeticException e){
System.out.println("除数不能玩为0");
}catch(Exception e){
System.out.println("程序可能出现问题了");
}

System.out.println("over");
}
}
为了简化类型,从jdk1.7允许一个catch子句中同时捕获多个不同的类型的异常
处理方式(3):try{...}catch(异常类名1 |异常类名2 ....变量名){...}
try{
//可能会出现问题的代码
//....
//...
}catch(异常类名1 | 异常类名2 ....变量名){
//处理异常...
}


注意事项:1)针对多个异常类名之间是一种平级关系

                     2)这种格式在实际开发中,虽然有多个异常,但是针对具体的异常给出具体的处理!
说明:“|”符号特点---两边都需要判断,如果不平级的话系统就不知道执行哪一个,出现错误

实例3

package org.westos_02;
public class ExceptionDemo4 {

public static void main(String[] args) {

//定义变量及数组
int a = 10 ;
int b = 0 ;
int[] arr = {1,2,3} ;

try{
System.out.println(a/b);
System.out.println(arr[3]);
}catch(ArithmeticException | ArrayIndexOutOfBoundsException  e){
System.out.println("程序出问题了...");
e.printStackTrace() ;
}

System.out.println("over");

}
}
说明:以上几个例子都是运行时的异常

上面我们提到过,对于编译时期的异常,Java程序必须给予处理,否则编译不通过(必须显示处理)

运行时期异常:可以处理,也可以像编译时期一样进行显示处理!
实例4   编译时异常

package org.westos_03;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo {

public static void main(String[] args) {

method() ;//ctrl+shift+M→抽取成一个方法
}

//调用parse()这个方法,本身就会抛出一个异常!
//注意:告诉调用者,注意了:我有问题,必须处理
private static void method()  {

//String日期"文本"格式---->Date格式:解析
String s = "2018-6-25" ;
//创建SimpleDataFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
//调用parse()方法
Date d;
try {
d = sdf.parse(s);//可能出现出现问题的代码
System.out.println("d:"+d);
} catch (ParseException e) {
System.out.println("解析出问题了...");
/**
* 为啥没执行catch的方法体呢?
* 原因:可能出现问题的代码没有出现问题,当然不执行catch语句了
*/
}
System.out.println("over");

}
}
异常中的常用方法:

  public String getMessage():消息字符串,出现错误原因
 public String toString():描述字符串

          (1)当前类的全路径名称(指的是当前异常类名所在的包的全路径名称)

          (2)": "(冒号和一个空格)消息字符串的内容

   public void printStackTrace():此方法一般返回三个信息------跟踪错误----直接在控制台打印(返回值是void)

          (1)描述字符串

          (2)使用哪个方法出现错误

          (3)代码中具体的错误出现第几行

                         说明:此方法从字面意思---打印跟踪方法调用栈获得详细异常信息

实例5   异常方法的描述

package org.westos_03;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo2 {
public static void main(String[] args) {

//解析日期的文本格式
String str = "2014-5-20" ;
//创建SimpleDataFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyy8y-MM-dd") ;

try {
Date date = sdf.parse(str) ;
/*
try语句中的代码一旦有问题,JVM就会抛出一个异常,和catch里面进行匹配,
如果一致,就会产生一个异常对象
换而言之,如果代码没有问题,就不会执行catch语句。产生异常对象
*/
System.out.println(2);//上面的代码一旦出现问题,就调转到catch语句中,寻找解决办法
} catch (ParseException e) {
//等价于:ParseException e = new ParseException() ;
e.printStackTrace();//相当于给出了页面:一般包含3个信息
System.out.println(e.getMessage());//消息字符串:错误原因
System.out.println(e.toString());     //将消息字符串转化成字符串
}
}
}
说明:关于方法的输出内容,最好自己手动编写,效果更好

异常处理第二种方式

   throws:抛出异常

   前面我们提到过,如果一个方法出现异常,但是此方法没有能力处理这种异常,可以在方法声明上抛出异常,让其他方法来解决。道理:问题出现了,自己解决不了,别人或许能帮你
注意:实际开发中,尽量的不要在main()方法抛出异常,在子方法中可以抛出异常

  面试题1:throws和throw的区别?

 

   throws:抛出

   后面跟的异常类名,可以跟多个异常类名,中间用逗号隔开

  throws在方法声明上抛出,表示异常的一种可能性,由调用者去处理

  throw:抛出

  后面跟的异常对象(匿名对象),只能跟具体的一个异常类的对象

  throw在方法中的语句中抛出,表示异常的绝对性,由方法中某些语句处理

  在实际开发中:throws要比throw用的比较多,而try...catch...又比throws用的比较多!
实例6 throw和throws区别与联系
package org.westos_03;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo3 {

public static void main(String[] args) throws ParseException,ArithmeticException{

//告诉调用者,抛出异常了,所以这里必须处理
method();//异常可以不处理(前提是没错)
/**
* 想说明问题:
* throw抛出的异常必须处理
* throws抛出的异常可以不用处理:
* {如果是运行时异常,必须处理;如果是编译时期异常,如果本身没有错误,只把异常抛出就Ok了}
*/
try {
method2();
} catch (Exception e) {
e.printStackTrace();
}
}

//throw
private static void method2() throws Exception {
int a = 10 ;
int b = 0 ;
if(b==0){
System.out.println(a/b);
throw new ArithmeticException() ;
//跟的异常对象,必须要处理的异常
}else{
System.out.println("不会出现问题");
}
}

private static void method() throws ParseException {
String str = "2017-11-19" ;

//创建SimpleDataFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;

//解析
Date d = sdf.parse(str) ;

System.out.println(d);
}
}

捕获异常的标准格式:try...catch...finally

对finally的几点说明:

   finally经常用在数据库中或者IO流中,用来释放资源的

   finally中的代码一定会执行

   补充:finally中的不执行只有一种情况----就是Jvm退出了---------->System.exit(0) ; 

   面试题2:final,finalize,和finally的区别?

   (1)final:字面意思:表示最终的,终态的意思,可以修饰类、方法、变量

   可以修饰类:类不能继承

  可以修饰成员方法:成员方法不能被重写(而不是不能被重载)

   可以修饰成员变量:此变量是一个自定义常量,不可修改;思考:方法中内部类的成员变量也是被final修饰,为什么可以修改

   (2)finalize:是一个方法,它的功能通过gc垃圾回收器回收不用的对象或者是变量;System.gc()的实质:调用的是重写了Object类中的finalize()方法,表示的是回收当前没有指向更多的引用对象或变量等等...

   (3)finally:不能单独使用,和try...catch...finally中一块使用,(应用场景:异常,IO以及数据库中中使用的),是用来释放资源的,finally中的代码一定会执行,并且除非Java虚拟机退出了,才不会执行!
实例7

package org.westos_04;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class FinallyDemo {
public static void main(String[] args) {

//String--->Date
String s = "2014-5-20" ;

//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;

Date d = null ;

try {
d = sdf.parse(s) ;
System.out.println("d:"+d);
} catch (ParseException e) {
//			e.printStackTrace();
System.out.println("解析出问题了...");
System.exit(0) ;//可能出现的异常没有出现,不走catch语句,直接走finally语句
}finally{
System.out.println("Jvm不退出,我就一定会执行...");
}

System.out.println("over");
}
}
衍生了一种新的异常处理规则:try代码块后面直接跟finally语句

作用域的问题:各自代码块中的变量只能在各自的代码块中使用,相当于局部变量

面试题3

   问题1:如果catch代码块中有return语句,那么finally中的代码还会执行吗?

           问题2:如果可以,是在return前执行还是return后执行?

   答:前面已经提到过,除非Jvm退出,不执行finally代码块;所以会执行,并且在return 前执行!

总结return语句用于退出方法,在执行try或catch代码块中的return语句时,如果有finally代码块,回先执行finally代码块

实例8

package org.westos_04;

public class FinallyDemo2 {

public static void main(String[] args) {
System.out.println(getInt());
}

/**
* 说明问题:
* (1)作用域问题
* (2)try或catch代码块中有return,而finally中无return时,执行次序
*
*/
private static int getInt() {
int a = 1
c2bd
0 ;
try{
System.out.println(a/0);//出现异常了
a = 20 ;
System.out.println("我是try代码块");
}catch(ArithmeticException e){
a = 30 ;
System.out.println("我是catch代码块");
return a ;//return:一个方法返回路径
}finally{
a = 40 ;
System.out.println("我是finally代码块");
}
return a;//(1):如果把这行注释掉,出现编译时的异常
}
}


通俗理解:当catch代码块中准备return时,发现finally没有执行,就回过头先执行finally代码块中的语句,再返回catch语句中执行return语句(前提是finally代码中没有return语句)

说明:上述打印结果,延伸出一个新的问题:try...catch...finally各自代码块执行情况
try:方法体中出现异常语句的后面都不会执行或者有return语句的情况下(在异常之前):执行return前的语句;如果没有异常,全部执行,在return前跳转到finally方法体

catch:try中出现异常后,方法体中在遇到return之前都会执行;try中无异常,不执行catch方法体

finally:return语句之前的或者(没有return)全部执行

实例9 finally代码块中有return语句情况

package org.westos_04;
public class FinallyDemo2 {

public static void main(String[] args) {
System.out.println(getInt());
}

private static int getInt() {
int a = 10 ;
try{
System.out.println(a/0);//出现异常了
a = 20 ;
System.out.println("我是try代码块");
}catch(ArithmeticException e){
a = 30 ;
System.out.println("我是catch代码块");
return a ;//return:一个方法返回路径
}finally{
a = 40 ;
System.out.println("我是finally代码块");
return a;
}
}
}
说明:与例8区别,finally有return语句

注意:代码出现了一个警告---Finally代码块未正常完成

警告原因:finally内不建议使用return,因为函数的执行过程是:try语句出现异常,在catch中对异常处理且调用了return前,执行finally中的代码,直接在finally代码块中return了,这与try...catch...finally最初设计相悖。最初设计中finally中只能放一些资源释放类的代码段,不能带return。会出现潜在的错误覆盖finally或者catch代码块中的return语句

补充:finally方法体中有return语句出现情况:丢失异常(后续补充)

链接:点击打开链接点击打开链接

下节:异常与继承关系,补充问题
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  异常 Exception try-catch
相关文章推荐