异常
在生活中,异常(exception)情况随时都有可能发生。以上下班为例,在正常情况下,小明每日开车去上班,耗时大约30分钟。但是,由于车多、人多、路窄,异常情况很有可能发生。有时会遇上比较严重得堵车,这种情况下,小明往往很晚才能到公司,这种异常虽然偶尔才会发生,但是真的发生也是件极其麻烦的事情。
在程序中,异常就是在程序的运行过程中所发生的不正常的事件,如所需文件找不到、数组下标越界等等,异常会中断正在运行的程序。
异常处理
异常处理机制就像我们对平时可能会遇到的意外情况,预先想好了一些处理的方法。也就是说,在程序执行代码的时候,万一发生了异常,程序会按照预定的处理办法对异常进行处理,异常处理完毕之后,程序继续运行。
Java的异常处理是通过五个关键字来实现的:try、catch、finally、throw、throws。
try-catch块
import java.util.Scanner;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
try { //里面含有可能出现异常的代码
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println(String.format("%d / %d = %d",num1,num2,num1/num2));
System.out.println("感谢使用本程序!");
} catch (Exception e) { //捕获异常
System.err.println("输入的被除数不是整数,除数不能为零");
e.printStackTrace();
}
}
- 如果try块中所有语句正常执行完毕,不会发生异常,那么catch块中的所有语句都将会被忽略。
- 如果try语句块在执行过程中遇到异常,并且这个异常与catch中声明的异常类型相匹配,那么try块中其余剩下的代码都将被忽略。
- 如果try语句块在执行过程中遇到异常,而抛出的异常在catch块里面没有被声明,那么程序立刻退出。
在catch块中可以加入用户自定义处理信息,也可以调用异常对象的方法输出异常信息,常用的方法主要有两种:
- void printStackTrace():输出异常的堆栈信息。堆栈信息包括程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用序列。
- String Message():返回异常信息描述字符串。该字符串描述异常产生的原因,是printStackTrace()方法输出信息的一部分。
常见的异常类型
异常 | 说明 |
---|---|
Exception | 异常层次结构的根类 |
ArithmeticException | 算术错误情形,如以0做除数 |
ArrayIndexOutOfBoundsException | 数组下标越界 |
NullPointException | 尝试访问null对象成员 |
ClassNotFoundException | 不能加载所需的类 |
InputMismatchException | 数据类型不匹配 |
IllegalArgumentException | 方法接收到非法参数 |
ClassCastException | 对象强制类型转换出错 |
NumberFormatException | 数字格式转换异常,如把“abc”转换为数字 |
try-catch-finally块
import java.util.Scanner;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println(String.format("%d / %d = %d",num1,num2,num1/num2));
} catch (Exception e) {
System.err.println("输入的被除数不是整数,除数不能为零");
e.printStackTrace();
}finally { //无论是否发生异常,finally块中的代码总能被执行
System.out.println("感谢使用本程序!");
}
}
- 如果try块中所有语句正常执行完毕,那么finally块就会被执行。
- 如果try语句在执行过程中碰到异常,无论这种异常是否被catch块捕获到,都将执行finally块中的代码。
try-catch-finally结构中try块是必须的,catch和finally块为可选,但两者至少必须出现其中之一。
注意:
即使在try块和catch块中存在return语句,finally块中语句也会被执行。发生异常时的执行顺序:执行try块或catch块中return之前的语句,执行finally块中的语句,执行try块或catch中的return语句退出。
多重catch块
一段代码可能会引发多种类型的异常,这时,可以在一个try语句块后面跟多个catch块,分别处理不同的异常。但排序顺序必须是从子类到父类,最后一个一般都是Exception类。因为所有异常子类都继承自Exception类,所以如果将父类异常放到前面,那么所有的异常都将被捕获,后面catch块中的子类异常将得不到被执行的机会。
当运行时,系统从上到下分别对每个catch语句块处理的异常类型进行检测,并执行第一个与异常类型匹配的catch语句。执行其中的一条catch语句之后,其后的catch语句都将被忽略。
import java.util.Scanner;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println(String.format("%d / %d = %d",num1,num2,num1/num2));
}catch (InputMismatchException e) {
System.err.println("被除数和除数必须是整数");
System.exit(1);
}catch (ArithmeticException e) { //检测是否算术错误
System.err.println("除数不能为零");
System.exit(1);
}catch (Exception e) {
System.err.println("其他未知错误");
System.exit(1);
}finally {
System.out.println("感谢使用本程序!");
}
}
声明异常–throws
如果在在一个方法体中抛出了异常,我们希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常。throws可以同时声明多个异常,之间用逗号隔开。
抛出异常的两种方式:
- 通过try-catch捕获并处理异常。
- 通过throws继续声明异常。如果调用者不打算处理该异常,则可以继续通过throws声明异常,让上一级调用者处理异常。main()方法声明的异常将由Java虚拟机来处理。
import java.util.Scanner;
public static void main(String[] args) {
try {
divide();
} catch (Exception e) {
System.err.println("输入的被除数不是整数,除数不能为零");
e.printStackTrace();
}
}
public static void divide() throws Exception{
Scanner input = new Scanner(System.in);
System.out.println("请输入被除数:");
int num1 = input.nextInt();
System.out.println("请输入除数:");
int num2 = input.nextInt();
System.out.println(String.format("%d / %d = %d",num1,num2,num1/num2));
}
抛出异常
抛出异常–throw
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现并解决的,如性别输入的不是“男”或“女”,此时需要程序员而不是系统来自行抛出异常,把问题提交给调用者解决。
public class Person {
private String name = "";
private int age = 0;
private String sex = "男";
public void setSex(String sex) throws Exception{
if("男".equals(sex) || "女".equals(sex)) {
this.sex = sex;
}else {
throw new Exception("性别必须\"男\"或者\"女\"!");
}
}
public void print() {
System.out.println(this.name + "(" + this.sex + "," + this.age + "岁)");
}
}
public class Test {
public static void main(String[] args) {
Person xx = new Person();
try {
xx.setSex("Male");
xx.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
throws和throw的区别
- 作用不同:throw用于在程序中抛出异常;throws用于声明在该方法内抛出了异常。
- 使用的位置不同:throw位于方法体内部,可以作为单独语句使用;throws必须跟在方法参数列表的后面,不能单独使用。
- 内容不同:throw抛出一个异常对象,而且只能是一个;throws后面跟异常类,而且可以跟多个异常类。
异常的分类
- Throwable类:所有异常类型都是Throwable类的子类,它派生两个子类,即Error和Exception。
- Error类:表示仅靠程序本身无法恢复的严重错误,如内存溢出动态链接失败、虚拟机错误。
- Exception类:由Java应用程序抛出和处理的非严重错误,如所需文件找不到、数组下标越界等等。
- 运行时异常:包括RuntimeException及其所有子类,不要求必须对它们做出处理。
- Checked异常(非运行时异常):除了运行时异常外的其他由Exception继承来的异常类。程序必须捕获或声明抛出这种异常,否则会出现编译错误,无法通过编译。
开源日志记录工具log4j
使用步骤:
- 在项目中加入log4j所使用的JAR文件。
- 创建log4j.properties文件。
- 编写log4j.properties文件,配置日志信息。
- 在程序中使用log4j记录日志信息。
日志及分类
软件的运行过程离不开日志,日志主要用来记录系统运行过程中的一些重要的操作信息,便于监视系统运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到发生的原因。
日志根据记录内容的不同,主要分成以下三类:
- SQL日志:记录系统执行的SQL语句。
- 异常日志:记录系统运行中发生的异常事件。
- 业务日志:记录系统运行过程,如用户登录、操作记录。
log4j是一个非常优秀的日志(log)记录工具。通过使用log4j,可以控制日志的输出级别,以及日志信息输送的目的地(如控制台、文件等),还可以控制每一条日志的输出格式。
要使用log4j,首先需要下载log4j的JAR文件。log4j是 Apache的一个开源项目,官方网站是http://logging.apache.org/log4j。
如何使用log4j记录日志
- 在项目中加入log4j所使用的JAR文件。在 MyEclipse中选中要使用log4j的项目,然后依次选择" Project"→" properties→" Java Build Path"→" Libraries→" Add External JARs…"选项,弹出选择JAR的窗口,找到自己计算机上存放的文件。
- 创建log4j.properties文件。选择要使用log4j的项目,右击src,依次选择“New”→“File”,输入文件名“log4j.properties”→“Finish”。
- 编写log4j.properties文件,配置日志信息。
### 设置Logger输出级别和输出目的地 ###
log4j.rootLogger=debug, stdout,logfile
### 把日志信息输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=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
import org.apache.log4j.Logger;
import prepare.Yx12;
public class Test{
private static Logger logger = Logger.getLogger(Yx12.class.getName());
public static void main(String[] args) {
try {
Scanner in = new Scanner(System.in);
System.out.print("请输入被除数:");
int num1 = in.nextInt();
logger.debug("输入被除数:" + num1);
System.out.print("请输入除数:");
int num2 = in.nextInt();
logger.debug("输入除数:" + num2);
System.out.println(String.format("%d / %d = %d",num1,num2,num1/num2));
logger.debug("输出运算结果:"+String.format("%d / %d = %d", num1,num2,num1/num2));
} catch (InputMismatchException e) {
logger.error("被除数和除数必须是整数",e);
}catch(ArithmeticException e) {
logger.error(e.getMessage());
}catch(Exception e) {
logger.error(e.getMessage());
}finally {
System.out.println("欢迎使用本程序!");
}
}
}
在程序中log4j使用:首先创建一个私有静态的Logger对象,然后就可以通过它的debug()或者error()等方法输出日志信息了。
Logger对象是用来替代System.err的日志记录器,供程序员输出日志信息,它提供了一系列方法来输出不同级别的日志信息。
- public void debug(Object msg)
- public void debug(Object msg, Throwable t)
- public void info(Object msg)
- public void info(Object msg, Throwable t)
- public void warn(Object msg)
- public void warn(Object msg, Throwable t)
- public void error(Object msg)
- public void error(Object msg, Throwable t)
- public void fatal(Object msg)
- public void fatal(Object msg, Throwable t)
log4j配置文件
1、输出级别
log4j.rootLogger=debug, stdout,logfile
debug指的是日志记录器(Logger)的输出级别,主要输出级别及含义如下:
- fatal:指出严重的错误事件将会导致应用程序的退出。
- error:指出虽然发生错误事件,但仍然不影响系统的继续运行。
- warn:表明会出现潜在错误的情形。
- info:在粗粒度级别上指明消息,强调应用程序的运行过程。
- debug:指出细粒度信息事件,对调试应用程序非常有帮助。
- 优先级:fatal > error > warn > info > debug
日志记录器(Logger)将只会输出那些级别高于或等于它的信息。例如级别为error,将只输出fatal、error级别的日志信息。
2、日志输出目的地Appender
log4j.rootLogger=debug, stdout,logfile
stdout,logfile指的是日志输出目的地的名字。
log4j允许记录日志到多个输出目的地,一个输出目的地被称为一个Appender。log4j最常用的Appender有以下两种:
- ConsoleAppender:输出日志事件到控制台。通过Target属性配置输出到System.out或System.err,默认的目标是System.out。
- FileAppender:输出日志事件到一个文件。通过File属性配置文件的路径及名称。
3、日志布局类型Layout
Appender必须使用一个与之相关联的布局类型Layout,用来指定它的输出样式:
- HTMLLayout:格式化日志输出为HTML表格。
- SimpleLayout:以一种非常简单的方式格式化日志输出,它输出级别Level(最小),然后跟着一个破折号,最后是日志信息。
- PatternLayout:根据特定的转换模式格式化日志输出,从而支持丰富多样的输出格式。需要配置layout.ConversionPattern属性,若没有配置该属性,则使用默认的转换模式。
4、转换模式ConversionPattern
- %d:设置输出日志的日期和时间,默认格式为ISO8601。
- %m:输出代码中指定的消息。
- %n:输出一个回车换行符。
- %l:输出日志事件的发生位置,包括类名、发生的线程,以及在代码中的行数。
- %p:输出优先级。
- %F:输出文件名
- %M:输出方法名。
Layout:以一种非常简单的方式格式化日志输出,它输出级别Level(最小),然后跟着一个破折号,最后是日志信息。
- PatternLayout:根据特定的转换模式格式化日志输出,从而支持丰富多样的输出格式。需要配置layout.ConversionPattern属性,若没有配置该属性,则使用默认的转换模式。
4、转换模式ConversionPattern
- %d:设置输出日志的日期和时间,默认格式为ISO8601。
- %m:输出代码中指定的消息。
- %n:输出一个回车换行符。
- %l:输出日志事件的发生位置,包括类名、发生的线程,以及在代码中的行数。
- %p:输出优先级。
- %F:输出文件名
- %M:输出方法名。