目录
代码示例5 也可以用一个 catch 捕获所有异常(不推荐)
代码示例6 finally 无论是否存在异常,都会执行的代码块!
一、异常类型
1. 除0异常
eg 👇
public class ExceptionTest {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a/b);
}
}
2. 空指针异常
eg 👇
public class ExceptionTest {
public static void main(String[] args) {
String str = null;
System.out.println(str.equals("test"));
}
}
3. 数组下标越界
eg 👇
public class ExceptionTest {
public static void main(String[] args) {
int[] arr = {10,20,30};
System.out.println(arr[10]);// 数组下标越界,下边的代码不执行
System.out.println(arr[1]);
}
}
二、避免异常的两种方式
1. LBYL
Look Before You Leap. 在操作之前就做充分的检查。
2. EAFP
It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到 问题再处理。
三、异常的基本用法
捕获异常的基本语法
try{
有可能出现异常的语句 ;
}[catch (异常类型 异常对象) {
} ... ]
[finally {
异常的出口
}]
- try 代码块中放的是可能出现异常的代码.
- catch 代码块中放的是出现异常后的处理行为.
- finally 代码块中的代码用于处理善后工作, 会在最后执行.
- 其中 catch 和 finally 都可以根据情况选择加或者不加 .
代码示例1 不处理异常
int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
// 执行结果
before
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
代码示例2 使用 try catch 后的程序执行过程
int[] arr = {1, 2, 3};
try {
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
// 打印出现异常的调用栈
e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.main(Test.java:10)
after try catch
关于 "调用栈" 方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为 "虚 拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的 方式查看出现异常代码的调用栈。
代码示例3 catch 只能处理对应种类的异常
int[] arr = {1, 2, 3};
try {
System.out.println("before");
arr = null;
System.out.println(arr[100]);
System.out.println("after");
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println("after try catch");
// 执行结果
before
Exception in thread "main" java.lang.NullPointerException
at demo02.Test.main(Test.java:11)
此时, catch 语句不能捕获到刚才的空指针异常. 因为异常类型不匹配。
代码示例4 catch 可以有多个
public class ExceptionTest {
public static void main(String[] args) {
int[] arr = {10,20,30};
try{
arr = null;
System.out.println(arr[10]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界!");
}catch (NullPointerException e){
System.out.println("空指针异常!");
}
System.out.println("异常之后的代码");
}
}
//执行结果
空指针异常!
异常之后的代码
一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块.
如果多个异常的处理方式是完全相同, 也可以写成这样
catch (ArrayIndexOutOfBoundsException | NullPointerException e) { ... }
代码示例5 也可以用一个 catch 捕获所有异常(不推荐)
public class ExceptionTest {
public static void main(String[] args) {
int[] arr = {10,20,30};
try{
arr = null;
//假设此时有可能会出现10种部不同可能的异常
System.out.println(arr[10]);
}catch (Exception e){//只要发生异常就可以向上转型变为Exception对象
System.out.println("异常发生了!");
}
System.out.println("异常之后的代码");
}
}
//执行结果
异常发生了!
异常之后的代码
由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常。
改进版👇
public class ExceptionTest {
public static void main(String[] args) {
int[] arr = {10,20,30};
try{
//假设此时有可能会出现10种部不同可能的异常
System.out.println(arr[10]);
}catch(ArrayIndexOutOfBoundsException e){
//此处e就是异常对象,默认有JVM产生传递给catch代码段
System.out.println("数组越界了!");
//printStackTrace() 打印错误堆栈信息
e.printStackTrace();
}
System.out.println("异常之后的代码");
}
}
//执行结果
数组越界了!
异常之后的代码
java.lang.ArrayIndexOutOfBoundsException: 10
at errorCode.ExceptionTest.main(ExceptionTest.java:33)
代码示例6 finally 无论是否存在异常,都会执行的代码块!
public class ExceptionTest {
public static void main(String[] args) {
int[] arr = {10,20,30};
try{
//假设此时有可能会出现10种部不同可能的异常
System.out.println(arr[10]);
System.out.println("异常之后的代码");
}catch(ArrayIndexOutOfBoundsException e){
//此处e就是异常对象,默认有JVM产生传递给catch代码段
System.out.println("数组越界了!");
//printStackTrace() 打印错误堆栈信息
e.printStackTrace();
}finally {
System.out.println("finally代码块的代码!");
}
}
}
//执行结果
数组越界了!
finally代码块的代码!
java.lang.ArrayIndexOutOfBoundsException: 10
at errorCode.ExceptionTest.main(ExceptionTest.java:33)
运行发现,无论异常是否产生,finally代码块中的内容一定执行,那么我们就将资源关闭操作等重要操作放在finally最终执行的代码——善后处理。以打开文件为例:👇
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ExceptionTest {
public static void main(String[] args) {
//File 是 Java文件中操作类
File file = new File ("test.txt");
//输入,将文件内容输入到Scanner中。不是从键盘输入
Scanner scanner = null;
try {
scanner = new Scanner(file);
System.out.println("文件正确加载完毕");
}catch (FileNotFoundException e){
System.out.println("文件不存在!");
e.printStackTrace();
}finally {
System.out.println("文件正常处理完毕");
}
}
}
//执行结果
文件不存在!
文件正常处理完毕
能走到 finally,说明 try 和 catch 中的逻辑已经处理完毕了,只剩 finally 的最后处理操作。
代码示例7 finally 异常的返回值
public class ExceptionTest {
public static void main(String[] args) {
System.out.println(testException());
}
public static int testException(){
try{
String str = null;
System.out.println(str.equals("123"));
return 1;
}catch (NullPointerException e){
return 2;
}finally {
System.out.println("finally中的代码段");
return 3;
}
}
}
//执行结果
finally中的代码段
3
一旦finally中带了返回值,相当于try和catch的返回值就失效了,无论是否有异常产生,finally- 定会执行,因此会覆盖try和catch的返回值。
在finally中不推荐写返回值,除非返回值和异常无关,默认返回值就可放在finally中。
代码示例8 关于异常的调用链
public class ExceptionTest {
public static void main(String[] args) {
try{
exceptionChain();
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("越界异常");
e.printStackTrace();
}
}
public static void exceptionChain(){
int[] arr = new int[3];
System.out.println(arr[10]);
}
public static int testException(){
try{
String str = null;
System.out.println(str.equals("123"));
return 1;
}catch (NullPointerException e){
return 2;
}finally {
System.out.println("finally中的代码段");
return 3;
}
}
}
//执行结果
越界异常
java.lang.ArrayIndexOutOfBoundsException: 10
at errorCode.ExceptionTest.exceptionChain(ExceptionTest.java:71)
at errorCode.ExceptionTest.main(ExceptionTest.java:63)
ps: 第一行报错做关键,是程序最开始出错的地方。
代码示例9 JDK7新增的自动关闭接口
一旦一个类实现了AutoCloseable接口,就表示该类具备了自动关闭的能力-声明在try代码块中会
自动调用close方法。 try(此处创建自动关闭类的实例) {}
public class ExceptionTest {
public static void main(String[] args) {
try (CloseTest closeTest = new CloseTest()) {
} catch (Exception e) {
}
}
}
class CloseTest implements AutoCloseable {
public void close() throws Exception {
System.out.println("close方法正常执行结束……");
}
}
//执行结果
close方法正常执行结束……
代码示例10 关键字 throws
用在方法声明上,明确表示该方法有可能会产生该异常,但是不处理此异常,抛回给调用者处理
public class ExceptionTest {
public static void main(String[] args) {
try {
test();
}catch (NullPointerException e){
System.out.println("捕获空指针异常!");
e.printStackTrace();
}
}
public static void test() throws NullPointerException,ArrayIndexOutOfBoundsException{
String str = null;
System.out.println(str.length());
}
}
//执行结果
捕获空指针异常!
java.lang.NullPointerException
at errorCode.ExceptionTest.test(ExceptionTest.java:82)
at errorCode.ExceptionTest.main(ExceptionTest.java:73)
代码示例11 关键字 throw
用在方法内部,人为产生异常对象并抛出。
public class ExceptionTest {
public static void main(String[] args) {
fun();
System.out.println("fun之后的代码");
}
public static void fun(){
//人为产生一个空指针异常对象,抛出给调用者
throw new NullPointerException("抛出一个异常~");
//抛出异常后,方法就会结束
}
}
//执行结果
Exception in thread "main" java.lang.NullPointerException: 抛出一个异常~
at errorCode.ExceptionTest.fun(ExceptionTest.java:84)
at errorCode.ExceptionTest.main(ExceptionTest.java:79)
四、Java异常体系
其中Error指的是Java运行时内部错误和资源耗尽错误,应用程序不抛出此类异常。这种内部错
除了告知用户并使程序终止之外,再无能无力,这种情况很少出现。
Exception 是我们程序猿所使用的异常类的父类。
Error : 指的是程序的内部错误,这种错误我们程序员无法捕获处理,一旦发生Error错误,程序只能告知用户出现错误,程序直接退出。
StackOverflowError : 栈溢出Error 一般发生在递归调用链太深,递归没有出口。
OutOfMemoryError : 堆溢出Error。
public class ExceptionTest {
public static void main(String[] args) {
System.out.println("fun之后的代码");
recursion();
}
public static void recursion() {
recursion();//递归调用
}
}
//执行结果
fun之后的代码
Exception in thread "main" java.lang.StackOverflowError
at errorCode.ExceptionTest.recursion(ExceptionTest.java:91)
Java的异常体系分为两大类
非受查异常
所有的非受查异常不强制程序使用try catch块处理。Error以及RuntimeException(运行时异常,空指针,类型转换,数组越界)及其子类都是非受查异常。如下代码所示👇
public class ExceptionTest {
public static void main(String[] args) {
test();
try {
test1();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void test1() throws FileNotFoundException {
File file = new File("test1.text");
Scanner scanner = new Scanner(file);
}
// 调用test方法有可能会产生空指针异常,但是test方法不处理此异常,谁调用谁处理
// 空指针和数组越界异常都属于非受查异常,可以不显示的进行try...catch
public static void test() throws NullPointerException,ArrayIndexOutOfBoundsException {
String str = null;
System.out.println(str.length());
}
}
//执行结果
Exception in thread "main" java.lang.NullPointerException
at errorCode.ExceptionTest.test(ExceptionTest.java:107)
at errorCode.ExceptionTest.main(ExceptionTest.java:92)
受查异常
受查异常必须显示使用try…catch…异常处理,或者throws抛出。除了Error和RuntimeExcpetion以及其子类的其他异常都是受查异常,必须显示处理。
自定义异常类
程序开发中,一定会有一些错误是和具体的业务相关的, 这种错误JDK是不可能提供相应的异常类,此时我们就需要继承已有的异常类,产生自定义的异常类。
若需要用户强制进行异常处理,继承Exception父类-受查异常。
若不需要用户显示处理异常,继承RuntimeException父类 —— 非受查异常。
import java.util.Scanner;
public class Login {
private static String userName = "xox";
private static String password = "123";
public static void main(String[] args) {
try {
login();
} catch (UserError userError) {
userError.printStackTrace();
} catch (PasswordError passwordError) {
passwordError.printStackTrace();
}
}
public static void login() throws UserError, PasswordError {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String userName = scanner.next();
System.out.println("请输入密码:");
String password = scanner.next();
if (!Login.userName.equals(userName)) {
throw new UserError("用户名错误");
}
if (!Login.password.equals(password)) {
throw new PasswordError("密码错误");
}
System.out.println("登陆成功");
}
}
class UserError extends Exception {
public UserError(String message) {
super(message);
}
}
class PasswordError extends Exception {
public PasswordError(String message) {
super(message);
}
}
五、内部类
所谓的内部类,就是将类结构的定义套在另一个类的内部。
内部类-共分为以下四种:成员内部类类比成员方法,静态内部类类比静态方法,方法内部类,匿名内部类- Lambda表达式的前身。
成员方法能访问静态域不能拥有静态域(无法定义静态变量)。
静态方法能访问静态域不能访问成员域。
1.成员内部类
直接定义在类中,不加任何修饰符(static)定义的类就是成员内部类 内部类和外部类可以方便的互相访问彼此的private域。
public class Otter {
// 心脏和发动机都属于私有内部类,对外部完全隐藏,只是在类的内部来使用
private class Inner {
}
}
2.内部类的使用方法/规则
a. 成员内部类的创建需要依赖外部类对象,在没有外部类对象之前,无法创建成员内部类对象的(心脏就是一个成员内部类,在没有人体的情况下,是无法直接创建心脏这个对象)
b. 内部类是一个相对独立的实体,与外部类不是is - a关系(心脏is not a人)
c. 内部类可以直接访问外部类的元素和方法(包括私有域)外部类必须通过内部类的对象来访问内部类的元素和方法(包括私有域)
d. 成员内部类对象的创建外部类的内部创建就和使用其他类没啥区别内部类名称内部类引用= new内部类();
public class Otter {
private String msg = "outter类中的msg属性";
// 心脏和发动机都属于私有内部类,对外部完全隐藏,只是在类的内部来使用
private class Inner {
private int num = 10;
public void test() {
// 直接访问外部类的msg属性
System.out.println(msg);
}
}
public void fun() {
// 通过内部类对象访问内部类的私有属性
Inner inner = new Inner();
// 访问inner类的私有属性
System.out.println(inner.num);
inner.test();
}
}
在外部类的外部创建内部类对象-内部类要对外部可见(访问权限问题) 外部类名称.内部类引用F new外部类().new内部类(); Otter.Inner inner = new Otter().new Inner(); inner.test();
e. 使用内部类可以曲线救国来实现"多继承” (了解)
class A{
int numA = 10;
}
class B{
int numB = 20;
}
public class E {
class C extends A{}
class D extends B{}
public void test() {
C c = new C();
D d = new D();
System.out.println(c.numA);
System.out.println(d.numB);
}
public static void main(String[] args) {
E e = new E();
e.test();
}
}
//执行结果
10
20
本节完^_^