- 一、使用try-catch-finally处理异常
public class Test1 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = in.nextInt();
System.out.println(String.format("%d / %d = %d",
num1, num2, num1/ num2));
System.out.println("感谢使用本程序!");
}
}
异常就是在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序
Java编程语言使用异常处理机制为程序提供了错误处理的能力
Java的异常处理是通过5个关键字来实现的:try、catch、 finally、throw、throws
- Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
- 当Java运行时系统接收到异常对象时,会寻找能处理这个异常对象的代码并把当前异常对象交给其处理,这一过程称为捕获(catch)异常。
- 如果Java运行时系统找不到可以解决该异常对象的方法,则运行时系统将终止,相应的Java程序也将退出。
- 程序员通常只能处理“违例”(Exception),而对错误(Error)无能为力,即:我们无法解决Error级别的错误。
printStackTrace的堆栈跟踪功能显示出程序运行到当前类的执行流程
方法名 | 说 明 |
void printStackTrace() | 输出异常的堆栈信息 |
String getMessage() | 返回异常信息描述字符串, 是printStackTrace()输出信息的一部分 |
【多重catch块】
- 一段代码可能会引发多种类型的异常
- 当引发异常时,会按顺序来查看每个 catch 语句,并执行第一个与异常类型匹配的catch语句
- 执行其中一条 catch 语句后,其后 catch 语句将被忽略
在安排catch语句的顺序时,首先应该捕获最特殊的异常,然后再逐渐一般化,即先子类后父类
如果一个try块后面有多个catch块时,如果这些catch块中的异常类型具有继承关系,这时这些catch块的排列顺序有如下要求:子类异常在前,父类异常在后
如果这些catch块中的异常类型没有继承关系时(即各异常类型相互独立没有任何关系),这些catch块的排列顺序没有先后,即顺序可随意排列。
如果在try块中的任何语句没有抛出某个类型的异常对象时,就不需要使用catch块来捕获该类型的异常对象,否则会有编译错误提示。
【finally块】
在try-catch块后加入finally块,可以确保无论是否发生异常,finally块中的代码总能被执行
finally块中语句不执行的唯一情况:异常处理代码中执行System.exit(1)退出Java虚拟机
finally语句块中放入的一般都是释放资源的代码,例如:关闭数据库、关闭输入/输出流、关闭已打开的文件等,用于释放系统资源,提高系统效率。
当执行到try语句块或catch语句块中的return语句时,会首先执行finally块中的代码,当执行完毕finally块中的所有代码后,再执行return语句。
finally里的操作有时也需要捕获处理异常
- 二、使用throw、throws抛出异常
【throws】
使用throws声明异常(编译时异常)
如果一个方法需要抛出不同的异常对象时,需要在throws关键字的后面将所有的异常对象的类型全部列出,各异常类型之间使用英文的逗号(”,”)隔开。
被调用的方法在定义时声明抛出多少种类型的异常对象,将来该方法的调用者就需要提供多少个catch块进捕获和处理,即:catch块的数量与方法定义时声明抛出的异常对象的类型个数相同
即使方法中使用throws关键字声明抛出一个运行时异常时,调用者也可以不使用catch块来进行捕获,即:运行时异常不需要捕获
【throw】
除了系统自动抛出异常外,有些问题需要程序员自行抛出异常
throw语句是用来在方法中手工抛出一个异常对象的,这个异常对象的类型可以是任意类型!
throw语句的语法格式为:throw new 异常类名([提示信息])
throw new Exception("性别必须是“男”或者“女”!");
throw new FileNotFoundException();
throw new SQLException(“数据库连接失败。”);
即:throw语句后面必须跟的是一个异常对象!
手工抛出异常对象时应注意如下问题:
- 一定不能抛出从Error中派生的那些异常,如果抛出则程序立即中止运行,因为Error及其子类我们是处理不了的
- 一般我们都抛出的是编译时异常
- 不要抛出从RuntimeException中派生的那些Java提供给我们的运行时异常,如:NullPointerException等,但是在编程时我们有时会根据需要抛出自定义的RuntimeExcepion类型的异常。
throws与throw配合应用示例
public void readFile() throws FileNotFoundException, IOException{
File f = new File("c:/a.txt");
if (!f.exists()){
throw new FileNotFoundException("File can't be found!");
}
FileInputStream fis = new FileInputStream(f);
int b;
b = fis.read();
while(b!= -1){
System.out.print((char)b);
b = fis.read();
}
fis.close();
}
//我们可以根据需要人为的抛出一个异常对象,但是抛出的这个异常对象必须和方法声明中抛出的异常类型相同
//或者是方法声明中异常类型的子类。
如果在一个方法中使用throw语句抛出的是一个编译时异常对象,这时,就需要在定义该方法时使用throws语句声明该方法会抛出编译时异常对象,将来该方法的调用者需要使用try-catch语句对该方法进行捕获和处理,其中throws语句后面的异常类型与throw语句抛出的异常对象的类型相同,如果不使用throws语句进行声明,则编译出错!
虽然在一个方法中可以使用throw语句抛出多个异常对象,但是一个方法最终只能抛出一个异常对象,因为当方法抛出一个异常对象后,该方法中剩余的代码都不会继续运行,因此其它的throw语句也就不会运行,所以就不可能再抛出异常对象。
【抛出异常】
throw语句后面不能再有其它语句存在
如果一个异常对象没有在当前的try-catch块中得到处理,则它会抛出到它的调用方法。
如果一个异常对象最终抛到了main()方法,并且在main()方法中仍然没有得到捕获和处理,这时程序就会异常终止。
【方法覆盖中的异常说明】
当子类中的方法覆盖父类中的方法时,子类覆盖方法可以不抛出异常。
当子类中的方法覆盖父类中的方法时,子类覆盖方法抛出的异常,可以抛出与被覆盖方法的相同的异常或者被覆盖方法的一部分异常或者被覆盖方法的异常的子类异常,但是不能抛出新的异常类型。
//父类
public class MethodException {
//父类方法
public void getAA() throws SQLException, FileNotFoundException,NullPointerException{
System.out.println("父类方法");
FileInputStream fi = new FileInputStream("c:/a.txt");
}
}
//子类
public class SubClass_MethodOverride extends MethodException {
//子类覆盖方法可以不抛出异常
public void getAA(){
}
//子类覆盖方法可以抛出与被覆盖方法的相同的异常或者被覆盖方法的一部分异常或者被覆盖方法的异常的子类异常
//public void getAA() throws SQLException{
//}
//子类覆盖方法,可以抛出被覆盖方法中异常的子类异常
//public void getAA() throws BatchUpdateException{ //BatchUpdateException类是SQLException类的子类
//}
//子类覆盖父类方法时,可以抛出与被覆盖方法的相同的异常或者被覆盖方法的异常的子类异常,不能抛出新的异常类型
//public void getAA() throws IOException{ //出错,由于父类的getAA()方法没有抛出IOException类型的异常,子类在覆盖该方法时抛出了IOException,即抛出了新类型的异常,编译出错
//}
}
【自定义异常类】
在实际项目开发中,有时为了更好的封装异常错误信息,以及降低程序各模块之间的依赖程度,这时就需要我们自己设计符合我们实际需求的异常类,通常我们将我们自己设计的异常类称为自定义异常类。
设计自定义异常类的要点:
让我们自己开发的类通过继承Exception或它的子类来实现自定义异常类
一般而言,对于自定义的异常类,会设计两个构造器:一个默认的不带参数的构造器以及一个带参数的构造器,后者用于传递详细的提示信息。
一般我们在开发自定义异常类时,都是继承Exception类或是RuntimeException类。
- 三、掌握异常及其分类
Java异常的层次关系图
异常的分类
【运行时异常(非检查异常)】
运行时异常特点:运行时异常我们可以使用程序代码完全的”消灭“掉,即可以使用程序代码完全避免它出现。
编译器不会检查该类型的异常,不需要捕获处理也不需要声明抛出,但是这类异常需要人为的避免。
【编译时异常(检查异常)】
编译时异常特点:编译时异常,当它的出现条件如果满足时,我们是没有办法(编写程序代码)来不让它出现,但是我们可以通过我们提供的解决方案来解决掉,即然解决掉了,就相当于这个异常没有出现一样。因此编译时异常编译器会检查,并强制要求我们给出相应的解决方案。
编译器会检查这种类型的异常,当出现编译时异常,要么捕获处理,要么声明抛出。如果不进行处理程序编译不成功。即:编译时异常必须捕获处理,否则程序不能编译。
无论是运行时异常还是编译时异常,它们都是在程序运行的时候发生的,如果程序都得不到运行,那么异常也就不会产生了。 因此需要我们记住:异常需要在程序运行时才能发生,程序未运行时,异常错误是不会发生的。
【常见异常举例】
运行时异常(RuntimeException)
- ArithmeticException:数学计算异常
- NullPointerException:空指针异常
- NegativeArraySizeException:负数组长度异常
- ArrayOutOfBoundsException:数组索引越界异常
- ClassNotFoundException:类文件未找到异常
- ClassCastException:类型转换异常
编译时异常
- FileNotFoundException:文件未找到异常
- EOFException:读写文件尾异常
- MalformedURLException:URL格式错误异常
- SocketException:Socket异常
- 四、使用log4j记录日志
日志(log)
- 主要用来记录系统运行中一些重要操作信息
- 便于监视系统运行情况,帮助用户提前发现和避开可能出现的问题,或者出现问题后根据日志找到原因
日志分类
- SQL日志、异常日志、业务日志
log4j是一个非常优秀的开源日志记录工具
- 控制日志的输出级别
- 控制日志信息输送的目的地是控制台、文件等
- 控制每一条日志的输出格式
如何使用log4j记录日志
- 第一步:在项目中加入log4j所使用的JAR文件
- 第二步:创建log4j.properties文件
- 第三步:编写log4j.properties,配置日志信息
### 设置Logger输出级别和输出目的地 ###
log4j.rootLogger=debug, stdout,logfile //设置优先级别为debug、日志被输出到多个输出源
### 把日志信息输出到控制台 ###
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 //指定转换模式
优先级从高到低分别是ERROR、WARN、INFO、DEBUG
在此处,如果优先级别设为info,那么使用debug方法打印的日志信息将不被输出
- 第四步:在程序中使用log4j记录日志信息