javaSE(从0开始)day14
目录
Exception异常处理:
异常的基本概念
一、java异常:
1、方法一:约定返回错误码。例如,处理一个文件,如果返回0,表示成功,返回其他整数,表示约定的错误码:
2、方法二:在语言层面上提供一个异常处理机制。Java内置了一套异常处理机制,总是使用异常来表示错误。异常是一种class,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了:
3、异常的种类:首先分为error(严重错误不是人为的)和Exception(异常):
4、异常又分为运行时异常和编译异常:
(1)运行时异常:非必要处理异常
(1.1)java.lang.NullPointerException: 空指针异常
(1.2) StringIndexOutOfBoundsException:下标越界异常
(1.3)java.lang.ArrayIndexOutOfBoundsException:数组下标越界
(1.4)java.lang.ArithmeticException:算术异常(除0异常)
(1.5)java.lang.NumberFormatException:格式异常
(1.6)ClassCastException:类型转换异常
(2)编译时异常:IOException UnsupportedEncodingException....除运行异常外都是编译异常
二、捕获异常:
1、捕获异常:必须处理异常(会报错)捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类:
2、多个catch 语句:可以使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。简单地说就是:多个catch语句只有一个能被执行。try块中可以放法多个异常代码,并且异常无关联,处理机制不同 ,try块出现异常,会跳转导对应的catch块,执行catch块中的内容,执行完成后,不会再返回到原出现异常的部分,进行下面的内容 ,异常后的代码不会执行。
3、finally代码块:无论是否有异常发生,如果我们都希望执行一些语句,例如清理工作,怎么写?可以把执行语句写若干遍:正常执行的放到try中,每个catch再写一遍。例如:
(1)finally特点:
(1.1)finally语句不是必须的,可写可不写;
(1.2)finally总是最后执行。
4、注意事项:
(1)多个异常进行捕获需要注意顺序问题
(2)Catch捕获时自上而下进行异常的匹配,越是顶级的类,越要放到下面,写法遂循先子后父 /
(3) 再不然把多余的catch省略
三、抛出异常:
1、抛出异常的两种方式:
(1) throws声明异常: throws:声明异常,在方法上声明可能会出现的异常类型
(2)throw 抛出异常: throw:扔出异常对象,放在方法体内,一般和if判断配合使用
2、异常的传递:当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch被捕获为止。
3、异常的转换:在捕获异常的时候catch代码块里面又出现了一个新的异常,将新的异常信息传递给旧的异常信息。
4、异常的屏蔽:如果在执行finally语句时抛出异常,那么,catch语句的异常不能抛出原本的异常,会将finally代码块中得新异常信息抛出,原本的异常将不被打印出来:
四、自定义异常:
在Java中除了核心类库给我们万宁的异常类之外,我们还可以定义自己所需要格式得自定义异常类在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:
五、使用Log4j:
1、使用Log4j的好处:
(1)可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
(2)可以被重定向到文件,这样可以在程序运行结束后查看日志;
(3)可以按包名控制日志级别,只输出某些包打的日志;
(4)可以规范项目开发;
2、使用Log4j输出的目的地:
(1)console:输出到屏幕;
(2)file:输出到文件;
(3)socket:通过网络输出到远程计算机;
(4)jdbc:输出到数据库
3、Log4j的日志级别:在Loggers(记录器)组件中,级别分6种:TRACE、DEBUG、INFO、WARN、ERROR和FATAL。
4、Log4j的应用:
(1)添加依赖:
(1.1)把从Apache官网下载。下载并解压后,将下面1个jar包加入项目的classpath中commons-logging-1.2.jar
(1.2)我们需要从Apache官网下载Log4j,解压后,把以下3个jar包放到classpath中:
(2)项目下创建lib文件夹 (与src同级),复制jar包到目录下,在将依赖导入项目依赖中
(3)创建log4j配置:在输出日志的过程中,通过Filter来过滤哪些log需要被输出,哪些log不需要被输出。例如,仅输出ERROR级别的日志。最后,通过Layout来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。上述结构虽然复杂,但我们在实际使用的时候,并不需要关心Log4j的API,而是通过配置文件来配置它。虽然配置Log4j比较繁琐,但一旦配置完成,使用起来就非常方便。对上面的配置文件,凡是INFO级别的日志,会自动输出到屏幕,而ERROR级别的日志,不但会输出到屏幕,还会同时输出到文件。并且,一旦日志文件达到指定大小(1MB),Log4j就会自动切割新的日志文件,并最多保留10份。
(4)Log4j使用:第一步,通过LogFactory获取Log类的实例; 第二步,使用Log实例的方法打日志。
(1.1)在静态方法中引用Log,通常直接定义一个静态类型变量:
(1.2)在实例方法中引用Log,通常定义一个实例变量:
(1.3)在子类中使用父类实例化的log:
Exception异常处理:
在编程中,Exception
(异常)是指程序运行过程中发生的意外情况或错误,这些情况会中断程序的正常执行流程。处理异常是编写健壮程序的重要部分。
异常的基本概念
- 异常类型:不同的错误对应不同的异常类型,例如文件未找到(
FileNotFoundError
)、除以零(ZeroDivisionError
)等 - 异常处理:通过特定语法捕获并处理异常,防止程序崩溃
- 异常传递:如果异常未被捕获,会向上传递,直到被捕获或导致程序终止
一、java异常:
在计算机程序运行的过程中,总是会出现各种各样的错误。有一些错误是用户造成的,比如,希望用户输入一个int
类型的年龄,但是用户的输入是abc
:
调用方如何获知调用失败的信息?有两种方法:
1、方法一:约定返回错误码。例如,处理一个文件,如果返回0,表示成功,返回其他整数,表示约定的错误码:
int code = processFile("C:\\test.txt");
if (code == 0) {// ok:
} else {// error:switch (code) {case 1:// file not found:case 2:// no read permission:default:// unknown error:}
}
2、方法二:在语言层面上提供一个异常处理机制。Java内置了一套异常处理机制,总是使用异常来表示错误。异常是一种class
,因此它本身带有类型信息。异常可以在任何地方抛出,但只需要在上层捕获,这样就和方法调用分离了:
try {String s = processFile(“C:\\test.txt”);// ok: } catch (FileNotFoundException e) {// file not found: } catch (SecurityException e) {// no read permission: } catch (IOException e) {// io error: } catch (Exception e) {// other error: }
3、异常的种类:首先分为error(严重错误不是人为的)和Exception(异常):
4、异常又分为运行时异常和编译异常:
(1)运行时异常:非必要处理异常
(1.1)java.lang.NullPointerException: 空指针异常
String str=null;
System.out.println(str.length());
(1.2) StringIndexOutOfBoundsException:下标越界异常
String str1="hello";
System.out.println(str1.charAt(5));
(1.3)java.lang.ArrayIndexOutOfBoundsException:数组下标越界
int[] arr=new int[0];
System.out.println(arr[0]);
(1.4)java.lang.ArithmeticException:算术异常(除0异常)
int Number =10/0;
System.out.println(Number);
(1.5)java.lang.NumberFormatException:格式异常
java.lang.NumberFormatException ;
int number=Integer.parseInt("f24");
System.out.println(number);
(1.6)ClassCastException:类型转换异常
Demo01 aa=(Demo01) (new Object());
System.out.println(aa);
(2)编译时异常:IOException UnsupportedEncodingException....除运行异常外都是编译异常
二、捕获异常:
1、捕获异常:必须处理异常(会报错)捕获异常使用try...catch
语句,把可能发生异常的代码放到try {...}
中,然后使用catch
捕获对应的Exception
及其子类:
public class Main {public static void main(String[] args) {byte[] bs = toGBK("中文");System.out.println(Arrays.toString(bs));}static byte[] toGBK(String s) {try {// 用指定编码转换String为byte[]:return s.getBytes("GBK");} catch (UnsupportedEncodingException e) {// 如果系统不支持GBK编码,会捕获到UnsupportedEncodingException:System.out.println(e); // 打印异常信息return s.getBytes(); // 尝试使用用默认编码}}
}
2、多个catch 语句:可以使用多个catch
语句,每个catch
分别捕获对应的Exception
及其子类。JVM在捕获到异常后,会从上到下匹配catch
语句,匹配到某个catch
后,执行catch
代码块,然后不再继续匹配。简单地说就是:多个catch
语句只有一个能被执行。try块中可以放法多个异常代码,并且异常无关联,处理机制不同 ,try块出现异常,会跳转导对应的catch块,执行catch块中的内容,执行完成后,不会再返回到原出现异常的部分,进行下面的内容 ,异常后的代码不会执行。
public class Demo04 {public static void main(String[] args) {try {process1("123");process2(6);process3("hello你", "GBK");} catch (NumberFormatException ex) {ex.printStackTrace();System.out.println("方法1出错");} catch (IndexOutOfBoundsException ex) {ex.printStackTrace();System.out.println("方法2出错");} catch (UnsupportedEncodingException ex) {ex.printStackTrace();System.out.println("方法3出错");}
//可使用短路或|连接try {
// process1("123");
// process2(2);
// process3("hello你", "GBK");
// } catch (NumberFormatException |IndexOutOfBoundsException|UnsupportedEncodingException ex) {
// ex.printStackTrace();
// System.out.println("异常了");
//
// }}//NumberFormatExceptionprivate static void process1(String s) {int number = Integer.parseInt(s);System.out.println(number);}//可能会有下表越界private static void process2(int index) {int[] arr = {1, 2, 3, 4, 5};System.out.println(arr[index]);}//次方阿飞可能会有UnsupportedEncodingException这类异常//属于编译时异常,必须要进行异常的处理和异常的捕获private static void process3(String str, String charSet) throws UnsupportedEncodingException {byte[] bytes = str.getBytes(charSet);System.out.println(Arrays.toString(bytes));}
}
3、finally代码块:无论是否有异常发生,如果我们都希望执行一些语句,例如清理工作,怎么写?可以把执行语句写若干遍:正常执行的放到try
中,每个catch
再写一遍。例如:
public static void main(String[] args) {try {process1();process2();process3();System.out.println("END");} catch (UnsupportedEncodingException e) {System.out.println("Bad encoding");System.out.println("END");} catch (IOException e) {System.out.println("IO error");System.out.println("END");}
}
上述代码无论是否发生异常,都会执行System.out.println("END");
这条语句。那么如何消除这些重复的代码?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");}
}
(1)finally特点:
(1.1)finally
语句不是必须的,可写可不写;
(1.2)finally
总是最后执行。
如果没有发生异常,就正常执行try { ... }
语句块,然后执行finally
。如果发生了异常,就中断执行try { ... }
语句块,然后跳转执行匹配的catch
语句块,最后执行finally
。可见,finally
是用来保证一些代码必须执行的。
某些情况下,可以没有catch
,只使用try ... finally
结构。
void process(String file) throws IOException {
try {
...
} finally {
System.out.println("END");
}
}
4、注意事项:
(1)多个异常进行捕获需要注意顺序问题
(2)Catch捕获时自上而下进行异常的匹配,越是顶级的类,越要放到下面,写法遂循先子后父 /
(3) 再不然把多余的catch省略
三、抛出异常:
1、抛出异常的两种方式:
(1) throws声明异常: throws:声明异常,在方法上声明可能会出现的异常类型
public static int divide1(int a,int b)throws ArithmeticException{return a/b;}
(2)throw 抛出异常: throw:扔出异常对象,放在方法体内,一般和if判断配合使用
//throw扔出编译异常外象,在方法上必须要声明此类的异常public static void process(String str) throws UnsupportedEncodingException, NullPointerException {if (str == null) {throw new NullPointerException("str为null了,请注意査");}if (!("6Bk".equalsIgnoreCase(str) || "uTF-8".equalsIgnoreCase(str))) {throw new UnsupportedEncodingException(str + "不符合编格式");};System.out.println(str);}
2、异常的传递:当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try ... catch
被捕获为止。
public class Main {public static void main(String[] args) {try {process1();} catch (Exception e) {e.printStackTrace();}}static void process1() {process2();}static void process2() {Integer.parseInt(null); // 会抛出NumberFormatException}
}
java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.process2(Main.java:16)
at Main.process1(Main.java:12)
at Main.main(Main.java:5)
printStackTrace()
对于调试错误非常有用,上述信息表示:NumberFormatException
是在java.lang.Integer.parseInt
方法中被抛出的,从下往上看,调用层次依次是:
main()
调用process1()
;process1()
调用process2()
;process2()
调用Integer.parseInt(String)
;Integer.parseInt(String)
调用Integer.parseInt(String, int)
;
3、异常的转换:在捕获异常的时候catch代码块里面又出现了一个新的异常,将新的异常信息传递给旧的异常信息。
public class Demo04 {public static void main(String[] args) {try {process1();} catch (Exception e) {e.printStackTrace();}System.out.println("end");}private static void process1() {try {process2();} catch (Exception e) {throw new IllegalArgumentException("参数不合法",e);}finally {System.out.println("end process1");}}private static void process2() {Integer.parseInt(null);}
}
4、异常的屏蔽:如果在执行finally
语句时抛出异常,那么,catch
语句的异常不能抛出原本的异常,会将finally代码块中得新异常信息抛出,原本的异常将不被打印出来:
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();}}
}
这说明finally
抛出异常后,原来在catch
中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin
变量保存原始异常,然后调用Throwable.addSuppressed()
,把原始异常添加进来,最后在finally
抛出:
public class Main {public static void main(String[] args) throws Exception {Exception origin = null;try {System.out.println(Integer.parseInt("abc"));} catch (Exception e) {origin = e;throw e;} finally {Exception e = new IllegalArgumentException();if (origin != null) {e.addSuppressed(origin);}throw e;}}
}
四、自定义异常:
在Java中除了核心类库给我们万宁的异常类之外,我们还可以定义自己所需要格式得自定义异常类在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。一个常见的做法是自定义一个BaseException
作为“根异常”,然后,派生出各种业务类型的异常。BaseException
需要从一个适合的Exception
派生,通常建议从RuntimeException
派生:
class BaseException extends RuntimeException{public BaseException() {}public BaseException(String message) {super(message);}public BaseException(String message, Throwable cause) {super(message, cause);}public BaseException(Throwable cause) {super(cause);}}
class OrderTimeOutException extends BaseException {public OrderTimeOutException(String message) {super(message);}
}public class Demo02{public static void main(String[] args) {checkOrderTime("0001",LocalDate.of(2025,07,12));}public static void checkOrderTime(String orderId,LocalDate orderDate){
// if(LocalDate.now().minusDays(3).isBefore(orderDate)){if(LocalDate.now().getDayOfYear()-orderDate.getDayOfYear()<=3){System.out.println("未超时");}else {throw new OrderTimeOutException("订单超时:订单号"+orderId);}}
}
异常信息:
Exception in thread "main" com.yuan.myexception.OrderTimeOutException: 订单超时:订单号0001
at com.yuan.myexception.Demo02.checkOrderTime(Order.java:16)
at com.yuan.myexception.Demo02.main(Order.java:9)
五、使用Log4j:
Log4j 是一种非常流行的日志框架,Log4j 有三个主要的组件:Loggers
(记录器),Appenders
(输出源)和Layouts
(布局)。这里可简单理解为日志类别、日志要输出的地方和日志以何种形式输出。
综合使用这三个组件可以轻松地记录信息的类型和级别,并可以在运行时控制日志输出的样式和位置。Log4j 的架构大致如下:
1、使用Log4j的好处:
(1)可以设置输出级别,禁止某些级别输出。例如,只输出错误日志;
(2)可以被重定向到文件,这样可以在程序运行结束后查看日志;
(3)可以按包名控制日志级别,只输出某些包打的日志;
(4)可以规范项目开发;
2、使用Log4j输出的目的地:
(1)console:输出到屏幕;
(2)file:输出到文件;
(3)socket:通过网络输出到远程计算机;
(4)jdbc:输出到数据库
3、Log4j的日志级别:在Loggers
(记录器)组件中,级别分6种:TRACE
、DEBUG
、INFO
、WARN
、ERROR
和FATAL
。
级别是有顺序的:TRACE
<DEBUG
< INFO
< WARN
< ERROR
< FATAL
,分别用来指定这条日志信息的重要程度,明白这一点很重要,Log4j
有一个规则:只输出级别不低于设定级别的日志信息。
假设Loggers
级别设定为INFO
,则INFO
、WARN
、ERROR
和FATAL
级别的日志信息都会输出,而级别比INFO
低的DEBUG
则不会输出。最后,通过Layout
来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。
4、Log4j的应用:
(1)添加依赖:
(1.1)把从Apache官网下载。下载并解压后,将下面1个jar包加入项目的classpath
中commons-logging-1.2.jar
(1.2)我们需要从Apache官网下载Log4j
,解压后,把以下3
个jar包放到classpath
中:
-
log4j-api-2.x.jar
-
log4j-core-2.x.jar
log4j-jcl-2.x.jar
(2)项目下创建lib文件夹 (与src同级),复制jar包到目录下,在将依赖导入项目依赖中
(3)创建log4j配置:在输出日志的过程中,通过Filter
来过滤哪些log
需要被输出,哪些log
不需要被输出。例如,仅输出ERROR
级别的日志。最后,通过Layout来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。上述结构虽然复杂,但我们在实际使用的时候,并不需要关心Log4j的API,而是通过配置文件来配置它。虽然配置Log4j
比较繁琐,但一旦配置完成,使用起来就非常方便。对上面的配置文件,凡是INFO
级别的日志,会自动输出到屏幕,而ERROR
级别的日志,不但会输出到屏幕,还会同时输出到文件。并且,一旦日志文件达到指定大小(1MB),Log4j就会自动切割新的日志文件,并最多保留10份。
(4)Log4j使用:第一步,通过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.trace("trace");//追踪级别log.debug("debug");//调试级别log.info("info");//普通信息log.warn("warn");//警告log.error("error");//错误级别log.fatal("fatal");//致命错误}}
(1.1)在静态方法中引用Log
,通常直接定义一个静态类型变量:
// 在静态方法中引用Log:
public class Main {static final Log log = LogFactory.getLog(Main.class);static void foo() {log.info("foo");}
}
(1.2)在实例方法中引用Log
,通常定义一个实例变量:
// 在实例方法中引用Log:
public class Person {protected final Log log = LogFactory.getLog(getClass());void foo() {log.info("foo");}
}
(1.3)在子类中使用父类实例化的log:
// 在子类中使用父类实例化的log:
public class Student extends Person {void bar() {log.info("bar");}
}