异常处理
一、异常概述
1、Java语言将程序运行过程中所发生的不正常严重错误称为异常,对异常的处理称为异常处理。
2、它会中断正在运行的程序,正因为如此异常处理是程序设计中一个非常重要的方面,也是程序设计的一大难点。
3、异常其实是人为定义的一个关于程序逻辑错误的处理机制,它的实现其实靠的是编译器的理解。
异常的分类
Throwable有两个直接子类,Error类和Exception类。
Error : 指合理的应用程序在执行过程中发生的严重问题。当程序发生这种严重错误时,通常的做法是通知用户并中止程序的执行。
Exception:异常可分为运行时异常(RuntimeException)和检查时异常(CheckedException)两种:
RuntimeException:
运行时异常,即程序运行时抛出的异常。这种异常在写代码时不进行处理,Java源文件也能编译通过。 RuntimeException异常类及其下面的子类均为运行时异常。
CheckedException:
检查时异常,又称为非运行时异常,这样的异常必须在编程时进行处理,否则就会编译不通过。Exception异常类及其子类(除去RuntimeException异常类及其子类)都是检查时异常。
会出现这样的报错
常见的异常
异常名 | 说明 |
Exception | 异常类的根类 |
RuntimeException | 运行时异常类的基类 |
ArithmeticException | 算术错误情形,如以零作除数 |
ArrayIndexOutOfBoundException | 数组大小小于或大于实际的数组大小 |
NullPointerException | 尝试访问 null 对象成员 |
ClassNotFoundException | 不能加载所需的类 |
NumberFormatException | 数字转化格式异常,字符串到 float 转换无效 |
IOException | I/O 异常的根类 |
FileNotFoundException | 找不到文件 |
SQLException | 数据库访问异常 |
InterruptedException | 线程被中断异常 |
处理异常
Java中对异常的处理有如下两种方式:
通过try、catch和finally关键字捕获异常;
通过throw或throws关键字抛出异常;
二、捕获异常
捕获异常语法结构:
try{
//可能抛出异常的语句块
}catch(SomeException1 e){ // SomeException1特指某些异常
//当捕获到SomeException1类型的异常时执行的语句块
} catch( SomeException2 e){
//当捕获到SomeException2类型的异常时执行的语句块
}finally{
//无论是否发生异常都会执行的代码
}
try…catch…finally异常处理结构中,try语句块是必须的, catch和finally语句块至少出现一个。
注意:如果try语句块包含的是检查时异常,则在没有通过throws抛出该异常类的情况下,try必须和catch一起使用,当该行代码去掉或注销掉时,catch相应的异常语句块必须去掉,如下代码:
public class Test {
public static void main(String[] args){
try {
Class.forName("java.lang.Object");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
由于该行代码抛出检查时异常,所以该行代码去掉或注销掉时,catch相应的异常语句块必须去掉
多重catch
•try语句块中的代码可能会引发多种类型的异常,当引发异常时,会按顺序查看每个 catch 语句,并执行第一个与异常类型匹配的catch语句,其后 catch 语句被忽略。
•在捕获异常的时候,应按照“从小到大”的顺序捕获异常,即先子类后父类。
先子类异常,后父类异常:ArrayIndexOutOfBoundsException异常类是RuntimeException的子类,而RuntimeException异常类是Exception的子类,他们的先后顺序不能颠倒。
finally 关键字
•Java异常在try/catch块后加入finally块,可以确保无论是否发生异常 finally块中的代码总能被执行。
public class TestException {
public static void main(String[] args) {
try {
String[] nameArray = { "小王", "小李", "小高" };
for (int i = 0; i < 4; i++) {
System.out.println(nameArray[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数据下标越界,请修改程序!");
System.out.println("调用异常对象的getMessage()方法:");
System.out.println(e.getMessage());
System.out.println("调用异常对象的printStackTrace()方法:");
e.printStackTrace();
return;// finally语句块仍然执行
// System.exit(1);//直接退出JVM,finally语句块不再执行
} finally {
System.out.println("显示完毕!");
}
System.out.println("显示完毕!");
}
}
无论是否异常,都将执行finally块中语句。即使return也不例外。仅当程序调用System.exit(1)才不执行该块。
final、finally和finalize区别
•final、finally和finalize区别:
final—修饰符(关键字),修饰的类不能被继承,修饰的方法不能被重写,修饰的变量为常量。
finally—在异常处理时提供 finally 块来执行任何清除操作。
finalize—方法名,finalize() 方法在垃圾收集器将对象从内存中清除之前做必要的清理工作,如下代码:
class Student {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("对象从内存中清除之前执行");
}
}
public class Test {
public static void main(String[] args) {
new Student();
System.gc();//执行该代码,垃圾收集器将回收未使用的内存空间。
}
}
三、抛出异常
1、throw关键字
•throw用于抛出具体异常类的对象,一般用于方法体中。
•什么时候使用:当所写的代码因不满足某些条件致使程序无法运行时可以借助throw抛出一个异常对象提醒程序员。
•throw关键字一般用在方法体中,也可以用在代码块中,但如果代码块中抛出的异常对象是由检查时异常创建的,则必须使用try-catch进行处理;
public class Test {
int a = 9;
int b = 0;
{
if (b == 0) {
try {
throw new Exception("操作失败:分母不能为0");
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(a / b);
}
public static void main(String[] args) {
new Test();
}
}
•throw将异常对象抛给谁
Ø抛给自己
Ø抛给方法调用者
仅仅使用throw关键字将异常对象抛出,此处没做任何处理;如果抛出的异常类对象是由检查时异常类创建的,则还需要使用throws抛出抛出创建该异常对象的异常类或其父类,参见备注示例代码
import javax.print.PrintException;
public class PrintUtil {
public static void printAge(int age) throws PrintException {//此处由于该方法使用throw抛出的是由检查时异常创建的对象并且throw后没有自行处理,所以这里必须要使用throws抛出创建该异常的异常类;此处也可以throws
if (age >= 150 || age <= 0) {
throw new PrintException("打印失败,请输入0~150之间的数");
} else {
System.out.println("年龄为:" + age);
}
}
}
2、throws关键字
•throws用于声明方法可能抛出的异常,其后为异常类,可以有多个,异常类之间用英文逗号间隔。
•什么时候使用:
1、当方法体中的throw关键字抛出由检查时异常创建的对象时,如果该异常对象在抛出的同时已经通过try-catch进行了处理,则可以不使用throws,否则必须使用throws抛出创建该对象的异常类或其父类。
2、所调用的方法抛出了检查时异常时,如果该方法在调用的同时已经通过try-catch进行了处理,则可以不使用throws继续上抛该异常,否则必须使用throws才能上抛该异常,此时上抛的异常类可以是调用方法时方法抛出的异常类也可以是其父类。
•如果方法中的异常已经通过try-catch进行了捕获则无需再使用throws上抛该异常了,否则即使上抛也无效,只会做无用功。
public class Test{
public static void show(int age) throws PrinterException{
if(age<0||age>150){
try {
throw new PrinterException(“操作失败:年龄取值范围为0~150”);//此处异常已经通过try-catch语句进行了处理,所以show方法无需再使用throws抛出异常了,否则即使上抛也无效:执行代码会发现,即使这里异常被触发了,在main方法中catch依然没有执行,所以此时在通过throws抛出异常类纯属无用功
} catch (PrinterException e) {
System.out.println("show方法");
e.printStackTrace();
}
return;
}
System.out.println(age);
}
public static void main(String[] args) {
try {
show(1000);
} catch (PrinterException e) {
System.out.println("main方法");
e.printStackTrace();
}
}
}
此处异常已经通过try-catch语句进行了处理,所以show方法无需再使用throws抛出异常了,否则即使上抛也无效:执行代码会发现,即使这里异常被触发了,在main方法中catch依然没有执行,所以此时在通过throws抛出异常类纯属无用功。
•如果方法中的代码或者方法中所调用的代码没有使用throw抛出异常类对象,则无需使用throws否则该throws所抛出的异常类无效:即使用throws则其方法中的代码一定存在使用throw抛出异常对象的代码。
public class Test {
public static void show(int data)throws Exception{//由于该方法中只是一个简单的输出语句,方法体和方法体中所调用的方法均没有使用throw抛出异常对象,所以此处没有必要使用throws抛出异常类,即便抛出了也是无用的——由于这里使用了throws抛出了异常类,main方法对该异常类进行了处理,可是我们会发现main方法中的catch语句块永远不可能执行
System.out.println(data);
}
public static void main(String[] args) {
try {
show(100000);
} catch (Exception e) {
System.out.println("永远不会执行");
e.printStackTrace();
}
}
}
由于该方法中只是一个简单的输出语句,方法体和方法体中所调用的方法均没有使用throw抛出异常对象,所以此处没有必要使用throws抛出异常类,即便抛出了也是无用的——由于这里使用了throws抛出了异常类,main方法对该异常类进行了处理,可是我们会发现main方法中的catch语句块永远不可能执行
3、throw与throws的区别:
1、抛出的东西不同:throw抛出的是具体的异常对象,而throws抛出的是抽象的异常类;
2、使用位置不同:throw一般用在方法体中,也可用在代码块中,但是如果抛出的是检查时异常类创建的对象,则必须使用try-catch自行处理;throws只能用在方法声明括号后面;
四、自定义异常
•为什么?
Java API提供的已有异常类无法准确表述当前发生的异常问题,这时就需要创建自定义的异常。
•怎么做?
1、创建继承Exception 或其子类的自定义类;
2、自定义异常类调用父类构造函数(通常是含有一个String类型参数的构造函数);
package com.ghj.exception;
public class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
package com.ghj.vo;
import com.ghj.exception.AgeException;
public class Person {
private int age;// 年龄
public int getAge() {
return age;
}
public void setAge(int age) throws AgeException {
if (age < 0 || age > 150) {
throw new AgeException("年龄必须在0~150之间:" + age + "不合理!");
} else {
this.age = age;
}
}
}
五、代码调试
•Eclipse开发工具中F5、F6、F7和F8 四个快捷键能辅助我们很好地进行代码的调试,各快捷键的作用如下:
F5:跳入方法;
F6:向下逐行调试;
F7:跳出方法;
F8:直接跳转到下一个断点;
六、Log4j概述
•Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息格式及其输送目的地(控制台、文件、数据库等),方便后期查找系统运行期间出现的问题,进而便于维护系统。
•步骤:
第一步:导入log4j-1.2.15.jar依赖包;
第二步:在src根目录下创建名为log4j.properties的文件,文件内容如下:
# DEBUG设置输出日志级别,由于为DEBUG,所以ERROR、WARN和INFO 级别日志信息也会显示出来
log4j.rootLogger=DEBUG,Console,RollingFile
#将日志信息输出到控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern= [%-5p]-[%d{yyyy-MM-dd HH:mm:ss}] -%l -%m%n
#将日志信息输出到操作系统D盘根目录下的log.log文件中
log4j.appender.RollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingFile.File=D://log.log
log4j.appender.RollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingFile.layout.ConversionPattern=%d [%t] %-5p %-40.40c %X{traceId}-%m%n
Log4j配置文件详解: https://blog.csdn.net/gaohuanjie/article/details/44077551
第三步:src目录创建Test类,代码如下:
import org.apache.log4j.Logger;
public class Test {
private static final Logger logger = Logger.getLogger(Test.class);
public static void main(String[] args) {
try {
Class.forName("ErrorClassName");
} catch (ClassNotFoundException e) {
logger.debug(e.getMessage(),e);//详细日报信息
logger.info(e.getMessage(),e);//详细日报信息
logger.warn(e.getMessage());//简单日报信息
logger.error(e.getMessage());//简单日报信息
}
}
}
经过上述三步,最终Java工程结构如下:
运行Test类方法,打开D盘根目录中log.log文件可以看到相应的日志信息,此时的日志信息都会显示出来,Log4j常用日志级别从高到低依次为:ERROR、WARN、INFO和DEBUG,由于上例所设置的Log4j日志级别为DEBUG,所以ERROR、WARN和INFO 级别的日志信息也会显示出来。
自定义properties的文件
Log4j默认使用src根目录中名为log4j.properties的文件,实际开发中有可能需要使用特定目录中的特定名字的properties文件,如下工程:
其中,config包内log.properties文件保存了Log4j相关配置信息,那么此时如何使用该文件呢,如下代码:
import java.net.URL;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
public class Test {
static {
// 获取src目录config包中log.properties文件对应的URL对象
URL url = Test.class.getResource("/config/log.properties");
PropertyConfigurator.configure(url);
}
static Logger logger = Logger.getLogger(Test.class);
public static void main(String[] args) {
try {
System.out.println(1 / 0);
} catch (Exception e) {
logger.debug(e.getMessage(), e);
}
}
}
开发中不止一个Java类需要将某些日志信息写入指定位置,此时每个类中都会重复性地写入如下代码:
static {
// 获取src目录config包中log.properties文件对应的URL对象
URL url = Test.class.getResource("/config/log.properties");
PropertyConfigurator.configure(url);
}