目录
前言
平时写代码时,经常出现编译时一切正常,运行时就出现错误了,例如下面这段代码
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[10]);
}
程序编译时没有报错,而运行时就出现了异常,这就是常见的数组越界异常,由于数组中只有三个元素,索引10是不存在的。
我们探讨的异常就是运行时抛出的程序错误,而不是编译时的错误
避免异常的两种方式:
-
LBYL : Look Before You Leap. 在操作之前就做充分的检查 .
-
EAFP: It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易"。 也就是先操作, 遇到问题再处理。
异常的核心思想就是EAFP.
基本语法
try {有可能出现异常的语句 ;}[ catch ( 异常类型 异常对象 ) {//出现异常后的处理方式} ... ][ finally {异常的出口}]
-
try 代码块中放的是可能出现异常的代码,不论异常是否发生,都会执行
-
catch 和 finally 都可以根据情况选择加或者不加.
示例:
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
System.out.println(arr[10]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界啦!");
}
System.out.println(arr[0]);
}
//输出:数组下标越界啦!
1
可以看到,将可能会报错的代码放在try中,并用catch进行捕获,当出现异常后,不影响异常代码段之后的正常代码。
多个catch快的使用:
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
arr = null;
System.out.println(arr[10]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界啦!");
}
System.out.println("异常之后的代码");
}
这里程序为什么运行时报错呢?可以发现,数组越界和空指针不是一个异常类型, 因此这个catch代码段就捕获不到空指针异常。这是,我们就需要增加catch块:
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
arr = null;
System.out.println(arr[10]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界啦!");
}catch (NullPointerException e){
System.out.println("空指针异常");
}
System.out.println("异常之后的代码");
}
//输出:空指针异常
异常之后的代码
但是,若是一个程序有多种异常,甚至我们也不太清楚异常类型时,使用多个代码块就不高明了。这时,我们使用异常的共同父类Exception来捕获异常。
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
arr = null;
System.out.println(arr[10]);
}catch (Exception e){
System.out.println("异常发生了!");
}
System.out.println("异常之后的代码");
}
//输出:异常发生了!
异常之后的代码
程序的异常错误输出——printStackTrace( )
在Java种,一切皆对象,异常也是对象,上面写的空指针, 数组越界,Exception 都是异常类
当产生错误时,JVM会构造一个相应的(相对应的异常类)异常对象传递给程序。例如Exception e 的 e就是异常类Exception类的对象。
当我们捕获异常之后,虽然知道有异常,但不会像运行时出错一样为我们标记出异常所在位置。这时,我们就使用e.printStackTrace(); 来找到异常位置。
public static void main(String[] args) {
int[] arr = {1,2,3};
try {
System.out.println(arr[10]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组越界异常!");
//打印错误堆栈信息,也就是错误开始的位置
e.printStackTrace();
}
System.out.println("异常之后的代码");
}
关于异常的调用链
public static void main(String[] args) {
try {
execeptionChain();
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("越界异常");
e.printStackTrace();
}
}
public static void execeptionChain(){
int[] arr = new int[3];
//此处有异常但是没有处理
//将此异常抛出给调用者
System.out.println(arr[10]);
}
在execeptionChain方法中的异常没有处理,在main方法调用它的时候,main来处理。异常会随着调用链不断向上传递,直到有一处捕获异常并处理为止。若调用过程都没有处理异常,最终会将异常抛到JVM,程序就终止了。
可以看到调用过程如下:
JDK7新增的自动关闭接口——AutoCloseable
一旦一个类实现了AutoCloseable接口, 就表示该类具备了自动关闭的能力,声明在try代码块中会
自动调用close方法。
try(此处创建自动关闭类的实例) {
}
class CloseTest implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("close方法正常执行结束");
}
}
public class ExceptionTest {
public static void main(String[] args) {
try(CloseTest closeTest = new CloseTest()) {
}catch ( Exception e){
}
}
}
//输出:close方法正常执行结束
可以看到,不需要我们显示调用close方法就可以自动关闭。
throws关键字
用在方法声明上,明确表示该方法有可能会产生该异常,但是不处理此异常,抛回给调用者处理。一般,我们搭配自定义异常来使用
示例:
public class ThrowsTest {
public static void main(String[] args) {
try {
test();
}catch (NullPointerException e){
System.out.println("空指针异常");
e.printStackTrace();
}
}
//调用test方法有可能会产生空指计异常,和越界异常但是test方法不处理这个异常。谁调用谁处理
public static void test() throws NullPointerException,ArrayIndexOutOfBoundsException{
String str = null;
System.out.println(str.length());
}
}
throw关键字
用在方法内部,人为产生异常对象并抛出。
示例:
public static void main(String[] args) {
fun();
System.out.println("fun方法之后的代码");
}
public static void fun(){
//人为产生了一个异常对象,抛出给调用者
throw new NullPointerException("没事干,抛个异常玩玩");
//抛出异常后,方法就会结束
}
异常体系结构
- Error指的是Java运行时内部错误和资源耗尽错误,应用程序不抛出此类异常。这种内部错误一旦出现,除了告知用户并使程序终止之外,再无能无力,这种情况很少出现。例如:StackOverflowError :栈溢出Error 一般发生在递归调用链太深,递归没有出口
OutOfMemoryError :堆溢出Error - Exception是我们程序猿所使用的异常类的父类
Java的异常体系分为两大类:
非受查异常
上图中蓝色方框以及其子类都属于非受查异常。所有的非受查异常不强制程序使用try catch块处理。
Error以及RuntimeException(运行时异常,空指针,类型转换,数组越界)及其子类都是非受查异常
受查异常
上图中红色方框以及其子类都属于受查异常除了非受查异常外都是受查异常,必须显示使用try catch异常处理,或者throws抛出。
除了Error和RuntimeExcpetion以及其子类的其他异常都是受查异常,必须显示处理。
受查异常必须做处理,可以将异常抛给主方法
public static void main(String[] args) throws FileNotFoundException {
test();
test1();
}
或者使用try catch
try {
test1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
自定义异常类
程序开发中,一定会有一些错误是和具体的业务相关的,这种错误JDK是不可能提供相应的异常类,此时我们就需要继承已有的异常类,产生自定义的异常类。例如:登录时的用户名错误、密码错误
若需要用户强制进行异常处理,继承Exception父类——受查异常
若不需要用户显示处理异常,继承RuntimeException父类 ——非受查异常
示例:
import java.util.Scanner;
public class Login {
public static final String USERNAME = "张三";
public static final String PASSWORD = "123456";
public static void main(String[] args) {
try {
login();
System.out.println("登录成功");
}catch (UsernameException e){
e.printStackTrace();
}catch (PasswordException e){
e.printStackTrace();
}
}
public static void login(){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = scanner.next();
System.out.println("请输入密码:");
String word = scanner.next();
if(!name.equals(USERNAME)){
//抛出用户名错误异常
throw new UsernameException("用户名错误");
}
if(!PASSWORD.equals(word)){
throw new PasswordException("密码错误");
}
}
//用户名异常
static class UsernameException extends RuntimeException{
public UsernameException(String msg){
super(msg);
}
}
//密码异常
static class PasswordException extends RuntimeException{
public PasswordException(String msg){
super(msg);
}
}
}