1认识
什么是异常?
-
异常是程序在"编译" 或者 "执行"的过程中可能出现的问题; 注意:语法错误不算在异常体系中
-
比如:数组索引越界、空指针异常、日期格式化异常等
为什么要学习异常?
-
异常一旦出现了,如果没有提前处理,程序就会退出 JVM 虚拟机 而终止;
-
研究异常并且避免异常,然后提前处理异常,体现的是程序的安全性,健壮性;
Throwable:
- 不管是错误,还是异常,都是可抛出的;
Error:
- 系统级问题、JVM退出等,代码无法控制;
- 所有错误只要发生,Java程序只有一个结果,那就是终止程序的执行,退出JVM,错误是不能处理的;
Exception:
-
RuntimeException:运行时异常,不要求检查的异常,运行时异常发生概率低;未受检异常或者非受控异常
- 空指针异常,数组索引越界异常
-
Exception的直接子类 ExceptionSubClass ,运行时异常发生概率高;受检异常或者受控异常(非运行时异常,编译时异常)
- 日期格式异常
实例:
public class ExceptionTest {
public static void main(String[] args) {
System.out.println(1 / 0); //ArithmeticException
// 这里的HelloWorld没有输出,没有执行。
System.out.println("Hello World!");
}
}
-
产生了ArithmeticException 异常,底层 new 了一个ArithmeticException异常对象,然后抛出;
-
这个异常ArithmeticException抛给了main方法,main方法没有处理,将这个异常自动抛给了 JVM;JVM最终终止程序的执行。
-
此时System.out.println(“Hello World!”); 并不会执行。
2运行时异常
没有考虑好业务需求 与 不严谨的编程逻辑,造成程序异常
public class RuntimeException {
public static void main(String[] args) {
//1.ArrayIndexOutOfBoundsException 数组越界
String[] arr = {"1","2"};
// System.out.println(arr[2]);
//2.空指针异常 NullPointerException
String s = null;
System.out.println(s);
// System.out.println(s.length());
//3.类型转换异常 : ClassCastException
Object o = "23";
// Integer integer = (Integer) o; 报错
// if (o instanceof String){
// String s1 = (String) o;
// System.out.println(1);
// }else if (o instanceof Integer){
// int i = (int) o;
// System.out.println(i);
// }
//4.数字操作异常 ArithmeticException
// Integer integer = 10/0;
//5.数字转换异常 NumberFormatException
String s3 = "32ab";
// Integer i = Integer.valueOf(s3);
}
}
3编译时异常
编译时异常的作用:
- 在编译阶段就爆出一个错误,目的在于提醒不要出错!!!
- 编译时异常是可遇不可求;
- idea出现红色波浪线提醒
public class JavacException {
public static void main(String[] args) throws ParseException {
String d = "2023-5-16 13:02:56";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// throw new ParseException("Unparseable date: \"" + source + "\"" ,
// pos.errorIndex);
Date date = sdf.parse(d);
System.out.println(date);
}
}
4异常处理
4.1默认处理
默认处理机制:出现异常,直接程序死亡
public class Demo {
public static void main(String[] args) {
System.out.println("程序开始");
divide(10,2);
// divide(11,0);
System.out.println("程序结束");
}
public static void divide(int a , int b){
int d = a/b;
System.out.println(d);
}
}
分析:
-
1.默认会在出现异常的代码,那里自动的创建一个异常对象(
ArithmeticException
); -
2.异常会从方法中出现的点,抛出给调用者,调用者最终会抛出给JVM虚拟机;
-
3.虚拟机接受到异常对象后,先在控制台直接输出异常栈信息数据;
-
4.直接从当前执行的异常点干掉当前程序;
-
5.后续代码就没有机会执行,程序已经死亡;
4.2异常处理
异常处理的三种方式:抛出、捕获和处理;
4.2.1抛出
1.throws
-
用在方法上,可以将方法内部出现的异常抛出去 给本方法的调用者处理;
-
Exception
逐层抛出,发生异常的方法,自己不处理异常,如果异常最终抛给 JVM虚拟机,将引起程序死亡; -
注意:
- throws RuntimeException 默认就存在
- 方法内部 throw 编译时异常(Exception的直接子类),该方法必须抛出去;而运行时异常不必抛出
格式(规范):
方法 throws Exception {
}
public class ThrowsDemo {
public static void main(String[] args) throws Exception{
String s = "2023-5-16 13:22:36";
func1(s);
}
public static void func1(String s) throws Exception { //逐级添加ParseException ...
InputStream inputStream = new FileInputStream("D:/meinv.jpg"); //FileNotFoundException: D:\meinv.jpg (系统找不到指定的文件。)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(s);
System.out.println(date);
}
}
2.throw
-
抛出异常是指在代码中使用
throw
关键字来主动抛出异常; -
当程序执行到
throw
语句时,会立即终止当前代码块的执行,并将异常抛给上一级调用栈(调用者);throw new IlleagalException("异常信息"); // 自定义继承Exception的类
-
抛出异常时,需要创建一个异常对象,并将其抛出。异常对象可以是任何继承自
Throwable
类的对象,例如Exception
类或RuntimeException
类的子类。
throws
和 throw
区分
- throws 放置在方法声明,抛出方法内部的异常
- throw 在方法内部创建一个异常对象,并从此处抛出
4.2.2捕获并处理
try-catch
- 程序执行到
try
代码块时,如果发生异常,程序捕获到异常,会跳转到相应的catch
代码块进行处理; - 捕获异常时,如果发生的异常类型与某个
catch
代码块相匹配,则执行该代码块的代码; - 如果捕获异常时,没有与
catch
的异常匹配,那异常将会传递到 上一级调用栈 中进行处理;
格式:
try {
// 可能会抛出ArithmeticException异常的代码
} catch (ArithmeticException e) {
// 处理ArithmeticException异常
} finally {
// 执行清理操作,例如关闭文件或释放资源
}
catch
后面的小括号中的类型可以是具体的异常类型
,也可以是该异常类型的父类型
。catch
可以写多个。建议catch的时候,精确的一个一个处理。这样有利于程序的调试。catch
写多个的时候,从上到下,必须遵守从小到大
。- 可以通过
finally
代码块来执行一些清理工作,例如关闭文件或释放资源等。
public class TryCatchDemo {
public static void main(String[] args){
System.out.println("---------程序开始---------");
String s = "2023-5-16 13:22:36";
parseTime(s);
System.out.println("---------程序结束---------");
}
//日期时间解析
public static void parseTime(String s){ //方法一旦出现异常,直接异常处理方法结束,但main调用者正常运行下去,只是发现第一个异常
try {
InputStream inputStream = new FileInputStream("D:/meinv.jpg");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(s);
System.out.println(date);
} catch (Exception e) {
e.printStackTrace(); //打印异常栈信息
}
}
}
throws 与 try-catch结合
方法使用throws , 调用者使用 try/catch 分析处理 底层处理情况
public class ThirdMethodDemo {
public static void main(String[] args) {
System.out.println("======程序开始======");
String s = "2023-5-16 13:22:36";
try {
parseTime(s);
} catch (Exception e) {
e.printStackTrace();
System.out.println("日期解析失败");
}
System.out.println("======程序结束======");
}
public static void parseTime(String s) throws Exception{ //发现第一个异常,就选择抛掉,方法结束
InputStream inputStream = new FileInputStream("D:/meinv.jpg");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(s);
System.out.println(date);
}
}
实例:输入合法定价
public class Test {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while(true){
try { //非数字 抛出NumberFormatException
System.out.println("请输入合法的定价");
String s = sc.next();
double d = Double.valueOf(s);
if(d > 0){
System.out.println("定价:" + d);
break;
}else {
System.out.println("你输入的是非正数");
}
} catch (NumberFormatException e) {
System.out.println("您输入的数据有误,请您输入合法的数值,建议为正数");
}
}
}
}
4.3finally
try-catch-finally
finally内的代码一定会执行
只有System.exit(0);
只有这个可以治finally。
public class ExceptionTest {
public static void main(String[] args) {
try {
System.out.println("try...");
// 退出JVM
System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
} finally {
System.out.println("finally...");
}
}
}
面试:
public class ExceptionTest13 {
public static void main(String[] args) {
int result = m();
System.out.println(result); //100
}
/*
java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
java中还有一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法!)
*/
public static int m(){
int i = 100;
try {
// 这行代码出现在int i = 100;的下面,所以最终结果必须是返回100
// return语句还必须保证是最后执行的。一旦执行,整个方法结束。
return i;
} finally {
i++;
}
}
}
反编译之后的效果:
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
5自定义异常
1.自定义编译时异常
- 定义一个异常类
extends
Exception (Exception的直接子类) - 重写构造器
- 在出现异常的地方用
throw
new 自定义对象抛出
public class AgeIlleagalException extends Exception{
public AgeIlleagalException(){
}
public AgeIlleagalException(String message) {
super(message);
}
}
2.自定义运行时异常
- 定义一个异常类
extends
RuntimeException(运行时异常) - 重写构造器
- 在出现异常的地方用 throw new 自定义对象抛出
public class AgeIlleagalException2 extends RuntimeException{
public AgeIlleagalException2(){
}
public AgeIlleagalException2(String message) {
super(message);
}
}
对比:(检查年龄)
public class Test {
public static void main(String[] args){
System.out.println("------------程序开始------------");
try {
checkAge(23);
} catch (AgeIlleagalException e) {
e.printStackTrace();
}
try {
checkAge2(202);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("------------程序结束------------");
}
/**
* 编译时异常
* @param age 年龄
* @throws AgeIlleagalException 自定义编译时异常
*/
public static void checkAge(int age) throws AgeIlleagalException {
if(age < 0 || age > 200){
//throws 放置在方法声明,抛出方法内部的异常
//throw 在方法内部创建一个异常对象,并从此处抛出
throw new AgeIlleagalException(age + " is illeagal!");
}else {
System.out.println("年龄合法: 推荐商品给其购买~~~");
}
}
/**
* 检查年龄
* @param age 年龄
*/
public static void checkAge2(int age){
if(age < 0 || age > 200){
//throws 放置在方法声明,抛出方法内部的异常
//throw 在方法内部创建一个异常对象,并从此处抛出
throw new AgeIlleagalException2(age + " is illeagal!");
}else {
System.out.println("年龄合法: 推荐商品给其购买~~~");
}
}
}