✨你好啊,我是“ 罗师傅”,是一名程序猿哦。
🌍主页链接:楚门的世界 - 一个热爱学习和运动的程序猿
☀️博文主更方向为:分享自己的快乐 briup-jp3-ing
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!

前言

目标:Java基础编程,熟练Java开发语法和规则,养成良好编程习惯

异常

概述

程序在运行过程中,由于意外情况导致程序发生异常事件,默认情况下发生的异常会终端程序的运行。在Java中,把常见的异常情况,都抽象成了对应的异常类型,那么每种异常类型都代表了一种特定的异常情况。

当程序中出现一种异常情况时,也会创建并抛出一个异常类型对象,这个对象就表示当前程序所出向的问题。

  • java.lang.ArrayIndexOutOfBoundsException
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
int[] arr = {1,3,5,7};
System.out.println(arr[2]);
//这行代码执行时,出现异常情况,因为下标超过了数组的最大边界
System.out.println(arr[4]);
}
}
//运行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at com.briup.demo.Test.main(Test.java:7)

当前程序出现异常情况时,会创建并抛出和该异常情况对应的异常类的对象,这个异常对象中保存了一些信息,用来表示当前程序到底发生了什么异常情况

通过异常信息,我们可以定位异常发生的位置,以及异常发生的原因。

异常体系

  • Throwable:表示可以被抛出的
  • Error:表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理。
  • Exception:表示异常情况,程序中出了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行。

注意,我们一般说的异常,都是指的Exception

Exception中并没有定义方法,它的方法都是从Throwable中继承过来的,其中常用的方式有:

  • printStackTrace(), 打印输出当前发送异常的详细信息
  • getMessage(),返回异常对象被抛出的时候,所携带的信息,一般是异常的发生原因
  • printStackTrace(PrintWriter s) ,方法重载,可以指定字符输出流,对异常信息进行输出
  • printStackTrace(PrintStream s) ,方法重载,可以指定字节输出流,对异常信息进行输出

异常种类

  • 编译时异常

编译时异常,继承自Exception类,也称为checked exception,编译器在编译期间,会主动检查这种异常,发现后会报错,并提示我们要对这种异常进行处理。

  • 运行时异常

运行时异常,继承自RuntimeException类,也称为 unchecked exception, 编译器在编译期间,不会检查这种异常,也不要求我们去处理,但是在运行期间,代码中可能会抛出这种类型的异常。

  • Error
    • StackOverflowError - 栈溢出错误:递归调用导致栈空间耗尽。
    • OutOfMemoryError - 内存溢出错误:内存不足以容纳应用程序所需的对象。
    • NoClassDefFoundError (类未定义错误) - 在运行时找不到某个类的定义。
    • UnsatisfiedLinkError (链接库未满足错误) - 本地方法无法找到对应的本地库实现。
    • InternalError (内部错误) - Java虚拟机遇到内部错误。
    • VirtualMachineError (虚拟机错误) - 通用的虚拟机错误类。
    • AssertionError (断言错误) - 断言失败时抛出,通常用于调试和测试。
    • UnknownError (未知错误) - 未知的错误类型。
  • Exception
    • IOException (输入/输出异常) - 在I/O操作期间可能发生的异常。
    • ClassNotFoundException (类未找到异常) - 尝试加载不存在的类时抛出的异常。
    • SQLException (SQL异常) - 在执行SQL查询时可能发生的异常。
    • FileNotFoundException (文件未找到异常) - 尝试访问不存在的文件时抛出的异常。
    • InstantiationException (实例化异常) - 在创建类的实例时遇到问题时抛出的异常。
    • IllegalAccessException (非法访问异常) - 在访问类的成员时权限不足时抛出的异常。
    • InterruptedException (中断异常) - 线程在等待或休眠期间被中断时抛出的异常。
    • NoSuchMethodException (方法未找到异常) - 尝试调用不存在的方法时抛出的异常。
    • NoSuchFieldException (字段未找到异常) - 尝试访问不存在的字段时抛出的异常。
    • CloneNotSupportedException (克隆不支持异常) - 尝试克隆不支持克隆的对象时抛出的异常。
  • RuntimeException (记得时Exception的子类哈~)
    • NullPointerException (空指针异常) - 尝试在引用为空的对象上调用方法或访问属性时抛出的异常。
    • ArrayIndexOutOfBoundsException (数组索引越界异常) - 尝试访问数组中不存在的索引时抛出的异常。
    • IllegalArgumentException (非法参数异常) - 传递给方法的参数不合法时抛出的异常。
    • ArithmeticException (算术异常) - 发生算术错误时抛出的异常,如除以零。
    • ClassCastException (类型转换异常) - 尝试将对象强制转换为不兼容的类型时抛出的异常。
    • NumberFormatException (数字格式异常) - 将字符串转换为数字时,字符串格式不正确时抛出的异常。
    • UnsupportedOperationException (不支持操作异常) - 尝试调用不支持的方法时抛出的异常。
    • ConcurrentModificationException (并发修改异常) - 在使用迭代器遍历集合时,另一个线程修改了集合。

异常传播

如果一个方法中抛出了异常,并且一直没有进行处理,那么这个异常将会抛给当前方法的调用者,并一直向上抛出直到抛给JVM,最后JVM将这个异常信息打印输出,同时程序运行的停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("start");
test1();
System.out.println("end");
}

public static void test1() {
test2();
}

public static void test2() {
test3();
}

public static void test3() {
int a = 1 / 0;
}
}

可以看出,异常信息打印输出的内容,就是发生异常的时候,栈区中方法调用的情况!

如果,在异常传播的过程中,任何一个地方对异常进行了处理,那么JVM不会停止,程序还会正常往下运行!

异常抛出

自动抛出异常

当前java代码中,出现了提前指定好的异常情况的时候,代码会自动创建异常对象,并且将该异常对象抛出。

例如,当代码中执行 int a = 1/0; 的时候,代码会自动创建并抛出 ArithmeticException 类型的 异常对象,来表示当前的这种异常情况。(算术异常)

例如,当前代码中执行 String str = null; str.toString(); 的时候,代码会自动创建并抛出 NullPointerException 类型的异常对象,来表示当前这种异常情况。(空指针异常)

手动抛出异常

以上描述的异常情况,都是JVM中提前规定好的,我们不需要干预,JVM内部自己就会创建并爆出异常对象。

我们可手动创建并抛出异常对象,其效果也是一样的。

  • RuntimeException
1
2
3
4
5
public void test(String name) {
if (!"lwsj".equals(name)) {
throw new RuntimeException("用户名不匹配!");
}
}

注意,因为方法中抛出的是一个运行时异常,编译器不会做出检查,所以代码可以正常的编译运 行,但是运行的时候,name的值不是tom的时候,代码会报错,这个错误信息是我们自己抛出的

  • Exception
1
2
3
4
5
public void test(String name) throws Exception {
if (!"lwsj".equals(name)) {
throw new Exception("用户名不匹配!");
}
}

使用throws关键字,声明方法所抛出的异常类型即可

这个声明的目的,就是告诉test方法的调用者,你调用我的这个test方法的时候要小心啦,方法在运行的时候可能会抛出Exception类型的异常

这里描述为可能会抛出异常的原因是,只有name的值不是tom的时候才会抛出异常,其他情况没 有异常!

可以看出, forName 就声明了,方法在执行过程中可能会抛出 ClassNotFoundException 类型 的异常

同时, ClassNotFoundException 属于编译异常,所以我们调用 forName 方法时候就要处理这 个异常,或者将异常继续抛出!

思考,为什么方法内抛出异常对象使用关键字throw,方法上声明异常使用的是throws?

  • 声明位置
    • throw 方法内部声明
    • throws 方法i上声明
  • 抛出异常的个数
    • throw 每一个只能抛出一个异常对象
    • throws 可以抛出多个异常类的声明
  • 关键字的作用
    • throw 抛出方法或代码快中的异常,编译时异常和运行时异常都可以被抛出
    • throws 标识该方法可能抛出的异常列表,调用者需要将异常范围从小到大进行catch

异常捕获

概述

  • 把这个异常在方法上进行声明抛出
  • 把这个异常在方法内进行捕获处理
1
2
3
4
// 声明抛出
public void test(String className)throws ClassNotFoundException{
Class.forName(className);
}

可以看出,这里我们并没有处理forName方法抛出的异常,而是将这个异常继续声明抛出,那么将来谁调用我们的test方法,谁就要处理这个异常情况

1
2
3
4
5
6
7
8
// 捕获异常
public void test(String className) {
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

这里使用了try-catch语句块,对可能抛出异常的代码进行异常捕获处理

try-catch

try-catch语句块,就是用来对指定代码,进行异常补货处理,并且处理完成后,JVM不会停止运行,代码仍然可以正常的往下运行!

捕获异常语法如下:

1
2
3
4
5
try{
// 编写可能会出现异常的代码
} catch(异常类型 e) {
// 处理异常的代码,可以是简单的输出异常信息,也可以使用日志进行了记录,可以对数据进行修改纠正等操作
}

try:该代码块中编写可能产生异常的代码。

catch:用来进行某种异常的捕获,并对捕获到的异常进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.briup.chap09.test;

public class Test {
public static void main(String[] args) throws Exception {
System.out.println("peter");
Test test = new Test();
try {
test.test("haha");
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("parker");
}

public void test(String name) throws Exception {
if (!"lwsj".equals(name)) {
throw new Exception("用户名不匹配");
}
}
}

捕获多种异常

  • 第一种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) throws Exception {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (ClassNotFoundException | NoSuchMethodException |
IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

这里,使用一个catch语句,里面使用 | 来表示捕获多种不同的异常类型

  • 第二种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) throws Exception {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}

这里,使用了四个catch语句,分别对四种不同的异常类型进行捕获处理

注意:子类异常先catch,父类异常后catch

  • 第三种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}

这里,使用了一个catch语句,但是捕获的异常类型是Exception,它是最大的异常类型,由于多态的 原因,Exception类型的引用e,可以捕获接收到任意类型的异常对象

finally语句

  • 不使用finally

其实只要使用 finally 关键,就可以保证指定代码一定会执行,无论是否发生异常!

自定义异常

自定义异常的原因

如何自定义异常

  • 如果要自定义一个编译时异常类型,就自定义一个类,并继承 Exception
  • 如果要自定义一个运行时异常类型,就自定义一个类,并继承 RuntimeException

如,自定义编译时异常类型,通过名字可知,这是在用户登录期间发生异常时,应该创建并抛出的异 常类型

1
2
3
4
5
6
7
public class LoginExceptin extends Exception{
public LoginExceptin(){
}
public LoginExceptin(String message) {
super(message);
}
}

例如,自定义运行时异常类型,通过名字可知,这是在修改用户信息期间发生异常时,应该创建并抛出 的异常类型

1
2
3
4
5
6
7
public class ModifyUserInfoExceptin extends RuntimeException{
public ModifyUserInfoExceptin(){
}
public ModifyUserInfoExceptin(String message) {
super(message);
}
}

当前我们在系统的日志信息中看到 LoginExceptinModifyUserInfoExceptin 这俩种异常类型的 信息时,就知道是用户在登录和修改信息的时候,出现了问题。

使用枚举实现:

  • 枚举代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.briup.chap09.exception;

public enum StudentMessageCode {

STUDENT_INFO_NOT_EXIST(10001, "学生信息不存在"), STUDENT_INFO_EXIST(10002, "学生信息已存在在"),
STUDENT_ID_NOT_UPDATE(10003, "学生信息中学号无法修改"), TEACHER_ACCESS_DENIED(20001, "老师权限不足"),
DATA_TYPE_MISMATCHED(30001, "数据类型不匹配"), USERNAME_PASSWORD_ERROR(40000, "账号密码错误");

private int code;
private String msg;

private StudentMessageCode() {

}

private StudentMessageCode(int code, String msg) {
this.code = code;
this.msg = msg;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

@Override
public String toString() {
return name() + "[code=" + this.code + ", msg = " + this.msg + "]";
}
}
  • 异常代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 自定义学生信息关系系统的异常通用列 就不使用或者单参数String的,直接维护的是枚举
*
* @author luozongwei
*
*/
public class SmException extends RuntimeException {

private static final long serialVersionUID = 6533744059542724668L;

public SmException(StudentMessageCode smec) {
super(smec.toString());
}

}
  • 测试类
1
2
3
4
5
6
7
8
9
public class Test4_SmTest {
public static void main(String[] args) {
studentManger();
}

public static void studentManger() {
throw new SmException(StudentMessageCode.STUDENT_ID_NOT_UPDATE);
}
}

断言 assert

断言(assert),是JDK1.4的时候,增加的一个关键字。用它可以再程序中,确认一些关键性条件必须是成立的,否则会抛出AssertionError类型的错误。

注意,断言(assert)并不是用来代替if判断的,而是确认系统中的一些关键性条件是必须成立的,所以assert和if并不冲突,并且还可以通过给JVM传参数,来控制断言(assert)是否生效。

断言(assert)的使用方式:

1
2
3
assert 布尔表达式;
//或者
assert 布尔表达式 : "错误信息";

当布尔表达式为true是,断言通过,否则抛出AssertionError类型错误

所以,assert后面的布尔表达式必须是true才行。(也就是条件必须成立)

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
test(0);
}

public static void test(int a) {
assert a != 0 : "参数a不能为0";
int b = 10;
int c = b / a;
System.out.println(c);
}
}

默认情况下,JVM是没有开启断言功能的,需要通过给JVM传参打开此项功能

需要使用 -enableassertions 或者 -ea JVM参数

例如:java -ea com.xxx.demo.Test

  • ecplise开启
  • idea开启

此时的运行结果为:因为断言要求参数a不能为0,但实际参数传的为0

1
2
3
Exception in thread "main" java.lang.AssertionError: 参数a的值不能为0
at com.briup.demo.Test.test(Test.java:14)
at com.briup.demo.Test.main(Test.java:8)

如果去掉 -ea参数的话,那么断言(assert)语句,在JVM执行代码的时候,会被直接忽略的

❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭!

天生我材必有用,千金散去还复来。💪