异常
在生活中异常情况随时都有可能发生,以上下班为例在正常情况下,小王每日开车去上班,耗时大约30分钟。但是,由于车多,人多,路窄异常情况横有可能发生。有时会遇上比较严重的堵车,偶尔还会很倒霉地与其他汽车情迷接触。这种情况下,小王往往很晚到达单位,这种异常虽然偶尔才会发生但是若真发生也是件极其麻烦的事情。
这就是生活中的异常。下面我们看看程序运行过程中会不会发生异常:
public static void main(String[] args) {
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));
System.out.println("感谢使用本程序");
}
我们只要输出了非数字,或者除数为0系统就会异常,一旦出现异常,程序将会立刻结束,不经计算和输出商的语句不被执行,而且输出”感谢使用本程序!“的语句也不执行,应该如何解决这些异常呢?我们可以通过if - else语句来对各种异常情况进行判断处理
但不过通过if-else语句来进行异常处理的机制主要有以下特点:
- 代码臃肿,加入大量的异常情况判断和处理代码
- 程序员把相当多的经理放在了处理异常代码上,放在了”堵漏洞“上,减少了编写业务代码的时间,必然影响开发效率
- 很难穷举所有的异常情况,程序任然不健壮
- 异常处理代码和业务代码交织在一起,影响代码的可读性,加大日后程序的维护难度
如果堵漏洞的工作由系统来处理,用户关注于业务代码的编写,对于异常只需调用相应的异常处理程序就好了。
异常处理
异常处理机制就像我们对平时可能遇到的意外情况,预先想好了一些处理代码的办法,也就是说在程序执行代码的时候,万一发生了异常,程序会按照预定的处理办法对异常进行处理,异常处理完毕之后程序继续进行。
Java的异常处理是通过五个关键字来实现的:try,catch,finally,throw,throws
try-catch块
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.out.println("程序出现错误");
e.printStackTrace();
}
}
- try-catch程序块比较简单,首先执行的是try语句快的语句,这时可能会有以下三种情况:
- 如果try中所有语句正常执行完毕,没有发生异常那么catch块中的所有语句都不执行
- 如果try语句块在执行过程中遇到异常,并且这个异常与catch中声明的异常类型相匹配,那么try里面的其余剩下的代码都将不执行了,而相应的catch快将会被执行。匹配是指catch所处理的异常类型与所生成的异常类型完全一致或是他的父类。
- 如果try语句块在执行过程中遇到异常,而抛出的异常在catch里面没有被被声明那么程序立刻退出
常见的异常类型
异常 | 说明 |
---|---|
exception | 异常层次结构的根类 |
ArithmeticException | 算数错误情形,如以零做除数 |
ArrayIndexOutOfBoundsException | 数组下标越界 |
NullPointerException | 尝试访问null对象的成员 |
ClassNotFoundException | 不饿能加载所需的类 |
InputMismatchException | 遇得到的数据类型与实际输入的类型不匹配 |
IIIegalArgumentException | 方法接收到非法参数 |
ClassCastException | 对象强制类型转换出错 |
NumberFormatException | 数字格式转换异常 |
try-catch-finally块
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));
} catch (Exception e) {
System.out.println("程序出现错误");
e.printStackTrace();
}finally {
System.out.println("感谢使用本程序");
}
}
try-catch-finally程序块的执行流程大致分为两种情况
-
如果try块中所有语句正常执行完毕,那么finally块将会被执行
-
如果try语句在执行过程中碰到一场,无论catch能不能捕获异常,都将执行finally块中的代码
try-catch-finally结构中try块是必须的,catch和finally块为可选,但两者至少必须出现其中之一
需要特别注意的是,即使在try块和catch块中存在return语句,finally块中语句也会被执行,发生异常时的执行顺序,执行try块或catch块中存在return语句,finally块中的语句也会被执行。finally块中语句不被执行的唯一情况:在异常处理代码中执行System.exit(1)。将退出Java虚拟机。
多重catch块
在上面计算并输出商的示例中,其中至少存在两种异常情况,输入非整数内容和除数为0.之前我同意按照Exception类型捕获,其实完全可以分别捕获,即使用多重catch块。
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));
} catch (InputMismatchException e) {
System.err.println("被除数和除数必须是整数");
}catch (ArithmeticException e) {
System.err.println("除数不能为0");
}catch (Exception e) {
System.err.println("程序出现错误");
}finally {
System.out.println("感谢使用本程序");
}
}
程序运行后,如果输出的不是整数,系统将会抛出InputMismatchException异常对象因此进入第一个catch块并执行其中的代码,而其他的catch块将不执行。
如果除数输入0系统会抛出ArithmeticException异常对象。因此进入第二个catch块,并执行其中的代码,其他catch将不执行。
注意:在使用多重catch块时,catch块的排序必须时从子类到父类,最后一个一般都是Exception类
声明异常——throws
如果在一个方法体中抛出了异常,我们就希望调用者能够及时的捕获异常,那么如何通知调用者呢?Java语言中关键字throws声明某个方法可能抛出的各种异常,throws可以声明多个异常之间用逗号隔开。
我们把计算并输出商的任务封装在divide()方法中,并在方法的参数列表后通过throws声明了一场,然后在main方法中调用该方法,此时main()方法就知道divide()方法抛出了异常,可以采用一下两种方式进行处理异常
通过try-catch捕获并处理异常。
通过throws继续声明异常。如果调用者不打算处理该异常,则可以继续通过throws声明异常,让上一级调用者处理异常。main方法声明的异常将由Java虚拟机来处理
public static void main(String[] args) {
try {
divide();
} catch (Exception e) {
System.out.println("出现错误");
e.printStackTrace();
}
}
public static void divide() throws Exception{
Scanner input = new Scanner(System.in);
int num1 = input.nextInt();
System.out.println("请输入除数");
int num2 = input.nextInt();
System.out.println(String.format("%d/d=%d", num1,num2,num1/num2));
}
抛出异常——throw
在Java语言中,可以使用throw关键字来自行抛出异常在person类中抛出一个异常,抛出的异常的原因,由于在当前环境中无法解决参数问题,因此在方法内通过throw抛出异常,把问题交给调用者去处理,在调用该方法的Test类中捕获并处理异常。
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(name+sex+age);
}
}
public class Test {
public static void main(String[] args) {
Person jkl = new Person();
try {
jkl.setSex("male");
jkl.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
异常的分类
Throwable类:所有的异常类型都是Throwable的子类,他派生两个子类,即Error和Exception
Error类:表示仅靠程序本身无法恢复的严重错误,如内存溢出动态链接失败,虚拟机错误。应用程序不应该抛出这种类型的对象。加入出现这种错误,除了尽力时程序安全退出外,在其他方面时无能为力的,所以在进行程序设计时,应该更关注Exception
Exception类:由Java应用程序抛出和处理的非严重错误,如所需文件找不到,网络连接不通或中断,常见的异常类型里面详细讲到Exception是他们的父类
运行时异常:包括RuntimeException及其所有子类,不要求程序必须对他们做出处理。在程序中并没有使用try-catch或throws进行处理,任然可以进行编译和运行,如果运行时发生异常,会输出异常的堆栈信息并终止程序运行
Checked异常:除了运行时异常外的其他有Exception继承来的异常类程序必须捕获或者声明抛出这种异常,否则会出现编译从无,无法通过编译,处理方式包括两种try-catch 通过Throws声明抛出异常,交给上一级调用方法处理
开源日志记录工具log4j
在MyEclipse中使用log4j的步骤比较简单,主要分为一下四个步骤
- 在项目中加入log4j所使用的JAR文件
- 创建Log4j.properties文件
- 编写Log4j.properties文件,配置日志信息。
- 在程序中使用log4j记录日志信息
日志及分类
日志根据记录内容的不同,主要分成一下三类
SQL日志:记录系统执行的SQL语句
异常日志:记录系统运行中发生的异常事件
业务日志:记录系统运行过程,如用户登录,操作记录
Log4j配置文件
输出级别
Log4j.rootLogger = debug,stdout,Logfile
其中debug指的是日志记录器Logger的输出级别,主要输出级别及含义如下:
fatal:指出严重的错误事件将会导致应用程序的退出。
error:指出虽然发生错误事件。但仍然不影响系统的继续运行
warm:表明会出现潜在错误的情形。
info:在粗粒度级别上指明消息,强调应用程序的运行过程
debug:指出细粒度信息事件,对调试应用程序是非常有帮助的
各个输出级别优先级:
fatal>error>warm>info>debug
日志输出目的地Appender
log4j允许记录日志到多个输出目的地,一个输出目的地被称为Appender.Log4j中最常用的Appender有以下两种
ConsoleAppender:输出日志事件到控制台,通过Target属性来配置输出到System.out或System.err
FileAppender:输出日志事件到一个文件通过File属性配置文件的路径及名称
日志布局类型Layout
有三种
HTMLLayout:格式化日志输出HTML表格
SimpleLayout:以一种非常简单的方式格式化日志输出,他输出级别Level,最后日志消息
PattermLayout:根据指定的转换模式格式化日志输出,从而支持丰富多样的输出格式。
转换模式ConversionPattern
%p:输出的等级
%m:消息,输出的内容
%n:换行
%F:输出文件名
%M:用来输出方法名