一、错误处理
(一) java的异常
1、Java 使用异常来表示错误:
(1)异常是一种类 class;
(2)本身带有类型信息;
(3)异常可以在任何地方抛出;
(4)异常只需要在上层捕获,要和方法调用分离。
try{
String s = processFile("C:\\tet.txt"); // ok
}catch(FileNotFoundException e){ // file not found
System.out.println(" File Not Found");
}catch(SecurityException e){ // no read permission
System.out.println(" Security");
}catch(IOException e){ // io error
System.out.println(" IO");
}catch(Exception e){
System.out.println("other error");
}
2、java 的 Exception 包括两类:RuntimeException 和 IOException
3、java语言规定:
(1)必须捕获的异常有:Exception 及其子类,但不包括 RuntimeException 及其子类,称为 Checked Exception。
(2)不需要捕获的异常:Error 及其子类,RuntimeException 及其子类。
说明:
(1)Error 是发生了严重错误,程序对此一般无能为力:OutOfMemoryError、NoClassDefFoundError、StackOverflowError ...
(2)Exception 是发生类运行时逻辑错误,应该捕获异常并处理:
a. 捕获并处理错误:IOException、NumberFormatException
b. 修复程序(代码出错): NullPointerException、IndexOutOfBoundsException
(二) 捕获异常
1、java 中使用 try {...} catch() {...} 语句来捕获异常
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK")
} catch (UnsupportedEncodingException e) {
System.out.println(e);
}
}
2、对可能抛出 Checked Exception 的方法调用:
(1)捕获 Exception 并处理
(2)不捕获但通过 throws 声明,将异常向上层抛出,进而在上层捕获。即通过 throws 声明的异常函数仍需要在上层捕获。
(3)main() 方法是 java 程序最后捕获异常 Exception 的机会,如果不捕获,程序会报错。
static byte[] toGBK(String s)
throws UnsupportedEncodingException{ // 将异常抛出到上层(main 方法中)
return s.getBytes("GBK");
}
public static void main(String[] args) {
try { // 在 main 方法中捕获内层函数抛出的异常
byte[] data = toGBK(”test“);
System.out.println("finash"); // 如果上行语句有异常,则该语句不会被执行
} catch (UnsupportedEncodingException e){
System.out.println(s);
}
}
3、可以使用多个 catch 子句:
(1)每个 catch 捕获对应的 Exception 及其子类。
(2)从上到下匹配,匹配到某个 catch 后不再继续匹配。
(3)catch 中异常的顺序很重要,子类必须写在父类前,否则永远无法匹配到。
4、finally 语句块保证有无错误都会执行,但是 finally 不是必须的。
public static void main(String[] arg){
try{
process1();
process2();
process3();
}catch (UnsupportedEncodingException e){
System.out.println("Bad encoding");
}catch (IOException e){
System.out.println("IO error");
}finally{ // 在 try语句程序 最后执行,无论如何都会执行
System.out.println("END");
}
}
5、如果某些异常的处理逻辑相同,但是异常之间不存在继承关系,需要写多条 catch 子句,则可以使用 “|” 表示多种 Exception。
public static void main(String[] arg){
try{
process1();
process2();
process3();
} catch (IOException | NumberFormatException e){ // 使用 “ | ” 表示多种 Exception
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
}
}
(三) 抛出异常与异常转换
1、Java 使用 throw 关键字抛出异常。
2、printStackTrace() 可以打印出异常传播的方法调用栈。
public static void main(String[] arg){
try{
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
process2();
}
static void process2(){
Integer.parseInt(null);
}
3、如果一个方法捕获了某个异常后,又在 catch 子句中抛出新的异常,就相当于把抛出的异常类型"转换"了:
public static void main(String[] args){
process1("");
}
static void process1(String s){
try{
process2();
} catch (NullPointerException e){
throw new IllegalArgumentException(e); // 将原有的 NullPointerException 转换为新的异常 IllegalArgumentException
// 参数e 是NullPointerException 的别名,传递 e 是为了在异常转换后仍能追踪异常源
}
}
static void process2(String s){
throw new NullPointerException();
}
4、特殊情况下 try{...}catch(){..}finally{..} 语句执行顺序:
public static void main(String[] args){
try{
process1("xxx"); // 1
} catch (Exception e) {
System.out.println("catched"); // 2
throw new RuntimeException(e); // 4 因为抛出异常会使程序结束,所以 finally 语句先执行
} finally {
System.out.println("finally"); // 3
}
}
以上如果在 finally 中也抛出了异常,则在原有的 catch 中的异常将不会再抛出,没有被抛出的异常称为 "被屏蔽" 的异常(Suppressed Exception):
public static void main(String[] args){
try{
process1("xxx"); // 1
} catch (Exception e) {
System.out.println("catched"); // 2
throw new RuntimeException(e); // 不执行 被屏蔽的异常
} finally {
System.out.println("finally"); // 3
throw new NullPointerException(); // 4
}
}
static void proces1(String s){
throw new IllegalArgumentException();
}
5、通过 getSuppressed() 获取所有 Suppressed Exception:
try{
somethingWrong("");
} catch(Exception e) {
e.printStackTrace();
for (Throwable t : e.getSuppressed){
t.printStackTrace();
}
}
(四) 自定义异常
1、JDK 定义的常用异常:
(1)RuntimeException:NullPointerException、IndexOutOfBoundsException、SecurityEception、IllegalArgumentException
(2)IOException:UnsupportedCharsetException、FileNotFoundException、SocketException...
(3)ParseException、GeneralSecurityException、SQLException、TimeoutException
2、自定义新的异常类型: 从合适的 Exception 派生,推荐从 RuntimeException 派生。
业务中从合适的 Exception( RuntimeException) 派生 BaseException,其他 Exception 从 BaseException 派生:
public class BaseException extends RuntimeException{
}
public class UserNotFoundException extends BaseException{ // 业务一
}
public class LoginFailedException extends BaseException{ // 业务二
}
3、自定义异常应该提供多个构造方法:
public class BaseException extends RuntimeException{
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(Throwable cause) {
super(cause);
}
}
public class UserNotFoundException extends BaseException{
public UserNotFoundException() {
}
public UserNotFoundException(String message) {
super(message);
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public UserNotFoundException(Throwable cause) {
super(cause);
}
}
二、断言和日志
(一) 断言
1、断言(Assertion) 是一种程序调试方法,使用关键字 assert,断言条件预期为 true,如果断言失败,则2、抛出 AssertionError
static double abs(double d){
return d >= 0 ? d : -d;
}
public static void main(String[] args){
double x = abs(-123.45);
assert x >= 0;
System.out.println(x)
}
2、断言的特点:
(1)断言失败时会抛出 AssertionError,导致程序结束退出
(2)不能用于可恢复的程序错误
(3)只应该用于开发和测试阶段
(4)断言很少被使用,更好的方法是编写单元测试
package com.feiyangedu.sample;
pub lic class Main{
public static void main(String[] arg){
double x = abs(-123.45);
assert x >= 0 : "x must >= 0 but x = " + x; // : 号后的是断言说明,断言失败时打印
System.out.println(x);
}
static double abs(double d){
return d <= 0 ? d : -d;
}
}
(二) 日志
日志是为了取代 System.out.println(),不同于 System.out.println() ,日志可以设置输出样式、输出级别、禁止某些输出级别、可以被重定向到文件、按包名控制日志级别等,java 常用的日志包有:
1、JDK 内置类 JDK Logging --- java.util.longging
import java.util.logging.Level;
import java.util.logging.Logger;
public class Hello{
public static void main(String[] args){
Logger logger = Logger.getGlobal();
logger.info("start..."); // info() 方法输出普通的信息
logger.log(Level.WARNING, "warning...""); // log() 方法指定输出的级别
logger.warning("start..."); // warning() 方法输出警告的信息
}
}
(1)JDK Logging 定义了 7 个日志级别:
a. SEVERE
b. WARNING
c. INFO (默认级别)
d. CONFIG
e. FINE
f. FINER
g. FINEST
(2)JDK Logging 的局限:
a. JVM 启动时读取配置文件并完成初始化
b. JVM 启动后无法修改配置
c. 需要在 JVM 启动是传递参数:-Djava.util.logging.config.file=config-file-name
2、Commons Longging(是第三方jar包 推荐 常用)
Commons Logging 是 Apache 创建的日志模块
(1)Commons Logging 可以挂接不同的日志系统,即可以通过配置文件指定挂接的日志系统。
(2)Commons Logging 会首先自动搜索并使用 Log4j,如果 Log4j 不存在会自动使用 JDK Logging (JDK >= 1.4)
public class Main{
public static void main(String[] args){
Log log = LogFactory.getLog(Main.class); // 针对 Main.class 创建 Log 实例对象
log.info("start...");
log.warn("end.");
}
}
(3)Commons Logging 定义了 6个日志级别:
a. FATAL
b. ERROR
c. WARNING
d. INFO // 默认级别
e. DEBUG
f. TRACE
// 在静态方法中引用 Log:
public class Main{
static final Log log = LogFactory.getLog(Main.class);
}
// 在实例方法中引用 Log:
public class Person{
final Log log = LogFactory.getLog(getClass());
}
// 在父类中实例化 Log:
public abstract class Base{
protected final Log log = LogFactory.getLog(getClass());
}
(4)实际案例
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main{
static Log log = LogFactory.getLog(Main.class);
public static void main(String[] args){
Person p = new Person("Xiao Ming");
log.info(p.hello());
try {
new Person(null);
} catch (Exception e) {
log.error("Exception", e);
}
}
}
3、Log4j 目前最流行的日志框架
(1)Log4j 有两个版本:1.x -- Log4j、2.x -- Log4j2。
(2)log4j 是一个组件化设计的日志系统,有几个主要的组建:Appender、Filter、Layout
a. 通过 Appender组件 把同一个log输出到不同的途径中(例如:Console、File、Socket);
b. 使用 Filter组件 来过滤哪些log需要被输出,哪些log不需要被输出;
c. 使用 Layout组件 来格式化要输出的 log 信息。如下图:
(3)在实际使用中不需要关心 Log4j 的内部 api,而是通过配置文件.xml 或者 .json 来配置它。
(4)Commons Logging 可以自动使用 Log4j:Commons Logging 如果在 classpath 中发现了 log4j,就会使用 log4j。
(5)实际业务中: 始终使用 Commons Logging 接口来写入日志,开发阶段无需引入 Log4j,使用 Log4j 只需要把正确的配置文件和相关 jar包 放入 classpath 下。且使用 .xml / .json 配置文件可以灵活修改日志,无需修改代码。
4、总结:
(1)通过 Commons Logging 实现日志,不需要修改代码即可使用 Log4j
(2)使用 Log4j 只需要把 log4j2.xml 和相关 jar 放入 classpath
(3)如果要更换 Log4j,只需要移除 log4j2.xml 和相关 jar
(4)只有扩展 Log4j 时,才需要引用 Log4j 的接口