基本语法
try catch throw
try {
process1();
} catch (Exception e) {
e.printStackTrace(); //打印异常栈
}
如果不想在函数中捕获异常,可以加上throws把它转交给调用方处理;
最后一层层扔到了main,若连main也不想处理,可以继续throw,这就真的丢弃了。这时一旦出错直接程序崩溃。
public class Main {
public static void main(String[] args) throws Exception {
byte[] bs = toGBK("中文");
System.out.println(Arrays.toString(bs));
}
static byte[] toGBK(String s) throws UnsupportedEncodingException {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
}
}
捕获多种异常
catch会依次执行,需要将子类放到前面,否则总会落到父类里去,正确做法如下:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) { //IOException的子类
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
}
}
若两种异常没有继承关系,可以用|
连接:
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
}
}
finally
try…catch…finally,finally总是最后执行,finally可以省略。
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
} finally {
System.out.println("END");
}
}
异常类型转换
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) { //2. 抓到process2的异常
throw new IllegalArgumentException(e); //3.扔新的异常
}
}
static void process2() {
throw new NullPointerException(); //1. 手动抛个异常
}
}
打印:
java.lang.IllegalArgumentException: java.lang.NullPointerException
at oop_package.src.com.itranswarp.sample.Main.process1(Main.java:16)
at oop_package.src.com.itranswarp.sample.Main.main(Main.java:6)
Caused by: java.lang.NullPointerException
at oop_package.src.com.itranswarp.sample.Main.process2(Main.java:21)
at oop_package.src.com.itranswarp.sample.Main.process1(Main.java:14)
... 1 more
Caused by: Xxx,指向了问题源头process2()的异常。
调试时Caused by很关键。
常用异常类型
Exception
│
├─ RuntimeException
│ │
│ ├─ NullPointerException
│ │
│ ├─ IndexOutOfBoundsException
│ │
│ ├─ SecurityException
│ │
│ └─ IllegalArgumentException
│ │
│ └─ NumberFormatException
│
├─ IOException
│ │
│ ├─ UnsupportedCharsetException
│ │
│ ├─ FileNotFoundException
│ │
│ └─ SocketException
│
├─ ParseException
│
├─ GeneralSecurityException
│
├─ SQLException
│
└─ TimeoutException
自定义异常
在大型项目中通常自己先定义一个BaseException
作为“根异常”,通常建议从RuntimeException
派生。然后再继续派生出各种业务类型的异常。
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
断言 assert
方便抛出异常并打印自定义消息,但程序会退出,所以只应用于调试阶段:
int x = -1;
assert x > 0 : "x <= 0!!!";
System.out.println(x);
assert默认是不开启的,需要给jvm传递-enableassertions(可简写为-ea)参数启用断言:
$ java -ea Main.java
Exception in thread "main" java.lang.AssertionError
at Main.main(Main.java:5)
实际开发很少用断言,一般采用单元测试。
JDK Logging
package oop_package.src.com.itranswarp.sample;
import java.util.logging.Logger;
public class Main{
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.info("start process...");
logger.warning("memory is running out...");
logger.fine("ignored."); //这条没被打印,info级别以上才打印
logger.severe("process will be terminated...");
}
}
打印:
May 26, 2021 4:46:33 PM oop_package.src.com.itranswarp.sample.Main main
INFO: start process...
May 26, 2021 4:46:33 PM oop_package.src.com.itranswarp.sample.Main main
WARNING: memory is running out...
May 26, 2021 4:46:33 PM oop_package.src.com.itranswarp.sample.Main main
SEVERE: process will be terminated...
logging级别:
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
默认级别是INFO,INFO以下的都忽略。
设置级别需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>
。
Commons Logging
第三方日志库,可以连接不同日志系统,默认搜索使用Log4j,如果没有则用JDK Logging。
只需操作两个类:
- LogFactory
- Log
//需要导入专门jar包,不在标准库里
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
public static void main(String[] args) {
Log log = LogFactory.getLog(Main.class);
log.info("start...");
log.warn("end.");
}
}
通常在一个父类里定义一个常量:
protected final Log log = LogFactory.getLog(getClass());
以后的子类继承后直接用log
就行。
Log4j
流行的日志实现,可以作为上面Commons Logging接口的具体实现。
Log4j能自动通过不同的Appender把同一条日志输出到不同的目的地。例如:
- console:输出到屏幕;
- file:输出到文件;
- socket:通过网络输出到远程计算机;
- jdbc:输出到数据库
使用Log4j的时候,需要配置log4j2.xml的文件。
只要把相关Log4j的相关jar包放到classpath,Commons Logging就能自动发现它,其 log.info()等打印的信息就是由Log4j完成的了。
SLF4J和Logback
SLF4J类似于Commons Logging,也是一个日志接口;
Logback类似于Log4j,是一个日志的实现。
它们俩搭档起来,对Commons Logging 和 Log4j 做了一些改进,比如自带带占位符功能,不用自己拼接字符串了:
logger.info("Set score {} for Person {} ok.", score, p.getName());
从目前的趋势来看,越来越多的开源项目从Commons Logging加Log4j转向了SLF4J加Logback。