Java学习_异常处理
- Java的异常
计算机程序运行的过程中,总是会出现各种各样的错误。有一些错误是用户造成的,比如,希望用户输入一个
int
类型的年龄,但是用户的输入是abc。程序想要读写某个文件的内容,但是用户已经把它删除了。
还有一些错误是随机出现,并且永远不可能避免的。比如:- 网络突然断了,连接不到远程服务器;
- 内存耗尽,程序崩溃了;
- 用户点“打印”,但根本没有打印机;
- ……
Java内置了一套异常处理机制,总是使用异常来表示错误。异常是一种
class
,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了。1 try {
2 String s = processFile(“C:\\test.txt”);
3 // ok:
4 } catch (FileNotFoundException e) {
5 // file not found:
6 } catch (SecurityException e) {
7 // no read permission:
8 } catch (IOException e) {
9 // io error:
10 } catch (Exception e) {
11 // other error:
12 }- Java的异常是
class
,它的继承关系如下: - 从继承关系可知:
Throwable
是异常体系的根,它继承自Object
。Throwable
有两个体系:Error
和Exception
,Error
表示严重的错误,程序对此一般无能为力。 Exception
则是运行时的错误,它可以被捕获并处理。Exception
又分为两大类:RuntimeException
以及它的子类;- 非
RuntimeException
(包括IOException
、ReflectiveOperationException
等等)
- Java规定:
必须捕获的异常,包括
Exception
及其子类,但不包括RuntimeException
及其子类,这种类型的异常称为Checked Exception。不需要捕获的异常,包括
Error
及其子类,RuntimeException
及其子类。
- 编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。
捕获异常
捕获异常使用
try...catch
语句,把可能发生异常的代码放到try {...}
中,然后使用catch
捕获对应的Exception
及其子类。1 import java.io.UnsupportedEncodingException;
2 import java.util.Arrays;
3
4 public class Main {
5 public static void main(String[] args) {
6 byte[] bs = toGBK("中文");
7 System.out.println(Arrays.toString(bs));
8 }
9
10 static byte[] toGBK(String s) {
11 try {
12 // 用指定编码转换String为byte[]:
13 return s.getBytes("GBK");
14 } catch (UnsupportedEncodingException e) {
15 // 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:
16 System.out.println(e); // 打印异常信息
17 return s.getBytes(); // 尝试使用用默认编码
18 }
19 }
20 }- 如果我们不捕获
UnsupportedEncodingException(去掉try catch)
,会出现编译失败的问题。编译器会报错,错误信息类似:unreported exception UnsupportedEncodingException; must be caught or declared to be thrown,并且准确地指出需要捕获的语句是return s.getBytes("GBK");
。意思是说,像UnsupportedEncodingException
这样的Checked Exception,必须被捕获。 - 这是因为
String.getBytes(String)
方法定义是(如下),在方法定义的时候,使用throws Xxx
表示该方法可能抛出的异常类型(Runtime及其子类除外)。调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
} - 在
toGBK()
方法中,因为调用了String.getBytes(String)
方法,就必须捕获UnsupportedEncodingException
。我们也可以不捕获它,而是在方法定义处用throws表示toGBK()
方法可能会抛出UnsupportedEncodingException
,就可以让toGBK()
方法通过编译器检查。1 import java.io.UnsupportedEncodingException;
2 import java.util.Arrays;
3
4 public class Main {
5 public static void main(String[] args) {
6 byte[] bs = toGBK("中文");
7 System.out.println(Arrays.toString(bs));
8 }
9
10 static byte[] toGBK(String s) throws UnsupportedEncodingException {
11 return s.getBytes("GBK");
12 }
13 } - 只要是方法声明的Checked Exception,不在调用层捕获,也必须在更高的调用层捕获。所有未捕获的异常,最终也必须在
main()
方法中捕获,不会出现漏写try
的情况。这是由编译器保证的。main()
方法也是最后捕获Exception
的机会。 - 如果是测试代码,上面的写法就略显麻烦。如果不想写任何
try
代码,可以直接把main()
方法定义为throws Exception。
因为main()
方法声明了可能抛出Exception
,也就声明了可能抛出所有的Exception
,因此在内部就无需捕获了。代价就是一旦发生异常,程序会立刻退出。 - 在
toGBK()
内部“消化”异常static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 什么也不干
}
return null;
} - 这种捕获后不处理的方式是非常不好的,即使真的什么也做不了,也要先把异常记录下来:
static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
// 先记下来再说:
e.printStackTrace();
}
return null;
} - 所有异常都可以调用
printStackTrace()
方法打印异常栈,这是一个简单有用的快速打印异常的方法。
- 捕获异常
- 凡是可能抛出异常的语句,都可以用
try ... catch
捕获。把可能发生异常的语句放在try { ... }
中,然后使用catch
捕获对应的Exception
及其子类。 可以使用多个
catch
语句,每个catch
分别捕获对应的Exception
及其子类。JVM在捕获到异常后,会从上到下匹配catch
语句,匹配到某个catch
后,执行catch
代码块,然后不再继续匹配。简单地说就是:多个
catch
语句只有一个能被执行。- 存在多个
catch
的时候,catch
的顺序非常重要:子类必须写在前面。 无论是否有异常发生,都希望执行一些语句,例如清理工作,可以把执行语句写若干遍:正常执行的放到
try
中,每个catch
再写一遍。1 public static void main(String[] args) {
2 try {
3 process1();
4 process2();
5 process3();
6 System.out.println("END");
7 } catch (UnsupportedEncodingException e) {
8 System.out.println("Bad encoding");
9 System.out.println("END");
10 } catch (IOException e) {
11 System.out.println("IO error");
12 System.out.println("END");
13 }
14 }- Java的
try ... catch
机制还提供了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");
}
} finally
有几个特点:finally
语句不是必须的,可写可不写;finally
总是最后执行。
- 某些情况下,可以没有
catch
,只使用try ... finally
结构。void process(String file) throws IOException {
try {
...
} finally {
System.out.println("END");
}
}因为方法声明了可能抛出的异常,所以可以不写
catch
。
- 凡是可能抛出异常的语句,都可以用
- 抛出异常
- 查看
Integer.java
源码可知,抛出异常的方法代码如下。public static int parseInt(String s, int radix) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
...
} 查看
Integer.java
源码可知,抛出异常的方法代码如下当发生错误时,例如,用户输入了非法的字符,我们就可以抛出异常。如何抛出异常?参考Integer.parseInt()
方法,抛出异常分两步:- 创建某个
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();
}
} - 在代码中获取原始异常可以使用
Throwable.getCause()
方法。如果返回null
,说明已经是“根异常”了。 - 捕获到异常并再次抛出时,一定要留住原始异常,否则很难定位第一案发现场!
- 创建某个
- 在
try
或者catch
语句块中抛出异常,不会影响finally
的执行。JVM会先执行finally
,然后抛出异常。 - 如果在执行
finally
语句时抛出异常,那么,finally
抛出异常后,原来在catch
中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("catched");
throw new RuntimeException(e);
} finally {
System.out.println("finally");
throw new IllegalArgumentException();
}
}
} 输出: catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
at Main.main(Main.java:11) - 在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用
origin
变量保存原始异常,然后调用Throwable.addSuppressed()
,把原始异常添加进来,最后在finally
抛出。(通过Throwable.getSuppressed()
可以获取所有的Suppressed Exception
。绝大多数情况下,在finally
中不要抛出异常。因此,通常不需要关心Suppressed Exception
。)
1 public class Main {
2 public static void main(String[] args) throws Exception {
3 Exception origin = null;
4 try {
5 System.out.println(Integer.parseInt("abc"));
6 } catch (Exception e) {
7 origin = e;
8 throw e;
9 } finally {
10 Exception e = new IllegalArgumentException();
11 if (origin != null) {
12 e.addSuppressed(origin);
13 }
14 throw e;
15 }
16 }
17 }
- 查看
- 自定义异常
在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。一个常见的做法是自定义一个
BaseException
作为“根异常”,然后,派生出各种业务类型的异常。BaseException
需要从一个适合的Exception
派生,通常建议从RuntimeException
派生。public class BaseException extends RuntimeException {
}
//其他业务类型的异常就可以从BaseException
派生:public class UserNotFoundException extends BaseException {
} public class LoginFailedException extends BaseException {
}
...//自定义的
BaseException
应该提供多个构造方法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);
}
}上述构造方法实际上都是原样照抄
RuntimeException
。这样,抛出异常的时候,就可以选择合适的构造方法。通过IDE可以根据父类快速生成子类的构造方法。
NullPointerException
NullPointerException
即空指针异常,俗称NPE。如果一个对象为null
,调用其方法或访问其字段就会产生NullPointerException
,这个异常通常是由JVM抛出的。- 指针这个概念实际上源自C语言,Java语言中并无指针。我们定义的变量实际上是引用,Null Pointer更确切地说是Null Reference。
NullPointerException
是一种代码逻辑错误,遇到NullPointerException
,遵循原则是早暴露,早修复,严禁使用catch
来隐藏这种编码错误。- 成员变量在定义时初始化使用空字符串
""
而不是默认的null
可避免很多NullPointerException
,编写业务逻辑时,用空字符串""
表示未填写比null
安全得多。 - 如果调用方一定要根据
null
判断,比如返回null
表示文件不存在,那么考虑返回Optional<T>
public Optional<String> readFromFile(String file) {
if (!fileExist(file)) {
return Optional.empty();
}
...
}这样调用方必须通过
Optional.isPresent()
判断是否有结果。 定位NullPointerException
- 从Java 14开始,如果产生了
NullPointerException
,JVM可以给出详细的信息告诉我们null
对象到底是谁。 这种增强的
NullPointerException
详细信息是Java 14新增的功能,但默认是关闭的,我们可以给JVM添加一个-XX:+ShowCodeDetailsInExceptionMessages
参数启用它:java -XX:+ShowCodeDetailsInExceptionMessages Main.java
- 从Java 14开始,如果产生了
使用断言
- 断言(Assertion)是一种调试程序的方式。在Java中,使用
assert
关键字来实现断言。public static void main(String[] args) {
double x = Math.abs(-123.45);
assert x >= 0;
System.out.println(x);
}语句
assert x >= 0;
即为断言,断言条件x >= 0
预期为true
。如果计算结果为false
,则断言失败,抛出AssertionError
。 - 使用
assert
语句时,还可以添加一个可选的断言消息。assert x >= 0 : "x must >= 0";
断言失败的时候,
AssertionError
会带上消息x must >= 0
,更加便于调试。
- 断言(Assertion)是一种调试程序的方式。在Java中,使用
使用JDK Logging
- Java标准库内置了日志包
java.util.logging
,可以直接用。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...");
}
} 日志的输出可以设定级别。JDK的Logging定义了7个日志级别,从严重到普通:
- SEVERE
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
- Java标准库内置了日志包
因为默认级别是INFO,因此,INFO级别以下的日志,不会被打印出来。使用日志级别的好处在于,调整级别,就可以屏蔽掉很多调试相关的日志输出。
Commons Logging(理解)
Commons Logging是一个第三方日志库,它是由Apache创建的日志模块。Commons Logging的特色是,它可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。
使用Commons Logging只需要和两个类打交道,并且只有两步:第一步,通过
LogFactory
获取Log
类的实例; 第二步,使用Log
实例的方法打日志。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.");
}
}
使用Log4j
使用SLF4J和Logback
Java学习_异常处理的更多相关文章
- Java学习之异常处理
在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出),Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性. Throwabl ...
- JAVA学习之 异常处理机制
今天就来说说java的异常处理机制,异常处理不是第一接触,尤其是写过非常多c#的代码,基本都会写到异常处理的代码,事实上c#的异常处理与java的异常处理基本都是一样的,仅仅是在一些细节上不是非常一样 ...
- Java学习_面向对象编程
抽象类 一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰.因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译 ...
- Java学习_反射
什么是反射? 反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息. 反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法. JAVA反射机制是在运行状 ...
- Java学习_注解
使用注解 注解是放在Java源码的类.方法.字段.参数前的一种特殊"注释". 1 // this is a component: 2 @Resource("hello&q ...
- Java学习_泛型
什么是泛型. Java标准库提供的ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当"可变数组". public class ArrayLi ...
- Java学习_常见异常
JAVA常见异常 Java.io.NullPointerException null 空的,不存在的 NullPointer 空指针 空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象 ...
- java学习_文件工具类
工具类里面的方法全部都是静态的,调用的时候不需要实例化
- Java 学习(10):java 异常处理
java 异常处理 异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. 要打开的文件不存在. 网络通信时连接中断,或者JVM内存溢出. 三种类型的异常: 检查性异常: 最具代表的检查性异 ...
随机推荐
- 三. Vue组件化
1. 认识组件化 1.1 什么是组件化 人面对复杂问题的处理方式 任何一个人处理信息的逻辑能力都是有限的,所以当面对一个非常复杂的问题时我们不太可能一次性搞定一大堆的内容. 但是我们人有一种天生的能力 ...
- Guava中EventBus分析
EventBus 1. 什么是EventBus 总线(Bus)一般指计算机各种功能部件之间传送信息的公共通信干线,而EventBus则是事件源(publisher)向订阅方(subscriber)发送 ...
- 17.java设计模式之观察者模式
基本需求: 气象站可以将每天测量到的温度,湿度,气压等等,以公告的形式发布出去(比如发布到自己的网站或第三方) 需要设计开放型API,便于其他第三方也能接入气象站获取数据 提供温度.气压和湿度的接口 ...
- this大全,还有谁??????!!!!!!!
this在函数调用时创建,一般的对象没有this,全局window可以理解为一个函数,他有一个全局this JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系. 函数里thi ...
- 第2.2节 Python的语句
上节已经介绍了极简的Python代码编写,已经用到了赋值语句,本节对Python的程序语句进行介绍. 一. 常用命令 在介绍Python语句之前,先介绍一下几个有用的Python命令. dir(模块名 ...
- 老猿Python重难点知识博文汇总
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 除了相关教程外,老猿在学习过程中还写了大量的学习随笔,内容比较杂,文章内容也参差不齐,为了方便,老猿 ...
- 问题:PyCharm调试方法Force run to cursor与run to cursor的区别
Force run to cursor与run to cursor的差别是,后者在执行到光标的代码行前,如果有代码中设置了断点,会在该断点处暂停,等待进一步调试指令,而Force run to cur ...
- PyQt(Python+Qt)学习随笔:Designer(设计师)中部件属性编辑的cursor(光标样式)属性
部件(又称为组件或控件)的cursor属性保存该部件的鼠标光标形状,当鼠标位于该部件上时就会呈现该属性设置的光标形状,对应类型为枚举类型Qt.CursorShape,可取值的范围可以在Qt文档官网:h ...
- 【Docker】 .Net Core 3.1 webapi 集成EF Code First,使用MySql进行业务操作 、配置swagger (三)
系列目录: [Docker] CentOS7 安装 Docker 及其使用方法 ( 一 ) [Docker] 使用Docker 在阿里云 Centos7 部署 MySQL 和 Redis (二) [D ...
- js- 实现属性名的拼接 obj['name']
obj.name---->obj[name] 这两种调用方式一样,使用obj.name内部转换成 obj['name'], 使用obj['name']更快. obj['name'] 里面必须是 ...