Java OOP——第五章 异常
1、 尝试通过if-else来解决异常问题:
Eg: public class Test2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); … System.out.print("请输入除数:"); int num2 = 0; if (in.hasNextInt()) { // 如果输入的除数是整数 num2 = in.nextInt(); if (0 == num2) { // 如果输入的除数是0 System.err.println("输入的除数是0,程序退出。"); System.exit(1); } } else { // 如果输入的除数不是整数 System.err.println("输入的除数不是整数,程序退出。"); System.exit(1); } … } } |
弊端: 1、代码臃肿 2、程序员要花很大精力“堵漏洞” 3、程序员很难堵住所有“漏洞”
|
java中用2种方法处理异常: 1、在发生异常的地方直接处理; 2、将异常抛给调用者,让调用者处理。 |
给代码添加异常处理:(alt+shift+z) 选择要处理的代码——右键:Surround with——try/catch Block |
2、异常机制:异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序;
●当出现程序无法控制的外部环境问题(用户提供的文件不存在,文件内容损坏,网络不可用...)时,JAVA就会用异常对象来描述。
3、异常处理:
Java编程语言使用异常处理机制为程序提供了错误处理的能力; |
Java的异常处理是通过5个关键字来实现的:try、catch、 finally、throw、throws |
●try-catch块: 程序运行产生异常时,将从异常发生点中断程序并向外抛出异常信息。
◆使用try-catch块捕获异常,分为三种情况:
★第一种情况 :正常 |
★第三种情况:异常类型不匹配; |
★第二种情况:出现异常: 异常是一种特殊的对象,类型为java.lang.Exception或其子类; |
printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程; |
●使用:
在catch块中处理异常: 1、加入用户自定义处理信息 System.err.println("出现错误:被除数和除数必须是整数,” +"除数不能为零。"); |
2、调用方法输出异常信息(获得异常的内存堆栈信息及错误的位置):e. printStackTrace(); 获得异常信息:e.getMessages(); |
||||||
异常对象常用的方法:
|
4、常见的异常类型:
异 常 类 型 |
说 明 |
Exception |
异常层次结构的父类 |
ArithmeticException |
算术错误情形,如以零作除数 |
ArrayIndexOutOfBoundsException |
数组下标越界 |
NullPointerException |
尝试访问 null 对象成员 |
ClassNotFoundException |
不能加载所需的类 |
IllegalArgumentException |
方法接收到非法参数 |
InputMismatchException |
欲得到的数据类型与实际输入的类型不匹配 |
ClassCastException |
对象强制类型转换出错 |
NumberFormatException |
数字格式转换异常,如把"abc"转换成数字 |
附加:
顶层是java.lang.Throwable类,检查性异常、运行期异常、错误都是这个类的子孙类,java.lang.Exception和java.lang.Error继承自java.lang.Throwable,而java.lang.RuntimeException继承自java.lang.Exception |
异常分类: 1、检查性异常(Checked异常:必须捕获或声明为抛出):java.lang.Exception 程序正确,但因为外在的环境条件不满足引发。例如:用户错误及I/O问题--程序试图打开一个并不存在的远程Socket端口,或者是打开不存在的文件时。这不是程序本身的逻辑错误,而很可能是远程机器名字错误(用户拼写错误),对商用软件系统,程序开发者必须考虑并处理这个问题。java编译器强制要求处理这类异常,如果不捕获这类异常,程序将不能被编译。 |
2、运行期异常(不要求必须捕获或声明抛出):java.lang.RuntimeException这意味着程序存在bug,如数组越界、0被除、入参不满足规范...这类异常需要更改程序来避免,java编译器强制要求处理这类异常。 |
3、错误:java.lang.Error 一般很少见,也很难通过程序解决,它可能源于程序的bug,但一般更可能源于环境问题,如内存耗尽。错误在程序中无需处理,而由运行环境处理。 |
5、try-catch-finally:在try-catch块后加入finally块,是否发生异常都执行;
●不执行的一种情况: |
★存在return的try-catch-finally块: |
●多重catch块:多重catch块的排序顺序必须是从子类到父类,最后一个一般都是Exception; 引发多种类型的异常 排列catch 语句的顺序:先子类后父类 发生异常时按顺序逐个匹配 只执行第一个与异常类型匹配的catch语句 |
|
如果把finally块置try...catch...语句后,finally块一般都会得到执行,它相当于一个万能的保险,即使前面的try块发生异常,而又没有对应异常的catch块,finally块将马上执行。 以下情形,finally块将不会被执行: 1、finally块中发生了异常; 2、程序所在的线程死亡; 3、在前面的代码中用了System.exit();//中断程序,退出Java虚拟机 4、关闭CPU //异常示例 import java.io.*; import java.net.*; public class Demo148 { public static void main(String[] args) { FileReader fr=null; //检查异常 //1、打开不存在的文件 //FileReader fr=new FileReader("d:\\aa.txt"); try { //使用try{}catch(Exception e){}将可能出错的程序放入到里面,当出错时会有相应提示,便于解决bug //在出现异常的地方就终止执行代码,然后直接进入到catch语句 //如里有多个catch语句,则进入匹配异常的catch语句输入出信息 fr=new FileReader("d:\\aa.txt"); //System.exit(-1); //使用System.exit()后finally语句块不再执行 Socket s=new Socket("192.168.1.1",21); } catch (FileNotFoundException e) {//catch(Exception e)捕获所有错误信息,为了方便一般使用此方法来捕获所有错误信息 // 把异常的信息输出,利于排除bug //e.getMessage(); System.out.println("文件不存在:"+e.getMessage());//.getMessage()不如.printStackTrace() //e.printStackTrace(); //输出bug信息 //处理 } catch (IOException e2){//UnknownHostException e2.printStackTrace(); } finally { //try..catch..语句块中不管出没出现异常,一般都会执行finally语句块 //一般说,把需要关闭的资源。如[文件]、[链接]、[内存]... System.out.println("测试进入finally语句块"); if(fr!=null){ try { fr.close(); } catch (Exception e) { e.printStackTrace(); } } } System.out.println("OK1"); |
6、声名异常(throws)与抛出异常(throw):
声明异常:throws |
抛出异常:throw |
|
throws:使用: eg1:由调用者处理异常: Eg2:由调用者继续声明异常: |
||
throw的使用:性别是由调用者处理异常:在main方法中不加try处理会报错; |
测试类: 结果: |
|
异常的分类: |
多个异常的处理规则: ●定义多个catch可精确地定位异常。如果为子类的异常定义了特殊的catch块,而父类的异常则放在另外一个catch块中,此时,必需满足以下规则: ●子类异常的处理块必需在父类异常处理块的前面,否则会发生编译错误。所以越特殊的异常越在前面处理,越普通的异常越在后面处理。这类似于制订防火墙的规则次序:较特殊的规则在前,较普通的规则在后。 |
|
throws和throw的区别: 1、作用不同:throw用于在程序中抛出异常,throws用于生命在该方法内抛出了异常; 2、使用的位置不同:throw位于方法体内部,可以作为单独语句使用; throws必须跟在方法参数列表的后面不能单独使用; 3、内容不同:throw抛出一个异常对象,而且只能是一个; throws后面可以跟多个异常类; |
7、log4j记录日志:
日志(log): 主要用来记录系统运行中一些重要操作信息 便于监视系统运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到原因 |
日志分类: SQL日志:记录系统执行的SQL语句; 异常日志:记录系统运行中发生的异常事件; 业务日志:记录系统运行过程,如用户登陆,操作记录; |
log4j是一个非常优秀的开源日志记录工具 控制日志的输出级别 控制日志信息输送的目的地是控制台、文件等 控制每一条日志的输出格式 |
使用log4j记录日志步骤: ★在项目中加入log4j所使用的JAR文件;(导入架包:项目(右击)—文件(properties)— ★创建log4j.properties文件(项目(右击)—新建(new)—文件(File)—输入文件名 ★编写log4j.properties文件,配置日志信息(文件上右键: ★在程序中使用log4j记录日志信息(在文件中使用); |
Log4j配置文件: 1、输出级别:日志记录器输出级别:fatal > error > warn > info >debug log4j.rootLogger=debug, stdout,logfile ●debug指的是日志记录器(Logger)的输出级别,主要输出级别及含义如下: ◆fatal:指出严重的错误事件将会导致应用程序的退出; ◆error:指出虽然发生错误事件,但仍不会影响系统的继续运行; ◆warn:表明会出现潜在的错误的形式; ◆info:在粗粒程度级别上指明消息,强调应用程序的运行过程; ◆debuj:指出细粒度信息事件,对调试应用程序时非常有帮助的; ▲日志记录器各个级别输出级别:fatal > error > warn > info >debug 2、日志输出的目的地Appender: log4j.rootLogger=debug, stdout,logfile ●stdout,logfile指的是日志输出目的地的名字; log4j允许记录日志到多个输出目的地,一个输出目的地被称为一个Adooender。 log4j中最常用的Appender有以下两种: ◆ConsoleAppender:输出日志事件到控制台。通过Target属性配置输出到System.out或System.err,默认的目标是System.out; Eg:log4j.appender.stdout=org.apache.log4j.ConsoleAppender;//日志信息输出到控制台 log4j.appender.stdout.Target=System.err;//信息打印到System.err上 ◆FileAppender:输出日志到一个文件, Eg:log4j.appender.logfile=org.apache.log4j.FileAppender;//日志信息写到文件中 3、日志布局类型Layout: log4j.appender.logfile.layout=org.apache.log4j.PatternLayout ●Appenderb必须使用一个与之相关联的布局类型Layout,用来指定它的输出样式。 Log4j中最常用的Layout有以下三种: ◆HTMLLayout:格式化日志输出为HTML表格 ◆SimpleLayout:以一种非常简单的方式格式化日志输出,它输出级别Level,然后跟着一个破折号“——”,最后是日志信息。 ◆PatternLayout:根据指定的转换模式格式化日志输出,从而支持丰富多样的输出格式。需要配置layout.ConversionPattern属性,若没有配置该属性,则使用默认的转换模式 4、转换模式ConversionPattern: log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n ●对于PatternLayout,需要配置layout.Conversionpattern属性,常用的配置参数及含义如下: ◆%d:用来设置输出日志的日期和时间,默认格式为ISO8601。也可以在其后指定格式。 比如:%d{yyyy-MM-dd HH:mm:ss},输出格式类似于2016-03-09 17:51:08 ◆%m:用来输出代码中指定的消息。 ◆%n:用来输出一个回车换行符 ◆%I:用来输出日志时间的发生位置,包括类名、发生的线程,以及在代码中的行数。 例如:如果输出位cn.jbit.log.Test11.main(Test11.java:21),则说明日志事件发生在cn.jbit.log包下的Test11类的main线程中,在代码中的行数为第21行。 ◆%p:用来输出优先级,及debug、info、warn、error、fatal等。 ◆%F:用来输出文件名 ◆%M:用来输出方法名 |
Eg: ### 设置Logger输出级别和输出目的地 ### log4j.rootLogger=debug, stdout,logfile //目的地的名字和目的地的名字 ### 把日志信息输出到控制台 ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender //日志信息输出到控制台 log4j.appender.stdout.Target=System.err //信息打印到System.err上 log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout //指定日志布局类型 ### 把日志信息输出到文件:jbit.log ### log4j.appender.logfile=org.apache.log4j.FileAppender //日志信息写到文件中 log4j.appender.logfile.File=jbit.log //指定日志输出的文件名 log4j.appender.logfile.layout=org.apache.log4j.PatternLayout //指定转换模式 log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n //指定日志布局类型 |
●解决文件乱码的问题: 1、properties 文件中文乱码问题 :鼠标“右击”文件 => Resource => Text file encoding => UTF-8 ; 2、properties 文件解析中文乱码问题 |
9、附加:
自己也可以定义并抛出异常,方法2步: ●创建异常,抛出异常(首先实例化一个异常对象,然后用throw抛出)合在一起就是---throw new IOException("异常说明信息"),将创建异常,抛出异常合在一起的好处是:创建异常时,会包含异常创建处的行信息,异常被捕获时可以通过堆栈迹(Stack Trace)的形式报告这些信息。如果在同一行代码创建和抛出异常时,对于程序的调试将非常有用。所以,throw new XXX()已经成为一个标准的异常抛出范式。 ●在定义一个方法时,方法块中调用的方法可能会抛出异常,可用上面的throw new XXX()处理,如果不处理,那么必需在方法定义时,用throws声明这个方法全抛出的异常。 |
|
对异常的处理,有一条行之有效的默认规则: 向上抛出----被调用类在运行过程中对遇到的异常一概不作处理,而是直接向上抛出,一直到最上层的调用类,调用类根据应用系统的需求和特定的异常处理规则进行处理,如向控制台输出异常堆栈信息,打印在日志文件中。用一句形象的话来说,就是谁使用,谁(最上层的调用类)处理。 |
|
●一个try语句块后面可以写多个catch语句块,分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般都是Exception类。 |
|
●Java的Exception分两大类运行时异常和非运行时异常(编译异常): ◆运行时异常: ★Java的非检查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。Java编译器不要求你一定要把它捕获或者一定要继续抛出, ▲运行时异常:运行时异常都是RuntimeException类及其子类异常: ■NullPointerException(空指针异常) ■ArrayIndexOutOfBoundsException(数组下标越界异常) ■ClassCastException(对象强制类型转换出错) ■ArithmeticException(算术错误) ■NumberFormatException(数字格式转换异常) ■java.lang.ArithmeticException类是RuntimeException的子类,当出现异常的运算条件时,将会抛出java.lang.ArithmeticException异常。 ●这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。 ◆非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过(对checked exception(检查异常)要求你必须要在方法里面或者捕获或者继续抛出。)。如IOException、SQLException 、ClassNotFoundException等以及用户自定义的Exception异常,一般情况下不自定义检查异常; |
|
●NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常。 ●这种情况包括:调用 null 对象的实例方法。 访问或修改 null 对象的字段。 将 null 作为一个数组,获得其长度。 将 null 作为一个数组,访问或修改其时间片。 将 null 作为 Throwable 值抛出。 |
|
●逻辑错误:是指代码逻辑问题 语法错误:是指代码的通用性不规范 内部错误:是指程序中错误,所以只能通过代码内部调试处理 运行时错误:是通过异常处理模型来完成 |
|
●Java中可能出现运行异常的代码最佳的处理时期就是在编译的时候处理并且捕获。 |
|
●提供两种异常处理机制:捕获异常和声明抛弃异常: 1)捕获异常:在Java程序运行过程中系统得到一个异常对象是,它将会沿着方法的调用栈逐层回溯,寻找处理这一异常的代码。找到能够处理这种类型异常的方法后,运行时系统把当前异常交给这个方法处理;如果找不到可以捕获异常的方法,则运行时系统将终止,相应的Java程序也将退出。捕获异常是通过try-catch-finally语句实现的。 2)声明抛弃异常:当Java程序运行时系统得到一个异常对象时,如果一个方法并不知道如何处理所出现的异常,则可在方法声明时,声明抛弃异常。声明抛弃异常是在一个方法声明中的throws子句中指明的。 |
|
●自定义异常可以继承Exception或Throwable 类; Java中,所有的异常都有一个共同的祖先 Throwable。 Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。 它有两个重要的子类:Exception(异常)和 Error(错误), 二者都是 Java 异常处理的重要子类,各自都包含大量子类。 |
|
●Java异常的基类为java.lang.Throwable,java.lang.Error和java.lang.Exception继承 Throwable,RuntimeException和其它的Exception等继承Exception,具体的RuntimeException继承RuntimeException。 ◆扩展:错误和异常的区别(Error vs Exception) 1) java.lang.Error: Throwable的子类,用于标记严重错误。合理的应用程序不应该去try/catch这种错误。绝大多数的错误都是非正常的,就根本不该出现的。 java.lang.Exception: Throwable的子类,用于指示一种合理的程序想去catch的条件。即它仅仅是一种程序运行条件,而非严重错误,并且鼓励用户程序去catch它。 2) Error和RuntimeException 及其子类都是未检查的异常(unchecked exceptions),而所有其他的Exception类都是检查了的异常(checked exceptions). ★checked exceptions: 通常是从一个可以恢复的程序中抛出来的,并且最好能够从这种异常中使用程序恢复。 比如FileNotFoundException, ParseException等。检查了的异常发生在编译阶段,必须要使用try…catch(或者throws)否则编译不通过。 ★unchecked exceptions: 通常是如果一切正常的话本不该发生的异常,但是的确发生了。发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的。 比如ArrayIndexOutOfBoundException, ClassCastException等。从语言本身的角度讲,程序不该去catch这类异常,虽然能够从诸如RuntimeException这样的异常中catch并恢复,但是并不鼓励终端程序员这么做,因为完全没要必要。因为这类错误本身就是bug,应该被修复,出现此类错误时程序就应该立即停止执行。 因此,面对Errors和unchecked exceptions应该让程序自动终止执行,程序员不该做诸如try/catch这样的事情,而是应该查明原因,修改代码逻辑。 ★RuntimeException:RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等。处理RuntimeException的原则是:如果出现 RuntimeException,那么一定是程序员的错误。 例如,可以通过检查数组下标和数组边界来避免数组越界访问异常。其他(IOException 是文件读取流异常等等)checked异常一般是外部错误,例如试图从文件尾后读取数据等,这并不是程序本身的错误,而是在应用环境中出现的外部错误 |
|
●Java中,异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。 异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。 |
|
●异常类有很多分类,父类是Exception. 子类:数组越界(ArrayIndexOutOfBoundsException) 空指针异常(NullPointerExceptoin), (ArthmeticException)算术异常,如零作除数, (ClassNotFoundException)不能加载所需的类, (ClassCastException)对象强制转换出错 , 这些都是特定情况下出现的异常。当有多个catch时,前面的catch的异常是子异常,最后一般是异常的父类exception,出现什么异常执行对应异常块,没有匹配的异常,执行父类异常块。 |
|
●Java的try-catch-finally语句中,关键词try后的一对大括号将一块可能发生异常的代码包起来,称为监控区域。 Java方法在运行过程中出现异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常。 若有匹配的catch子句,则运行其异常处理代码,try-catch语句结束。 如果try块中代码量太多,过于庞大,会提高程序的复杂度。如果程序发生异常,排查错误,分析问题代码会需要较长的时间; |
|
●Java中,Throwable类的printStackTrace()方法可以将此 throwable 及其追踪输出到指定的 PrintWriter。 |
|
●Java中,执行try-catch-finally语句需要注意: 第一:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回) 第二:finally里面不建议放return语句,根据需要,return语句可以放在try和catch里面和函数的最后。可行的做法有四种: 1)return语句只在方法最后出现一次。 2)return语句仅在try和catch里面都出现。 3)return语句仅在try和方法最后都出现。 4)return语句仅在catch和方法的最后都出现。 注意,除此之外的其他做法都是不可行的,编译器会报错。 (1)如果程序运行到try成功时可以返回结果,则采用方法2。 (2)如果程序运行到catch时(即中途出错时)无需再继续执行后面的代码了,则采取方法4。 (3)如果程序运行到try或catch时还需要继续执行后面的代码,则采取方法1 |
|
●Java的异常处理机制中,throws语句用在方法定义时声明该方法要抛出的异常类型, 如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。使用throws关键字将异常抛给调用者后,调用者必须将throws声明有异常的方法放置在try-catch语句中调用,在catch块中要指定抛出的异常类或其父类,以便捕捉程序中可能出现的这种类型的异常,并处理。 如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。 |
|
●Java的异常处理机制中,throws语句用在方法定义时声明该方法要抛出的异常类型, 如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。 使用throws关键字将异常抛给调用者后,调用者必须将throws声明有异常的方法放置在try-catch语句中调用,在catch块中要指定抛出的异常类或其父类,以便捕捉程序中可能出现的这种类型的异常,并处理。 如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。 |
|
●Try是正常执行代码,catch是try中代码块出现异常时被执行的代码,finally是代码正常执行后或异常执行后都要去执行的代码块,try-catch-finally结构中try是必须的,catch和finally均为可选,但两者至少要出现一个。 ●一个try语句块后面可以写多个catch语句块,分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般都是Exception类。 |
|
●eg: 分析:在静态方法main()中,类的非静态成员方法不能直接调用,必须先创建对象,再使用对象名访问。正确答案是A。 |
|
●Java异常分为check Exception还是uncheck Exception。 uncheck exception表明调用方无需捕获处理该异常,一般是与运行时无法的错误,可能是调用方传递参数的问题,也有可能是虚拟机的问题,反正开发者不应该关注的异常。 check exception表明调用方必须捕获并处理该异常,一般是与运行时有关的错误,例如IO未准备好,文件不存在等,需要开发者关注的异常。 如果不想处理check exception,也可以在自己的方法里直接声明抛出异常throws XXXException即可, |
|
●throw是语句抛出一个异常。 语法:throw (异常对象); throw e; ●throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常) 语法:[(修饰符)](返回值类型)(方法名)([参数列表])[throws(异常类)]{......} public void doA(int a) throws Exception1,Exception3{......} |
|
●log4j日志: |
%d:用来设置输出日志的日期和时间 %m:用来输出代码中指定的消息 %n:用来输出一个回车换行符 %l:用来输出日志事件的发生位置 %p:用来输出优先级 %f:用来输出文件名 %m:用来输出方法名 %t: 输出产生该日志事件的线程名,
|