愿你如阳光,明媚不忧伤。
目録
1. 异常定义
Exception 异常指的是在程序运行过程中发生的异常事件(编译时产生的不是异常,而是错误),通常是由外部问题(如硬件错误、输入错误)所导致的。它阻止了程序按照程序员的预期正常执行。在Java等面向对象的编程语言中异常属于对象。
2. 异常类型
Throwable类包括了Error类和Exception类,异常类有两个主要的子类:IOException 类和 RuntimeException 类。
三种类型的异常:
- 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
- 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
可以看到 Java 语言定义了很多的异常类在 java.lang 标准包中。实在是太多了,整理不过来,具体遇到时再具体分析。
3. 异常方法
下面的列表是 Throwable 类的主要( public )方法:
方法 | 说明 |
---|---|
Throwable() | 构建一个空的 Throwable ,随后可被 initCause 初始化 |
Throwable(String message) | 构建一个带有详细信息的 Throwable ,随后可被 initCause 初始化 |
Throwable(String message, Throwable cause) | 构建一个带有详细信息和导致原因的 Throwable |
Throwable(Throwable cause) | 构建一个带有导致原因的 Throwable |
final synchronized void addSuppressed(Throwable exception) | 记录被抑制的异常(finally语句块抛出异常,try语句块中的异常会丢失) |
synchronized Throwable fillInStackTrace() | 清空原来的栈内的trace信息,然后在当前的调用位置处重新建立trace信息 |
synchronized Throwable getCause() | 返回一个Throwable 对象异常的导致原因 |
String getLocalizedMessage() | 返回关于发生异常的本地化详细信息 |
String getMessage() | 返回关于发生异常的详细信息 |
StackTraceElement[] getStackTrace() | 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底 |
final synchronized Throwable[] getSuppressed() | 获取被抑制的异常 |
synchronized Throwable initCause(Throwable cause) | 初始化导致原因,最多可调用一次 |
void printStackTrace() | 打印toString()结果和栈层次到System.err,即错误输出流 |
void printStackTrace(PrintStream s) | 打印toString()结果和栈层次到标准错误流 |
void printStackTrace(PrintWriter s) | 打印toString()结果和栈层次到指定文件 |
void setStackTrace(StackTraceElement[] stackTrace) | 设置将返回的堆栈跟踪元素,高级系统使用 |
String toString() | 返回 Throwable 的字符串表示形式 |
4. 异常关键字
finally语句块总是会被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则不会跳回执行,直接停止。
主方法上也可以使用throws抛出。如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。
关键字 | 说明 |
---|---|
try | 用于监听异常 |
catch | 用于捕获异常 |
finally | 用于回收在try块里打开的资源(数据库连接,网络连接,磁盘文件等) |
throw | 用于抛出异常 |
throws | 用在方法签名中,用于声明该方法可能抛出的异常 |
5. 内置异常
- java.lang.ArithmeticException
算数异常,例如除数不能为0
package com.ITGodRoad.exception;
public class ExceptionDemo {
public static void main(String[] args) {
int a = 5;
int b = 0;
System.out.println(a / b);
}
}
-----------------------------------------------------------------
・【运行结果】
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:7)
Eclipse 中异常快捷键:Alt+Shift+Z
现在我们用try-catch监听并捕获异常
public class ExceptionDemo {
public static void main(String[] args) {
int a = 5;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
e.printStackTrace();
}
}
}
-----------------------------------------------------------------
・【运行结果】
除数不能为0
java.lang.ArithmeticException: / by zero
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:8)
coverage 代码覆盖率 → 无论是否发生异常,finally 代码块中的代码总会被执行。
绿色:代码被执行过
黄色:代码部分被执行过
红色:代码没有被执行过
throw 主动抛出异常,可以看到虽然捕获的是 ArithmeticException 算数异常,但是控制台信息打印的是我们主动抛出的 RuntimeException 运行时异常。
public class ExceptionDemo {
public static void main(String[] args) {
int a = 5;
int b = 0;
try {
System.out.println(a / b);
} catch (ArithmeticException e) {
throw new RuntimeException();
} finally {
System.out.println("这是一个有趣的世界!");
}
}
}
-----------------------------------------------------------------
・【运行结果】
这是一个有趣的世界!
Exception in thread "main" java.lang.RuntimeException
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:10)
throws 向上抛出异常,用在函数上,声明该函数的功能可能会出现问题,将异常抛出,使问题暴露出来,用于处理。可以抛给虚拟机处理,或者使用 try…catch… 进行处理。虚拟机的处理方式,就是将异常打印出来,并且将在异常处的代码终止。
public class ExceptionDemo {
public static void main(String[] args) {
int a = 5;
int b = 0;
System.out.println(devision(a, b));
}
public static int devision(int a, int b) throws ArithmeticException {
return a / b;
}
}
-----------------------------------------------------------------
・【运行结果】
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ITGodRoad.exception.ExceptionDemo.devision(ExceptionDemo.java:11)
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:7)
6. 简单的自定义异常
在 Java 中你可以自定义异常。如果要自定义异常类,扩展Exception类即可,这样的自定义异常都属于检查异常(checked exception),编译时要求进行 try-catch 处理。如果要自定义非检查异常,则扩展RuntimeException。
按照国际惯例,自定义的异常应该总是包含如下的构造函数:
- 一个无参构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数。
- 一个带有String参数和Throwable参数,并都传递给父类构造函数
- 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
Alt+shift+S 选择从父类生成构造器即可自动生成上面的构造函数
package com.ITGodRoad.exception;
public class ExceptionDemo {
public static void main(String[] args) {
int a = 5;
int b = 0;
int result = 0;
try {
result = devision(a, b);
} catch (MyCompileException e) {
e.printStackTrace();
}
System.out.println("程序还可以继续运行,result:" + result);
System.out.println("****************************************");
updatePassword("123456");
}
-----------------------------------------------------------------
public static int devision(int a, int b) throws MyCompileException {
if (b == 0) {
throw new MyCompileException("除数不能为0");
}
return a / b;
}
-----------------------------------------------------------------
public static String updatePassword(String password) {
if (password.equals("123456")) {
throw new MyRuntimeException("密码不能过于简单");
}
return password;
}
}
-----------------------------------------------------------------
class MyCompileException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public MyCompileException() {
super();
// TODO Auto-generated constructor stub
}
public MyCompileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public MyCompileException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public MyCompileException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public MyCompileException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
-----------------------------------------------------------------
class MyRuntimeException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
public MyRuntimeException() {
super();
// TODO Auto-generated constructor stub
}
public MyRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public MyRuntimeException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public MyRuntimeException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public MyRuntimeException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
-----------------------------------------------------------------
・【运行结果】
com.ITGodRoad.exception.MyCompileException: 除数不能为0
at com.ITGodRoad.exception.ExceptionDemo.devision(ExceptionDemo.java:23)
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:10)
程序还可以继续运行,result:0
****************************************
Exception in thread "main" com.ITGodRoad.exception.MyRuntimeException: 密码不能过于简单
at com.ITGodRoad.exception.ExceptionDemo.updatePassword(ExceptionDemo.java:30)
at com.ITGodRoad.exception.ExceptionDemo.main(ExceptionDemo.java:18)
可以看出,自定义异常,会将异常的处理权交给自己,而不是 JVM ,这样就避免了 JVM 遇到一些级别低的错误而使整个程序关闭。
7. 高级的自定义异常
可以自定义属性,例如添加异常级别(警告 warn 、错误 error 等),异常类型(业务异常、操作异常等),等其他自定义信息,还可以指定异常记录的输出位置,这样以后查看 log 就可以很轻松的定位程序问题所在了。
- NormalException
package com.ITGodRoad.exception;
import java.util.Map;
import com.ITGodRoad.constant.Constant;
public class NormalException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
private String level;
private String errorType;
private Map<String, String> data;
private boolean isMessage;
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
public String getLevel() {
return level;
}
public String getErrorType() {
return errorType;
}
public void setLevelError() {
this.level = Constant.LEVEL_ERROR;
}
public void setErrorTypeBusiness() {
this.errorType = Constant.ERROR_TYPE_BUSINESS;
}
public boolean isMessage() {
return isMessage;
}
public NormalException(String errorMessageCode) {
super(errorMessageCode);
this.level = Constant.LEVEL_WARN;
this.errorType = Constant.ERROR_TYPE_COMMUNICATION;
this.data = null;
this.isMessage = false;
}
// 判断是否是直接输出信息,false 则去数据库查询
public NormalException(String errorMessageCode, boolean isMessage) {
super(errorMessageCode);
this.level = Constant.LEVEL_WARN;
this.errorType = Constant.ERROR_TYPE_COMMUNICATION;
this.data = null;
this.isMessage = isMessage;
}
}
- MessageMasterService
@Service
@Transactional
public class MessageMasterService extends BaseService {
@Autowired
private MessageMasterDao messageMasterDao;
public String getMessage(String codeId) {
log.info("codeId:" + codeId);
if (Util.isEmptyString(codeId)) {
return MessageCodes.DEFAULT_ERROR_DETAIL;
}
try {
MessageMaster messageMaster = messageMasterDao.getMessageMasterById(Constant.IT_GOD_ROAD_WEB_SITE, codeId);
if (Util.isNull(messageMaster)) {
messageMaster = messageMasterDao.getMessageMasterById(Constant.IT_GOD_ROAD_WEB_SITE, MessageCodes.DEFAULT_ERROR);
}
if (Util.isNull(messageMaster)) {
return "";
}
return messageMaster.getMessageDesc();
} catch (Exception e) {
return MessageCodes.DEFAULT_ERROR_DETAIL;
}
}
}
- MessageMasterDao
@Repository
public class MessageMasterDao extends BaseDao {
public MessageMaster getMessageMasterById(String type, String id) {
log.info("type:" + type);
log.info("id:" + id);
MessageMasterId messageMasterId = new MessageMasterId();
messageMasterId.setMessageType(type);
messageMasterId.setMessageId(id);
Session session = sessionFactory.getCurrentSession();
MessageMaster messageMaster = session.get(MessageMaster.class, messageMasterId);
return messageMaster;
}
}
- MessageMaster
/**
* MessageMaster generated by hbm2java
*/
@Entity
@Table(name = "MESSAGE_MASTER")
public class MessageMaster implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private MessageMasterId id;
private String messageDesc;
public MessageMaster() {
}
public MessageMaster(MessageMasterId id, String messageDesc) {
this.id = id;
this.messageDesc = messageDesc;
}
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name = "messageType", column = @Column(name = "MESSAGE_TYPE", nullable = false, length = 32)),
@AttributeOverride(name = "messageId", column = @Column(name = "MESSAGE_ID", nullable = false, length = 32)) })
public MessageMasterId getId() {
return this.id;
}
public void setId(MessageMasterId id) {
this.id = id;
}
@Column(name = "MESSAGE_DESC", nullable = false, length = 1024)
public String getMessageDesc() {
return this.messageDesc;
}
public void setMessageDesc(String messageDesc) {
this.messageDesc = messageDesc;
}
}
【每日一面】
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?在finally中return好吗?
注意!!! 在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。
catch 中 return 了 finally 还是会执行,在 return 前执行