异常与日志

异常

本章技能目标

  • 熟悉使用try-catch-finally处理异常
  • 会使用throw、throws抛出异常
  • 掌握异常及其分类
  • 使用log4j记录日志

1. 异常概述

异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。

程序错误分为三种:1.编译错误;2.运行时错误;3.逻辑错误。

  1. 编译错误是因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
  2. 运行时错误是因为程序在执行时,运行环境发现了不能执行的操作。
  3. 逻辑错误是因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制

2. 异常处理

异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

Java的异常处理是通过5个关键字来实现的:try、catch、finally、throw和throws。

2.1 try-catch块

try-catch程序块的执行流程比较简单,首先执行的是try语句块中的语句,这时可能会有以下三种情况:

  • 如果try语句块中所有的语句正常执行完毕,不会发生异常,那么catch块中的所有语句都将会被忽略
  • 如果try语句块在执行过程中遇到异常,并且这个异常与catch中声明的异常类型相匹配,那么在try块中其余剩下的代码都将忽略
  • 如果try语句块在执行过程中遇到异常,而抛出的异常在catch块里面没有被声明,那么程序立刻退出。

资料:

如果try块在执行过程中遇到异常,那么在try块中其余剩下的代码都将被忽略,系统会自动生成相应的异常对象,包括异常的类型、异常出现时程序的运行状态及对该异常的详细描述。如果这个异常对象与catch中声明的异常类型相匹配,则会把该异常对象赋给后面的异常参数,相应的catch块将会被执行。

异常对象常用的方法主要有以下两种:

  • void printStackTrance():输出异常的堆栈信息。堆栈信息包括运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用序列
  • String getMessage():返回异常信息描述字符串。该字符串描述异常产生的原因,是printStackTrance()方法输出信息的一部分

2.2 try-catch-finally块

try-catch语句块后加入finally块,把需要执行释放资源的语句放入finally块。它表示无论是否出现异常,都应当执行的内容。try-catch-finally语句的一般语法形式为:

try {  
	// 可能会发生异常的程序代码  
} catch (Type1 id1){  
	// 捕获并处置try抛出的异常类型Type1  
} catch (Type2 id2){  
	 //捕获并处置try抛出的异常类型Type2  
}finally {  
	// 无论是否发生异常,都将执行的语句块  
} 

try、catch、finally语句块的执行顺序:

  1. 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句
  2. 当try捕获到异常,catch语句块里没有处理此异常的情况:此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
  3. 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。

注意:

finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:

  1. 在finally语句块中发生了异常。
  2. 在前面的代码中用了System.exit()退出程序。
  3. 程序所在的线程死亡。
  4. 关闭CPU。

2.3 多重catch块

一段代码可能会引发多种类型的异常,这是可以在一个try语句块后面跟多个catch语句块,分别处理不同的异常。但排列顺序必须是从子类到父类,最后一个一般是是Exception类。因为所有的异常子类都继承自Exception类,所以如果将父类异常放到前面,那么所有的异常都将被捕获,后面catch块中的子类将得不到被执行的机会。

2.4 声明异常—throws

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。

throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。

throws语句的语法格式为:

 methodname() throws Exception1,Exception2,..,ExceptionN  { 
 }  

方法名后的throws Exception1,Exception2,…,ExceptionN 为声明要抛出的异常列表。当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理。

使用throws关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。

Throws抛出异常的规则:

  1. 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  2. 如果一个方法可能出现可查异常(checked exception),要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
  3. 只有当抛出了异常时,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
  4. 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

3. 抛出异常

3.1 抛出异常

throw总是出现在方法体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:

throw new exceptionname("exception");

注意,throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。

如果抛出了可查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

throw和throws的区别表现在以下三个方面:

  • 作用不同:throw用于在程序中抛出异常;throws用于声明在方法内抛出了异常。
  • 使用的位置不同:throw位于方法体内部,可以作为单独语句使用;throws必须跟在方法参数列表的后面,不能单独使用。
  • 内容不同:throw抛出一个异常对象,而且只能是一个;throws后面跟异常类,而且可以跟多个异常类。

3.2 异常的分类

Java中的异常体系包括许多异常类,它们之间存在继承关系。Java的异常体系结构如图所示:

在这里插入图片描述

异常体系:

  • Throwable: Throwable类是java语言中所有错误或异常的超类,它派生两个子类:即 Error和Exception

  • Error类:表示仅靠程序本身无法恢复的严重错误,如内存溢出动态链接失败、虚拟机错误。应用程序不应该抛出这种类型的对象(一般是由虚拟机抛出)。如图所示:在这里插入图片描述

    假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以在程序设计时,应该更关注Exception类。

  • Exception类:由Java应用程序抛出和处理的非严重的错误,如所需文件找不到、网络连接不同或中断、算术运算出错(如被0除)、数组下标越界、装载了一个不存在的类、对null对象操作、类型转换异常等。它的各种不同的子类分别对应不同类型的异常。

  • 运行时异常:包括RuntimeException及其所有的子类,不要求程序必须对他们做出处理。如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)、ClassCastException(类型强制转换异常)、IllegalArgumentException(参数异常)、,ClassNotFoundException(找不到类异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

  • 非运行时异常 (编译异常):除了Exception中的RuntimeException及RuntimeException的子类以外,其他的Exception类及其子类(例如:IOException和ClassNotFoundException)都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

4. 开源日志记录工具log4j

4.1 日志以及分类

软件的运行过程离不开日志,日志主要用来记录系统运行过程中的一些重要的操作信息,便于监视系统 运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到发生的原因。

日志根据记录内容的不同,主要分为三类:

  • SQL日志:记录系统执行的sql语句。
  • 异常日志:记录系统运行中发生的异常事件
  • 业务日志:记录系统运行过程,如用户登录,操作记录。

log4j是一个非常优秀的日志记录工具。

通过log4j我们可以控制日志的输出级别,以及日志信息输送的目的地(如控制台、文件等),还可以控制每一条日志的输出格式。

log4j 是Apache的一个开源项目,官方网站是:http://logging.apache.org/log4j/1.2/,这里使用log4j 1.2.17版本,当前下载地址是:http://logging.apache.org/log4j/1.2/download.html,下载其中的zip文件并解压,里面包含的主要内容以及在zip包内的路径如下。

  • log4j 的jar包:apache–log4j–1.2.17\log4j–1.2.17.jar
  • 使用手册(manual):apache–log4j–1.2.17\site\manual.html
  • javadoc(APIDocs): apache–log4j–1.2.17\site\apidocs\index.html

4.2 如何使用log4j记录日志

  1. 在项目中加入log4j所使用的jar文件。

  2. 在项目的src目录下,创建log4j.properties 文件

  3. 编写log4j.properties文件,配置日志信息

    常用配置如下:

    ### 设置Logger输出级别和输出目的地 ### trace/debug/info/warn/error/,这个是输出的级别
    log4j.rootLogger=debug, console,logfile
    ###  debug表示最低的输出级别,更改此处,可以过滤日志输出的类型,
    
    ### console 表示输出到那个位置,此处表示输出到控制台,
    ### logfile 表示输出到文件,
    
    ### 类似console 和logfile可以有好几个,还可以写到数据库
    
    
    ### 把日志信息输出到控制台 ###
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target=System.out
    log4j.appender.console.layout=org.apache.log4j.SimpleLayout
    # log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss}%p %m%n
    
    ####这里的p  m  n等符号都有特定含义,可查看手册
    
    
    ### 把日志信息输出到文件:test.log ###
    log4j.appender.logfile=org.apache.log4j.FileAppender
    # 此处表示日志文件存储的文件名,可在项目刷新后进行查看test文件
    log4j.appender.logfile.File=test.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
    
  4. 在程序中记录日志信息。

    首先需要创建一个私有静态的Logger对象,然后可以通过它的debug()或者error() 等方法输出日志信息了。

    编写TestLog.java,代码如下:

    public class TestLog {
    	private int age;
    
    	private static Logger log = Logger.getLogger(TestLog.class);
    	public static void main(String[] args) {
    		log.warn("构建对象");
    		TestLog test = new TestLog();
    		log.info("开始调用方法");
    		try {
    			test.setAge(-1);
    		}catch(Exception e) {
    			log.debug(e.getMessage());
    		}
    		log.error("调用结束");
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		if(age > 100 || age < 0) {
    			throw new RuntimeException("年龄必须在0-100之间");
    		}
    		this.age = age;
    	}
    
    }
    

4.3 log4j配置文件

log4j配置文件中配置信息详细解释如下:

  1. 输出级别

    log4j.rootLogger=debug, console,logfile
    

    其中,dubug指的是日志记录器(Logger)的输出级别,主要输出级别及含义如下:

    • fatal: 指出严重的错误事件将会导致应用程序的退出 (0)
    • error:指出虽然发生错误事件,但仍然不影响系统的继续运行
    • warn:表明会出现潜在错误的情形
    • info: 在粗粒度级别上指明消息,强调应用程序的运行过程
    • debug:指出细粒度信息事件,对调试应用程序是非常有帮助的
    • trace:跟踪,与debug同级别

    各个输出级别优先级:

    fatal > error > warn > info > debug

  2. 日志输出的目的地 Appender

    log4j.rootLogger=debug,console,logfile
    

    其中,debug、logfile指的是日志输出目的地的名字。

    log4j允许记录日志到多个输出的目的地,一个输出目的地被称为一个Appender。Log4j中最常用的Appender有以下两种:

    • ConsoleAppender:输出日志事件到控制台。通过Target属性配置输出到System.out或System.err,默认目标是System.out
    • FileAppender:输出日志事件到一个文件。通过File属性配置文件的路径以及名称。

    如上的配置文件中共有两个Appender,第一个命名为console,使用了ConsoleAppender,通过配置Target属性,把日志信息写到控制台System.out;第二个命名为logfile,使用了FileAppender,通过配置File属性,把日志信息写到指定的文件test.log中。

  3. 日志布局类型

    Appender必须使用一个与之相关联的布局类型Layout,用来指定它的输出样式。log4j中最常用的Layout有以下三种:

    • HTMLLayout:格式日志输出为HTML表格
    • SimpleLayout:以一种非常简单的方式格式化日志输出,它输出级别Level,然后跟着一个破折号“—”,最后是日志信息。
    • PatternLayout:根据指定的转换模式格式化日志输出,从而支持丰富多样的输出格式。需要配置layout.ConversionPattern属性,若没有配置该属性,则使用默认的转换模式。

    上述配置文件中的第一个Appender是console,使用了SimpleLayout;第二个Appender是logfile,使用了PatternLayout,需要配置layout.ConversionPattern属性来自定义输出格式。

  4. 转换模式ConversionPattern

    对应PatternLayout,需要配置layout.ConversionPattern属性,常用的配置参数以及含义如下:

    • %d 设置输出日志的日期和时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss },输出类似:2002-10-18- 22:10:28
    • %m 输出代码中指定的信息,如log(message)中的message
    • %n 当前平台下的换行符
    • %l 输出日志事件的发生位置,即输出日志信息的语句处于它所在的类的第几行
    • %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL。如果是调用debug()输出的,则为DEBUG,依此类推
    • %F 输出日志信息所属的类的类名
    • %M 用来输出方法名

5. 自定义异常

5.1 自定义异常的意义

Java异常机制可以保证程序更安全和更健壮。虽然Java类库已经提供很多可以直接处理异常的类,但是有时候为了更加精准地捕获和处理异常以呈现更好的用户体验,需要开发者自定义异常。

5.2 继承Exception自定义异常

创建自定义异常类,语法格式:

public class [自定义异常类名] extends Exception{

}

示例代码如下:

public class GenderException extends Exception{
	public GenderException(String message) {
		super(message);
	}
}

测试代码如下:

class Person {
	private String name = ""; // 姓名
	private int age = 0; // 年龄
	private String sex = "男"; // 性别
	// 设置性别

	public void setSex(String sex) throws GenderException {
		if ("男".equals(sex) || "女".equals(sex))
			this.sex = sex;
		else {
			throw new GenderException("性别必须是“男”或者“女”!");
		}
	}

	// 打印基本信息
	public void print() {
		System.out.println(this.name + "(" + this.sex + "," + this.age + "岁)");
	}
}

public class Test {
	public static void main(String[] args) {
		Person person = new Person();
		try {
			person.setSex("Male");
			person.print();
		} catch (GenderException e) {
			e.printStackTrace();
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值