2021.8.26
java的异常
继承树
特点:
- java的用异常来表示错误,可通过try.......catch来捕捉
- 异常也是类,继承自Throwable
- Error是无需捕获的严重错误,Exception,是可以处理的,
- Runtime无需强制捕获,非
RuntimeException
(Checked Exception)需强制捕获,或者用throws
声明
【Java规定】
-
必须捕获的异常,包括
Exception
及其子类,但不包括RuntimeException
及其子类,这种类型的异常称为Checked Exception。
-
不需要捕获的异常,包括
Error
及其子类,RuntimeException
及其子类。
【异常捕获】: try... catch
public class Main {
public static void Main(String[] args){
bytes[] bs = toGBK('中文');
System.out.println(Array.toString(bs));
}
static byte[] toGBK(String s) {
try {
// 用指定编码转换String为byte[]:
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
System.out.println(e); // 打印异常信息
return s.getBytes(); // 尝试使用用默认编码
} //不捕获会报错
}
}
像getBytes方法的定义,在方法定义的时候,使用throws Xxx表示该方法可能抛出的异常类型。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}
觉得麻烦,不想写try的代码,可以在main方法定义throws exception
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】
- 捕获多个异常时,要注意顺序,因为多个catch也只会执行一个,子类要放在前面,如果放在对应父类的后面,会永远不被执行
- 在异常判断里,也可以用关系运算符
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"); } }
【final】
- 无论是否捕捉到异常,都要执行某个字段,放在每个catch里面,显得太繁杂,所以java提供了fanal
void process(String file) throws IOException { try { ... } finally { System.out.println("END"); } }
抛出异常
【异常的传播】:如果当前出现的异常,没有被捕捉,会传到上一层调用方法,知道被捕
【printStackTrace()】:打印出方法的调用栈
抛出异常的方法:
- 先创建某个exception的实例
- 在throw 出来
void process2(String s) { if (s==null) { NullPointerException e = new NullPointerException(); throw e; } } // 实际会省略一些 void process2(String s) { if (s==null) { throw new NullPointerException(); } }
捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e); //传入参数,保留原异常的casuse
}
}
static void process2() {
throw new NullPointerException();
}
}
【异常屏蔽】:catch的捕捉和抛出异常不影响final的输出,但是在final抛出异常之后,catch就不会抛出异常了,因为只能抛出一个异常,这种就叫做''被屏蔽''的异常,suppressed Exception
如仍然需要抛出所有异常,我们可以用一个变量先保存,调用Throwable.addsuppresed。在final里面一起输出。
public class Main {
public static void main(String[] args) throws Exception {
Exception origin = null;
try {
System.out.println(Integer.parseInt("abc"));
} catch (Exception e) {
origin = e;
throw e;
} finally {
Exception e = new IllegalArgumentException();
if (origin != null) {
e.addSuppressed(origin);
}
throw e;
}
}
}
大多数不用final抛出异常
自定义异常
业务需要的时候,DIY异常
一般时从Exception中派送一个baseException,然后在从baseException派生出具体的异常
public class BaseException extends RuntimeException {
}
public class UserNotFoundException extends BaseException {
}
public class LoginFailedException extends BaseException {
}
具体以后需要再看
NullPointerException空指针异常
null
NullPointerException
是Java代码常见的逻辑错误,应当早暴露,早修复;
如果一个语句 a.b.c.d(),不知道时哪个出现了nul,就可以启用Java 14的增强异常信息来查看NullPointerException
的详细错误信息。这种增强的NullPointerException
详细信息是Java 14新增的功能,但默认是关闭的,我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages
参数启用它:
java -XX:+ShowCodeDetailsInExceptionMessages Main.java
使用断言
【assert】:成立时,正常,不成立时抛出AssertionError
断言失败时,会使程序结束推出,因此一般不用于可回复的异常,比如NPE空指针异常。
public static void main(String[] args) {
double x = Math.abs(-123.45);
assert x >= 0 : "x must >= 0";
System.out.println(x);
}
要使用断言时,要在JVM下执行一下语句
$ java -ea Main.java
Exception in thread "main" java.lang.AssertionError
at Main.main(Main.java:5)
使用JDK Logging
【java.util.logging】做程序调试的时候,使用log方便
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 process...");
logger.warning("memory is running out...");
logger.fine("ignored.");
logger.severe("process will be terminated...");
}
}
---------------------------
Mar 02, 2019 6:32:13 PM Hello main
INFO: start process...
Mar 02, 2019 6:32:13 PM Hello main
WARNING: memory is running out...
Mar 02, 2019 6:32:13 PM Hello main
SEVERE: process will be terminated...
没有输出fine severe
JDK的Logging定义了7个日志级别,从严重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
默认等级时INFO,INFO以下的不会被打印
使用Commons Logging
JDK logging用的比较少,一般是用第三方库Commons Logging,一般只用连个类log,logFactory
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.");
}
}
编译的时候,还需要制定classpath,具体要自己另外观看。
先把具体的包放在同一个package下,然后编译命令
javac -cp commons-logging-1.2.jar Main.java
成功的话会有一个.class文件,然后就可以执行这个class了,注意也要制定classpath
java -cp .;commons-logging-1.2.jar Main
Commons logging里级别有6个
- FATAL
- ERROR
- WARNING
- INFO
- DEBUG
- TRACE
默认级别是INFO。
如果在静态方法中使用log,通常直接定义一个静态类型变量:
// 在静态方法中引用Log:
public class Main {
static final Log log = LogFactory.getLog(Main.class);
static void foo() {
log.info("foo");
}
}
实例方法,就定义一个实例变量
// 在实例方法中引用Log:
public class Person {
protected final Log log = LogFactory.getLog(getClass());
void foo() {
log.info("foo");
}
}
//注意到实例变量log的获取方式是LogFactory.getLog(getClass())
//,虽然也可以用LogFactory.getLog(Person.class),
//但是前一种方式有个非常大的好处,就是子类可以直接使用该log实例。
如果student继承Person,其log实际等于 LogFactory.getLog(Student.class)
Commons Logging的日志方法,例如info()
,除了标准的info(String)
外,还提供了一个非常有用的重载方法:info(String, Throwable)
,这使得记录异常更加简单:
try {
...
} catch (Exception e) {
log.error("got exception!", e);
}
使用Log4j
- 和Commons Logging是两兄弟,Commons Logging作为API写入信息,Log4j作为底层配置。
- 通过Commons Logging实现日志,不需要修改代码即可使用Log4j
- 一开始要配置一些内容,比如XML的一些配置,根据配置分配信息
使用SLF4J和logback
SLF4J和Commons log的不同
//Commons log
int score = 99;
p.setScore(score);
log.info("Set score " + score + " for Person " + p.getName() + " ok.");
//SLF4J
int score = 99;
p.setScore(score);
logger.info("Set score {} for Person {} ok.", score, p.getName());
Commons Logging | SLF4J |
---|---|
org.apache.commons.logging.Log | org.slf4j.Logger |
org.apache.commons.logging.LogFactory | org.slf4j.LoggerFactory |
logBack和SLF4J是两兄弟,logBack也是用来配置文件的,