• 14.0_[Java 异常]-异常以及异常处理机制


    ##################################################

    目录

    异常

    为什么需要异常

    效率低下的 if-else 异常处理

    Java 的异常处理

    try-catch 块

    try-catch-finally 块

    多重 catch 块

    声明异常 throws

    抛出异常

    throw 抛出异常

    throw 和 throws 的区别

    异常的分类

    如何处理 Checked 非运行时异常

    在 MyEclipse 中使用开源日志记录工具 log4j

    为什么需要 log4j 日志记录工具

    日志是什么以及日志的分类

    获取 log4j 开源项目

    如何在 MyEclipse 中配置使用 log4j 记录日志

    导入 log4j 的 jar 包

    创建 log4j.properties 文件

    编写 log4j.properties 文件以配置日志信息

    在程序中使用 log4j 记录日志信息

    Logger 对象

    详解 log4j 配置文件

    输出级别

    日志输出目的地 Appender

    日志布局类型 Layout

    转换模式 ConversionPattern

    示例写一个 log4j.properties 配置文件


    ##################################################

    异常

    ——————————

    为什么需要异常

            Java 通过异常机制使得程序中的业务代码与异常处理代码分离

            从而使得代码更加优雅 使程序员更专心于业务代码的编写

            我们先使用 try-catch-finlly 捕获异常

            使用 throw、throuws 抛出和生命异常

            以及异常的分类 最后介绍用于记录日志的开源框架 log4j 记录异常信息

    ——————————

    效率低下的 if-else 异常处理

            例如 本该输入数字 输入字母的代码:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String[] args) {
    4. Scanner in = new Scanner (System.in);
    5. System.out.print ( "请输入第一个数字 <<< " );
    6. int num_1 = in.nextInt ();
    7. System.out.print ( "请输入第二个数字 <<< " );
    8. int num_2 = in.nextInt ();
    9. System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
    10. }
    11. }

            测试的时候我们输入数字:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< 1
    4. 请输入第二个数字 <<< 2
    5. 1 + 2 = 3
    6. C:\Users\byme\javaTest>java Test
    7. 请输入第一个数字 <<< A
    8. Exception in thread "main" java.util.InputMismatchException
    9. at java.util.Scanner.throwFor(Unknown Source)
    10. at java.util.Scanner.next(Unknown Source)
    11. at java.util.Scanner.nextInt(Unknown Source)
    12. at java.util.Scanner.nextInt(Unknown Source)
    13. at Test.main(Test.java:10)
    14. C:\Users\byme\javaTest>

            我们可以看出 一旦程序出现异常就会立刻结束 我们可以通过 if-else 语句来对各种异常情况进行判断处理;

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String[] args) {
    4. Scanner in = new Scanner (System.in);
    5. System.out.print ( "请输入第一个数字 <<< " );
    6. int num_1 = 0;
    7. if ( in.hasNextInt() )
    8. /* 如果输入的是整数 */
    9. num_1 = in.nextInt();
    10. else {
    11. /* 如果输入的不是整数 */
    12. System.out.println ( " 输入的数字不为整数 程序退出.. " );
    13. System.exit (1); // 程序结束执行
    14. }
    15. System.out.print ( "请输入第二个数字 <<< " );
    16. int num_2 = 0;
    17. if ( in.hasNextInt() )
    18. /* 如果输入的是整数 */
    19. num_2 = in.nextInt();
    20. else {
    21. /* 如果输入的不是整数 */
    22. System.out.println ( " 输入的数字不为整数 程序退出.. " );
    23. System.exit (1); // 程序结束执行
    24. }
    25. System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
    26. }
    27. }

            这次有了提示信息 不再中断程序:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< 1
    4. 请输入第二个数字 <<< A
    5. 输入的数字不为整数 程序退出..
    6. C:\Users\byme\javaTest>java Test
    7. 请输入第一个数字 <<< 1
    8. 请输入第二个数字 <<< 2
    9. 1 + 2 = 3
    10. C:\Users\byme\javaTest>

            但是使用 if-else 语句进行异常处理的缺点也很明显:

            代码臃肿 加入了大量的异常情况判断和处理代码

            程序员将大量精力放在了异常处理代码上 放在了堵漏洞上 减少了编写主要代码的时间 必然影响开发效率

            很难穷举所有的异常情况 程序仍旧不健壮

            异常处理代码和业务代码交织在一起 映像代码的可读性 加大日后程序的维护难度

            如果堵住漏洞的工作能右系统来处理 用户只需要关注业务代码的编写 对于异常只需要调用相关的异常处理程序即可!

    ——————————

    Java 的异常处理

            异常处理机制就像我们对平时可能遇到的意外情况 预先想好了一些处理方法

            也就是说 在程序执行代码的时候万一发生了一场 程序会按照预定的方法处理异常

            异常处理完毕之后程序继续运行

            Java 的异常处理是通过 5 个关键字来实现的:

    try

    catch

    finally

    throw

    throws

    ——————————

    try-catch 块

            我们可以把可能出现的异常放入 try 块

            利用 catch 块捕获异常

            如下:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String[] args) {
    4. Scanner in = new Scanner (System.in);
    5. try {
    6. System.out.print ( "请输入第一个数字 <<< " );
    7. int num_1 = in.nextInt();
    8. System.out.print ( "请输入第二个数字 <<< " );
    9. int num_2 = in.nextInt();
    10. System.out.printf ( "%d + %d = %d", num_1, num_2, num_1 + num_2 );
    11. } catch (Exception e) {
    12. System.out.println ( "发生异常:输入的数字不为整数!" );
    13. e.printStackTrace (); // 输出异常详细信息
    14. }
    15. }
    16. }

            运行效果如下:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< 1
    4. 请输入第二个数字 <<< 2
    5. 1 + 2 = 3
    6. C:\Users\byme\javaTest>java Test
    7. 请输入第一个数字 <<< 1
    8. 请输入第二个数字 <<< C
    9. 发生异常:输入的数字不为整数!
    10. java.util.InputMismatchException
    11. at java.util.Scanner.throwFor(Unknown Source)
    12. at java.util.Scanner.next(Unknown Source)
    13. at java.util.Scanner.nextInt(Unknown Source)
    14. at java.util.Scanner.nextInt(Unknown Source)
    15. at Test.main(Test.java:15)
    16. C:\Users\byme\javaTest>

            如果 try 块中的所有语句正常执行完毕 不会发生异常

            如果 catch 块中的所有语句都将被忽略不会被执行

            但是 try 块执行时遇到异常 并且这个异常与 catch 中声明的异常类型相匹配

            那么在 try 块中其余剩下的代码都将被忽略 而相应的 catch 块将会被执行

            匹配是指 catch 所处理的异常类型与所生成的异常类型完全一致或者是她的父类

            当在控制台提示异常信息时

            例如我们输入 C 上面的测试中代码

    int num_1 = in.nextInt();

            处抛出 InputMismatchException 异常

            由于 InputMismatchException 是 Exception 的子类

            程序将忽略 try 块中的其余剩下的代码而去执行 catch 语句块

            如果我们去掉 printStackTrace 方法就不会输出详细的异常信息:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< A
    4. 发生异常:输入的数字不为整数!
    5. C:\Users\byme\javaTest>

            如果 try 语句块在执行过程中遇到异常 而抛出的异常在 catch 块中没有被声明 那么程序就会立刻退出

            在 catch 块中可以加入用户自定义处理信息 也可以调用异常对象的方法输出异常信息

            常用的方法主要有如下两种:

    1. void printStackTrace()
    2. 输出异常的堆栈信息
    3. 堆栈信息包括程序运行到当前类的执行流程
    4. 她将输出从方法调用处到异常抛出处的方法调用序列
    5. 该列中的 java.util.Scanner 类中的 throwFor() 这个方法是异常抛出处
    6. 而 Test 类中的 main() 在最外层的方法调用处
    7. String getMessage()
    8. 返回异常信息描述字符串
    9. 该字符串描述异常产生的原因
    10. 是 printStackTrace() 输出信息的一部分

            如果 try 块在执行过程中遇到异常 那么在 try 块中其余剩下的代码都将被忽略

            系统会自动生成或相应的异常对象 包括异常的类型、异常出现时程序的运行状态以及对该异常的详细描述

            如果这个异常对象与 catch 中声明的异常类型相匹配 会把该异常对象赋給 catch 后面的异常参数

            相应的 catch 块将会被执行

    1. 常见的异常类型:
    2. Exception 异常层次结构的根类
    3. ArithmeticException 算术错误情形 例如以零作为除数
    4. AreeayIndexOutOfBoundsException 数组下标越界
    5. NullPointerException 尝试访问 null 对象成员
    6. ClassNotFoundException 不能加载所需的类
    7. InputMismatchException 欲得到的数据类型与实际输入的类型不匹配
    8. IllegalArgumentException 方法接收到非法参数
    9. ClassCastException 对象强制类型转换出错
    10. NumberFormatException 数字格式转换异常 例如把字符串 "abc"转换为数字!

    ——————————

    try-catch-finally 块

            如果希望无论是否发生异常 都执行某些代码语句 该如何实现?

            此时可以在 try-catch 语句块后加入 finally 块

            把该语句放入 finally 块 无论是否发生异常 finally 块中的代码总能够被执行

            例如:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String [] args) {
    4. Scanner in = new Scanner ( System.in );
    5. System.out.print ( "请输入第一个数字 <<< " );
    6. try {
    7. /* 可能出现异常的代码块 */
    8. int num_1 = in.nextInt();
    9. System.out.print ( "请输入第二个数字 <<< " );
    10. int num_2 = in.nextInt ();
    11. System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );
    12. } catch (Exception e) {
    13. /* 异常处理块 */
    14. System.out.println ( "发生异常:输入的数字不为整数!" );
    15. System.out.println ( e.getMessage () ); // 输出异常描述
    16. } finally {
    17. /* 无论是否正常结束都将执行该代码块 */
    18. System.out.println ( "程序结束.." );
    19. }
    20. }
    21. }

            如果 try 块所有的语句正常执行完毕 那么 finally 块就会被执行 不会执行 catch 语句块中的代码

            如果 try 语句块在执行过程中碰到异常 无论这种异常能否被 catch 块捕获到 都将执行 finally 块中的代码

            例如我们在输入字母时 try 块会抛出异常 进入 catch 语句块 最后 finally 块中的代码也将被执行:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< 1
    4. 请输入第二个数字 <<< 2
    5. 1 + 2 = 3
    6. 程序结束..
    7. C:\Users\byme\javaTest>java Test
    8. 请输入第一个数字 <<< 1
    9. 请输入第二个数字 <<< B
    10. 发生异常:输入的数字不为整数!
    11. null
    12. 程序结束..
    13. C:\Users\byme\javaTest>

            try-catch-finally 结构中 try 块是必需的

            catch 和 finally 为可选 但两者至少需要出现一个

            特别需要注意的是 即使在 try 块和 catch 块中存在 return 语句

            finally 块中语句也会被执行

            发生异常时的执行顺序:

    执行 try 块或 catch 中 return 之前的语句

    执行 finally 块中的语句

    执行 try 块或 catch 中的 return 语句退出

            示例代码:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String [] args) {
    4. Scanner in = new Scanner ( System.in );
    5. System.out.print ( "请输入第一个数字 <<< " );
    6. try {
    7. int num_1 = in.nextInt();
    8. System.out.print ( "请输入第二个数字 <<< " );
    9. int num_2 = in.nextInt ();
    10. System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );
    11. return; // finally 块仍然会被执行
    12. } catch (Exception e) {
    13. System.out.println ( "发生异常:输入的数字不为整数!" );
    14. return; // finally 块仍然会被执行
    15. } finally {
    16. System.out.println ( "程序结束.." );
    17. }
    18. }
    19. }

            测试如下:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< A
    4. 发生异常:输入的数字不为整数!
    5. 程序结束..
    6. C:\Users\byme\javaTest>java Test
    7. 请输入第一个数字 <<< 1
    8. 请输入第二个数字 <<< 2
    9. 1 + 2 = 3
    10. 程序结束..
    11. C:\Users\byme\javaTest>

            finally 块唯一不被执行的情况在异常处理代码中执行

    System.exit (1);

            将退出 Java 虚拟机

            如下:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String [] args) {
    4. Scanner in = new Scanner ( System.in );
    5. System.out.print ( "请输入第一个数字 <<< " );
    6. try {
    7. int num_1 = in.nextInt();
    8. System.out.print ( "请输入第二个数字 <<< " );
    9. int num_2 = in.nextInt ();
    10. System.out.printf ( "%d + %d = %d\n", num_1, num_2, num_1 + num_2 );
    11. } catch (Exception e) {
    12. System.out.println ( "发生异常:输入的数字不为整数!" );
    13. System.exit (1);; // finally 块中语句唯一不会被执行的情况
    14. } finally {
    15. System.out.println ( "程序结束.." );
    16. }
    17. }
    18. }

            测试如下:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入第一个数字 <<< 1
    4. 请输入第二个数字 <<< 2
    5. 1 + 2 = 3
    6. 程序结束..
    7. C:\Users\byme\javaTest>java Test
    8. 请输入第一个数字 <<< 1
    9. 请输入第二个数字 <<< B
    10. 发生异常:输入的数字不为整数!
    11. C:\Users\byme\javaTest>

    ——————————

    多重 catch 块

            如果遇到多种异常情况 我们还可以使用多重 catch 块分别捕获

            一段代码可能会引起多种类型的异常 这时可以在一个 try 语句块后跟多个 catch 语句块 分别处理不同的异常

            但排列顺序必须是从子类到父类 最后一个一般都是 Exception 类

            因为所有的异常子类都是继承自 Exception 类

    所以如果将父类放到前面 那么所有的异常都将被捕获!后面的 catch 块中的子类异常将得不到被执行的机会

            运行时 系统从上到下分别对每个 catch 语句块处理的异常类型进行检测 并执行第一个与异常类型匹配的 catch 语句

            执行其中的一条 catch 语句之后 其后的 catch 语句都将被忽略

            Test.java code:

    1. import java.util.Scanner;
    2. import java.util.InputMismatchException; /* 需要导包 */
    3. public class Test {
    4. public static void main (String [] args) {
    5. Scanner in = new Scanner ( System.in );
    6. try {
    7. System.out.print ( "请输入被除数 <<< " );
    8. int num_1 = in.nextInt();
    9. System.out.print ( "请输入除数 <<< " );
    10. int num_2 = in.nextInt ();
    11. System.out.printf ( "%d / %d = %d\n", num_1, num_2, num_1 / num_2 );
    12. } catch (InputMismatchException e) {
    13. System.out.println ( "被除数和除数必须是整数!" );
    14. } catch (ArithmeticException e) {
    15. System.out.println ( "除数不能为零!" );
    16. } catch (Exception e) {
    17. System.out.println ( "发生未知异常!" );
    18. } finally {
    19. System.out.println ( "程序结束.." );
    20. }
    21. }
    22. }

            测试结果:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入被除数 <<< 9
    4. 请输入除数 <<< 3
    5. 9 / 3 = 3
    6. 程序结束..
    7. C:\Users\byme\javaTest>java Test
    8. 请输入被除数 <<< 9
    9. 请输入除数 <<< 0
    10. 除数不能为零!
    11. 程序结束..
    12. C:\Users\byme\javaTest>java Test
    13. 请输入被除数 <<< 9
    14. 请输入除数 <<< Q
    15. 被除数和除数必须是整数!
    16. 程序结束..
    17. C:\Users\byme\javaTest>

            如果一切正常 最后也会执行 finally 块

            如果输入的不是整数 系统会抛出 InputMismatchException 异常对象 因此进入第一个 catch 语句块执行其中的代码 其她的 catch 将被忽略

            如果被除数为 0 系统会抛出 ArithmeticException 异常对象 进入第二个 catch 块执行代码 其她的 catch 块将被忽略

    一定要记住 多重 catch 块的排列顺序必须是从子类到父类 最后一个一般都是 Exception 类

    ——————————

    声明异常 throws

            如果在一个方法体中抛出了异常 我们就希望调用者能够及时地捕获异常

            那么如何通知调用者呢?

            Java 语言中通过关键字 throws 声明某个方法可能抛出的各种异常

            throws 可以同时声明多个异常 之间用逗号隔开

            我们可以在计算输出商的过程封装在 divide 方法中

            并在方法的参数列表后通过 throws 声明了异常 然后再 main() 方法中调用该方法

            此时 main() 就知道 divide() 方法中抛出了异常

            可以采用如下两种方式处理:

    通过 try-catch 捕获并处理异常

    通过 throws 继续声明异常 如果调用者不打算处理该异常 可以继续通过 throws 声明异常让上一级调用者处理异常 main() 声明的异常将由 Java 虚拟机来处理

            code_1:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String [] args) {
    4. /* 通过 try-catch 捕获并处理异常 */
    5. try {
    6. divide ();
    7. } catch (Exception e) {
    8. System.out.println ( "被除数和除数必须为整数且除数不能为零!" );
    9. e.printStackTrace ();
    10. }
    11. }
    12. public static void divide() throws Exception {
    13. /* 输入被除数和除数 计算商并输出 */
    14. Scanner in = new Scanner ( System.in );
    15. System.out.print ( "请输入被除数 <<< " );
    16. int num_1 = in.nextInt();
    17. System.out.print ( "请输入除数 <<< " );
    18. int num_2 = in.nextInt ();
    19. System.out.printf ( "%d / %d = %d\n", num_1, num_2, (num_1 / num_2) );
    20. }
    21. }

            demo_1:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入被除数 <<< 10
    4. 请输入除数 <<< 5
    5. 10 / 5 = 2
    6. C:\Users\byme\javaTest>java Test
    7. 请输入被除数 <<< 10
    8. 请输入除数 <<< 0
    9. 被除数和除数必须为整数且除数不能为零!
    10. java.lang.ArithmeticException: / by zero
    11. at Test.divide(Test.java:26)
    12. at Test.main(Test.java:10)
    13. C:\Users\byme\javaTest>

            code_2:

    1. import java.util.Scanner;
    2. public class Test {
    3. public static void main (String [] args) throws Exception {
    4. /* 通过 throws 继续声明异常 */
    5. divide ();
    6. }
    7. public static void divide() throws Exception {
    8. Scanner in = new Scanner ( System.in );
    9. System.out.print ( "请输入被除数 <<< " );
    10. int num_1 = in.nextInt();
    11. System.out.print ( "请输入除数 <<< " );
    12. int num_2 = in.nextInt ();
    13. System.out.printf ( "%d / %d = %d\n", num_1, num_2, (num_1 / num_2) );
    14. }
    15. }

            demo_2:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. 请输入被除数 <<< 8
    4. 请输入除数 <<< 2
    5. 8 / 2 = 4
    6. C:\Users\byme\javaTest>java Test
    7. 请输入被除数 <<< 8
    8. 请输入除数 <<< 0
    9. Exception in thread "main" java.lang.ArithmeticException: / by zero
    10. at Test.divide(Test.java:18)
    11. at Test.main(Test.java:8)
    12. C:\Users\byme\javaTest>

    ##################################################

    抛出异常

    ——————————

    throw 抛出异常

            我们介绍了很多捕获异常的知识 既然可以捕获到各种类型的异常 那么这些异常是在什么地方抛出的呢?

            除了系统自动抛出异常外 在编程过程中我们往往会发现有些问题是系统无法自动发现并解决的

            此时需要程序员而不是系统来自行抛出异常 把问题提交給调用者去解决

            在 Java 中可以使用 throw 关键字来自行抛出异常

            例如 无法解决参数问题 因此在方法内通过 throw 抛出异常 把问题交給调用者去解决 在调用该方法者中捕获并处理异常:

    1. class Person {
    2. /* 人类 */
    3. private String name = "姓名";
    4. private int age = 0;
    5. private String sex = "男";
    6. public void setSex (String sex) throws Exception {
    7. /* 设置性别 声明异常 Exception */
    8. if ( "男".equals(sex) || sex.equals("女") )
    9. /* 条件只能为男或女 */
    10. this.sex = sex;
    11. else
    12. /* 将异常提交給调用者处理 */
    13. throw new Exception ( "性别必须为 男/女 !" );
    14. }
    15. public void print () {
    16. /* 输出基本信息 */
    17. System.out.printf ( "%s 的年龄为 %d 性别为 %s\n", this.name, this,age, this.sex );
    18. }
    19. }
    20. public class Test {
    21. /* 测试类 */
    22. public static void main (String[] args) {
    23. try {
    24. Person person = new Person();
    25. person.setSex ( "不男不女" ); // 故意給错误参数
    26. person.print ();
    27. } catch (Exception e) {
    28. e.printStackTrace ();
    29. }
    30. }
    31. }

            测试结果如下:

    1. C:\Users\byme\javaTest>javac Test.java
    2. C:\Users\byme\javaTest>java Test
    3. java.lang.Exception: 性别必须为 男/女 !
    4. at Person.setSex(Test.java:15)
    5. at Test.main(Test.java:33)
    6. C:\Users\byme\javaTest>

    %%%%%

    throw 和 throws 的区别

            作用不同:

    throw 用于在程序中抛出异常

    throws 用于声明在该方法内抛出了异常

            使用的位置不同:

    throw 位于方法体内部 可以作为单独语句使用

    throws 必须跟在方法参数列表后面 不能单独使用

            内容不同:

    throw 抛出一个异常对象 而且只能是一个

    throws 后面跟异常类 而且可以跟多个异常类

    ——————————

    异常的分类

            Java 的异常体系包括许多异常类 她们之间存在继承关系

            Java 的异常体系结构如下:

    1. Object
    2. Throwable
    3. Error
    4. AWTError
    5. ThreadDeath
    6. ...
    7. Exception
    8. RuntimeException
    9. ArithmeticException
    10. NullPointerException
    11. NumberFormatException
    12. ...
    13. SQLException
    14. ClassNotFoundException

            Throwable 类:

            所有的异常类型都是 Throwable 类的子类

            她派生两个子类 即 Error 和 Exception 两个类

            Error 类:

            Error 类表示仅靠程序本身无法恢复的严重错误 例如内存溢出动态链接失败、虚拟机错误

            应用程序不应该抛出这种类型的对象 一般都是由虚拟机抛出

            假如出现这种错误 除了尽力使得程序安全退出以外在其她方面是无能为力的

            所以在进行程序设计时应该更关注 Exception 类

            Exception 类:

            此类由 Java 应用程序抛出和处理的非严重错误!

            例如

    所需文件在找不到

    网络连接不通或中断

    算术运算出错例如被零除

    数组下标越界

    装载了一个存在的类

    对 null 对象操作

    类型转换异常

            等

            她的各种不同地方子类分别对应不同类型的异常

            运行时异常:

            包括 RuntimeException 及其所有子类 不要求程序必须对她们做出处理

            例如 ArithmeticException 异常和 InputMismatchException 异常

            在程序中并没有使用 try-catch 或 throws 进行处理 仍旧可以进行编译和运行

            如果运行时发生异常会输出异常的堆栈信息并中止程序运行

           非运行时异常:

            Checked 异常即非运行时异常

            除了运行时异常外的其她由 Exception 继承来的异常类

            程序必须捕获或者声明抛出这种异常 否则会出现编译错误 导致无法通过编译!

            处理方式包括两种:

    通过 try-catch 在当前位置捕获并处理异常

    通过 throws 声明抛出异常交給上一级调用方法处理

    %%%%%

    如何处理 Checked 非运行时异常

            示例不处理 Checked 异常 code:

    1. import java.io.*;
    2. public class Test {
    3. public static void main (String[] args) {
    4. /* 创建指定文件的流 */
    5. FileInputStream fis = null;
    6. fis = new FileInputStream ( new File ( "temp.txt" ) );
    7. fis.close (); // 关闭指定文件的流
    8. }
    9. }

            在该代码中由于没有对 Checked 异常进行处理 无法通过编译:

    1. C:\Users\byme\javaTest>javac Test.java
    2. Test.java:9: 错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明
    3. 以便抛出
    4. fis = new FileInputStream ( new File ( "temp.txt" ) );
    5. ^
    6. Test.java:11: 错误: 未报告的异常错误IOException; 必须对其进行捕获或声明以便抛出
    7. fis.close (); // 关闭指定文件的流
    8. ^
    9. 2 个错误
    10. C:\Users\byme\javaTest>

            这就是没有处理 Checked 异常

            下面就对 Checked 异常进行处理 使其可以正常通过编译 code:

    1. import java.io.*;
    2. public class Test {
    3. public static void main (String[] args) {
    4. FileInputStream fis = null;
    5. try {
    6. fis = new FileInputStream ( new File ( "temp.txt" ) ); // 创建指定文件的流
    7. } catch (FileNotFoundException e) {
    8. System.err.println ( "无法找到指定的文件!" ); // 异常中推荐使用 err 错误输出而不是 out 正常输出
    9. e.printStackTrace ();
    10. }
    11. try {
    12. fis.close (); // 关闭指定文件的流
    13. } catch (IOException e) {
    14. System.err.println ( "关闭指定文件输入流时出现异常!" );
    15. e.printStackTrace ();
    16. }
    17. }
    18. }

            测试过程:

    1. C:\Users\byme\javaTest>javac Test.java /* 编译通过 */
    2. C:\Users\byme\javaTest>java Test /* 运行成功 只是没有指定文件 */
    3. 无法找到指定的文件!
    4. java.io.FileNotFoundException: temp.txt (系统找不到指定的文件。)
    5. at java.io.FileInputStream.open(Native Method)
    6. at java.io.FileInputStream.(Unknown Source)
    7. at Test.main(Test.java:11)
    8. Exception in thread "main" java.lang.NullPointerException
    9. at Test.main(Test.java:21)
    10. C:\Users\byme\javaTest>dir > temp.txt /* 管道创建一个文件 */
    11. C:\Users\byme\javaTest>java Test /* 再次运行成功执行没有错误 */
    12. C:\Users\byme\javaTest>

    ##################################################

    在 MyEclipse 中使用开源日志记录工具 log4j

    ——————————

    为什么需要 log4j 日志记录工具

            如果我们不希望在控制台上输出相应信息 而是以文件形式记录这些异常信息

            甚至记录程序正常运行的关键步骤信息 以便日后查看 这种情况该如何处理呢?

            很显然我们可以自行编程实现这一效果

            但是从更注重效率和性能方面考虑 还有一个更好的选择 那就是使用流行的开源项目

    log4j

            在 MyEclipse 中使用 log4j 的步骤比较简单 主要分为 4 步:

    在项目中加入 log4j 所使用的 JAR 文件

    创建 log4j.properties 文件

    编写 log4j.properties 文件

    在程序中使用 log4j 记录日志信息

    ——————————

    日志是什么以及日志的分类

            软件运行的过程离不开日志

            日志主要用来记录系统运行过程中的一些重要的操作信息

            便于监视系统运行情况 帮助用户提前发现和避开可能出现的问题

            或者出现问题后根据日志找到发生的原因

            日志根据记录的内容不同 主要分为如下三类:

    1. SQL 日志
    2. 机制系统执行的 SQL 语句
    3. 异常日志
    4. 记录系统运行中发生的异常事件
    5. 业务日志
    6. 记录系统运行过程例如用户登录、操作记录等

            log4j 是一个非常优秀的 日志/log 记录工具

            通过使用 log4j 我们可以控制日志的输出级别以及日志信息输送的目的地如控制台或文件中

            还可以控制每条日志的输出格式

    ——————————

    获取 log4j 开源项目

            要使用 log4j 首先需要下载 log4j 的 JAR 文件

            log4j 是 Apache 的一个开源项目 官网网站是

    https://logging.apache.org/log4jhttps://logging.apache.org/log4j
            这里使用的为 log4j 1.2.17 版本 项目地址为:

    Apache log4j 1.2 -https://logging.apache.org/log4j/1.2/        下载地址为:

    Apache log4j 1.2 - Download Apache log4j 1.2https://logging.apache.org/log4j/1.2/download.html        网站截图:

            好人在这儿:

    1. pan.baidu.com/s/1mAJrhiWj5rFgt4ivmeXFeg
    2. 1297

            这里因为 Java 的版本较低所以使用了 1.2.17 旧版本

            解压如下 这边我直接放在 C 盘根目录下了

            其中 log4j-1.2.17.jar 就是我们需要的 log4j 的 JAR 包:

    C:\apache-log4j-1.2.17

             site 目录下的 manual.html 是使用手册的离线网页:

    manual.html

            site 目录下的 apidocs 子目录中的 index.html 是 JavaDoc/APIDocs :

    index.html

    ——————————

    如何在 MyEclipse 中配置使用 log4j 记录日志

    %%%%%

    导入 log4j 的 jar 包

            在 MyEclipse 10.0 中新建了一个 Main 项目:

    Main

            依次选择

    Project/项目

    properties/属性

    Java Build Path/Java构建路径

    Libraries

    Add External JARs../添加外部 JAR 。。

            选中要加入 log4j 的项目 这里选中 Main 后选择菜单栏的 项目 中的 属性

    选择属性

            或者右击项目选择属性:

    选择属性

            选择构建路径:

    构建路径

            选择 库 选项卡:

    库

            选择添加外部 JAR 打开后选择 jar 包:

    log4j-1.2.17.jar

            点击打开:

    点击打开

            点击确定添加成功:

    添加成功

    %%%%%

    创建 log4j.properties 文件

            使用 log4j 需要创建 log4j.properties 文件

            此文件专门用来配置日志信息

            例如输出级别、输出目的地、输出格式等

            选择要使用 log4j 的项目

            右击 src 依次选择

    New/新建

    File/文件

    新建文件

            打开 New File 对话框:

    New File

            输入文件名

    log4j.properties

            后点击 Finish 完成:

    完成

            创建成功:

    创建成功

            将中间左下角的 Properties 模式改成 Source 模式 就可编写这个文件了!

    %%%%%

    编写 log4j.properties 文件以配置日志信息

            根据配置 将在控制台和文件中同时记录日志信息 这里的日志文件的名字是

    JavaMainLog.log

            log4j.properties code:

    1. ### 设置 Logger 输出级别和输出目的地 ###
    2. log4j.rootLogger = debug, stdout, logfile
    3. ### 把日志信息输出到控制台 ###
    4. log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    5. log4j.appender.stdount.Target = System.err
    6. log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout
    7. ### 把日志信息输出到文件 ###
    8. log4j.appender.logfile = org.apache.log4j.FileAppender
    9. log4j.appender.logfile.File = JavaMainLog.log
    10. log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
    11. log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

    %%%%%

    在程序中使用 log4j 记录日志信息

            现在我们就可以在程序中使用 logj 了!

            测试代码如下:

    1. package mmain;
    2. import java.util.Scanner;
    3. import org.apache.log4j.Logger; /* 在此处导入 log4j */
    4. import java.util.InputMismatchException;
    5. public class Main {
    6. private static Logger logger = Logger.getLogger ( Main.class.getName() ); /* 首先创建一个私有静态的 Logger 对象以后就可以通过该对象的 debug() 或者 error() 等方法输出日志信息了!!! */
    7. public static void main (String [] args) {
    8. try {
    9. Scanner in = new Scanner ( System.in );
    10. System.out.print ( "请输入被除数 <<< " );
    11. int num_1 = in.nextInt();
    12. logger.debug ( "输入被除数 >>> " + num_1 );
    13. System.out.print ( "请输入除数 <<< " );
    14. int num_2 = in.nextInt ();
    15. logger.debug ( "输入除数 >>> " + num_2 );
    16. System.out.printf ( "%d / %d = %d\n", num_1, num_2, num_1 / num_2 );
    17. logger.debug ( "输出运算结果 >>> " + String.format ( "%d / %d = %d", num_1, num_2, num_1 / num_2 ) );
    18. } catch (InputMismatchException e) {
    19. logger.error ( "被除数和除数必须是整数!", e ); // 又一种错误输出方
    20. } catch (ArithmeticException e) {
    21. logger.error ( e.getMessage () ); // 输出异常方法返回值
    22. } catch (Exception e) {
    23. logger.error ( e.getMessage () );
    24. } finally {
    25. System.out.println ( "程序结束.." );
    26. }
    27. }
    28. }

            正常运行的时候发现 log4j 出现意外错误 但是不妨碍运行:

    运行结果

    1. log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
    2. 请输入被除数 <<< 9
    3. DEBUG - 输入被除数 >>> 9
    4. 请输入除数 <<< 3
    5. DEBUG - 输入除数 >>> 3
    6. 9 / 3 = 3
    7. DEBUG - 输出运算结果 >>> 9 / 3 = 3
    8. 程序结束..

            日志文件我的默认路径为:

    C:\Users\用户名\Workspaces\MyEclipse 10\项目名\日志文件

            反转都是在项目目录下 自己可以找一找 我的是

    C:\Users\byme\Workspaces\MyEclipse 10\Main\JavaMainLog.log

    C:\Users\byme\Workspaces\MyEclipse 10\Main\JavaMainLog.log

            查看日志文件 JavaMainLog.log 内容:

    JavaMainLog.log
    JavaMainLog.log

            试试零除报错:

    零除报错

    1. log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
    2. 请输入被除数 <<< 5211314
    3. DEBUG - 输入被除数 >>> 5211314
    4. 请输入除数 <<< 0
    5. DEBUG - 输入除数 >>> 0
    6. ERROR - / by zero
    7. 程序结束..

            再试试其她错误:

    其她错误

    1. log4j:ERROR Unexpected char [ ] at position 30 in conversion patterrn.
    2. 请输入被除数 <<< 1
    3. DEBUG - 输入被除数 >>> 1
    4. 请输入除数 <<< A
    5. ERROR - 被除数和除数必须是整数!
    6. java.util.InputMismatchException
    7. at java.util.Scanner.throwFor(Scanner.java:840)
    8. at java.util.Scanner.next(Scanner.java:1461)
    9. at java.util.Scanner.nextInt(Scanner.java:2091)
    10. at java.util.Scanner.nextInt(Scanner.java:2050)
    11. at mmain.Main.main(Main.java:23)
    12. 程序结束..

            打开日志文件查看:

    日志文件

    ——————————

    Logger 对象

            Logger 对象是用来替代 System.out 或 System.err 的日志记录器 供程序员输出日志信息

            她提供了一系列方法来输出不同级别的日志信息:

    1. 调试级别的异常情况:
    2. public void debug (Object msg)
    3. public void debug (Object msg, Throwable t)
    4. 例如:
    5. 用于记录程序变量
    6. 多次迭代中的变量
    7. 方法/运算 结果
    8. 替代代码中的注释
    9. 运行过程中的强调异常:
    10. public void info (Object msg)
    11. public void info (Object msg, Throwable t)
    12. 例如:
    13. 系统运行信息
    14. serviceimpl 方法的出入口
    15. 主要逻辑分步骤
    16. 外部接口部分
    17. 客户端请求参数和返回给客户端的结果
    18. 调用第三方的调用参数和调用结果
    19. 不应该出现但是不影响程序,当前请求正常运行的异常情况:
    20. public void warn (Object msg)
    21. public void warn (Object msg, Throwable t)
    22. 例如
    23. 有容错机制的时候出现错误情况
    24. 找不到配置文件但是系统能自动创建配置文件
    25. 即将接近临界值的时候例如缓存池占用达到警告线
    26. 影响程序运行,当前请求正常运行的异常情况:
    27. public void error (Object msg)
    28. public void error (Object msg, Throwable t)
    29. 例如
    30. 打开配置文件失败
    31. 第三方网络连接异常
    32. sqlException 等不应该出现的情况
    33. 某个 serviceimpl 的方法返回的 list 集合应该有元素却获取到一个空集合 list
    34. 字符转换的时候报错显示无 GBK 字符集
    35. 主要用于系统运行中的完整信息:
    36. public void fatal (Object msg)
    37. public void fatal (Object msg, Throwable t)
    38. 例如
    39. 完整的 http request 和 http response

    ——————————

    详解 log4j 配置文件

            log4j.properties code:

    1. ### 设置 Logger 输出级别和输出目的地 ###
    2. log4j.rootLogger = debug, stdout, logfile
    3. ### 把日志信息输出到控制台 ###
    4. log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    5. log4j.appender.stdount.Target = System.err
    6. log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout
    7. ### 把日志信息输出到文件 ###
    8. log4j.appender.logfile = org.apache.log4j.FileAppender
    9. log4j.appender.logfile.File = JavaMainLog.log
    10. log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
    11. log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

    %%%%%

    输出级别

            这段代码

    1. ### 设置 Logger 输出级别和输出目的地 ###
    2. log4j.rootLogger = debug, stdout, logfile

            语法如下:

    log4j.rootLogger = 输出级别, 目的地

            其中 debug 指的是 日志记录器/Logger 的输出级别

            主要输出级别及含义如下:

    1. fatal 指出严重的错误事件将会导致应用程序的退出
    2. error 指出虽然发生错误事件但是仍然不影响系统的继续运行
    3. warn 表明会出现潜在错误的情形
    4. info 在粗粒度级别上指明消息 强调应用程序的运行过程
    5. debug 指出细粒度信息事件 对调试应用程序是非常有帮助的

            各个输出级别优先级如下:

    fatal > error ? warn > info > debug

            日志记录器 Logger 将只输出那些级别高于或等于她的信息

            例如级别为 debug 将输出 fatal、error、warn、info、debug 级别的日志信息

            而级别为 error 将只输出 fatal、error 级别的日志信息

    %%%%%

    日志输出目的地 Appender

            语法如下:

    log4j.rootLogger = 输出级别, 目的地

            示例代码为:

    1. ### 设置 Logger 输出级别和输出目的地 ###
    2. log4j.rootLogger = debug, stdout, logfile

            其中 stdout、logfile 指的是日志输出目的地的名字

            log4j 允许记录日志到多个输出目的地 一个输出目的地被称为一个 Appender

            log4j 中最常用的 Appender 有如下两种:

            ConsoleAppender 输出日志到控制台

    通过 Target 属性配置输出到 System.out 或 System.err 默认的目标是 System.out

            FileAppender 输出日志到文件

    输出日志事件到一个文件 通过 File 属性配置文件的路径以及名称

            示例代码中有两个 Appender

    1. ### 把日志信息输出到控制台 ###
    2. log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    3. log4j.appender.stdount.Target = System.err
    4. ### 把日志信息输出到文件 ###
    5. log4j.appender.logfile = org.apache.log4j.FileAppender
    6. log4j.appender.logfile.File = JavaMainLog.log

            第一个命名为 stdout 使用了 ConsoleAppender

            通过配置 Target 属性把日志信息写到控制台 System.err

            第二个 Appender 命名为 logfile 使用了 FileAppender

            通过配置 File 属性将日志信息写到指定的文件 JavaMainLog.log 中 这边没有指定路径 所以放在项目根目录下了

    %%%%%

    日志布局类型 Layout


            Appender 必须使用一个与之相关联的布局类型 Layout 用来指定输出样式

            log4j 中最常用的 Layout 有如下 3 种:

            HTMLLayout

    格式化输出为 HTML 表格

            SimpleLayout

    以一种非常简单的方式格式化日志输出

    她的输出级别为 Level

    然后跟着一个破折号 —— 最后是日志消息

            PatternLayout

    根据指定的转换模式格式化日志输出

    从而支持丰富多样的输出格式

    需要配置 layout.ConversionPattern 属性

    若没有配置该模式将使用默认的转换模式

            示例代码:

    1. ### 设置 Logger 输出级别和输出目的地 ###
    2. log4j.rootLogger = debug, stdout, logfile
    3. ### 把日志信息输出到控制台 ###
    4. log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    5. log4j.appender.stdount.Target = System.err
    6. log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout
    7. ### 把日志信息输出到文件 ###
    8. log4j.appender.logfile = org.apache.log4j.FileAppender
    9. log4j.appender.logfile.File = JavaMainLog.log
    10. log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
    11. log4j.appender.logfile.layout.ConversionPattern = %d { yyyy-MM-dd HH:mm:ss } %1 %d %p %m %n

            第一个控制台 Appender 输出使用了 SimpleLayout

            第二个文件 Appender 输出使用了 PatternLayout 需要配置 layout.ConversionPattern 属性来自定义输出格式

    %%%%%

    转换模式 ConversionPattern

            对于 PatternLayout 来说需要配置 ConversionPattern 属性

            常用的配置参数及其含义如下:

    1. %d 用来设置输出日志的日期和时间 默认格式为 ISO8601 也可以在其后指定格式 比如 %d { yyyy-MM-dd HH:mm:ss } 输出格式类似于 2022-06-28 15:43:30
    2. %m 用来输出代码中指定的消息
    3. %n 用来输出一个回车换行符
    4. %l 用来输出日志事件的发生位置 包括类名、发生的线程以及在代码中的行数 例如如果输出为 mmain.Main.main(Main.java:23) 说明日志事件发生在 mmain 包下的 Main 类里的 maini 方法中 在代码中的行数为第 23
    5. %P 用来输出优先级 即 debug、info、warn、error、fatal 等
    6. %F 用来输出文件名
    7. %M 用来输出方法名

    %%%%%

    示例写一个 log4j.properties 配置文件

            log4j.properties 文件要求如下:

    日志级别为 debug

    输出目的地名字为 stdout

    布局类型为 PatternLayout

    使用 ConversionPattern 配置输出格式 至少输出异常日期、完整的异常堆栈信息

            示例如下:

    1. log4j.properties
    2. log4j.rootLogger = debug, stdout
    3. log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    4. log4j.appender.stdount.Target = System.err
    5. log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
    6. log4j.appender.stdout.layout.ConversionPattern = %d %l %m %n

            我终于知道错在哪里了 是 l/L 不是 1/数字 !!!

  • 相关阅读:
    9.2.5 TIMESTAMP类型
    HTML+CSS+JavaScript仿京东购物商城网站 web前端制作服装购物商城 html电商购物网站
    通过数字证书获取CRL吊销列表
    跟着cherno手搓游戏引擎【27】升级2DRenderer(添加旋转)
    Tomcat+Maven+Servlet安装与部署
    Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单
    Shell程序退出状态码的命令详解
    FineReport制作任务日历
    PHP微服务 hyperf+nacos使用
    万物皆可集成系列:低代码对接企企云实现数据集成
  • 原文地址:https://blog.csdn.net/m0_67268286/article/details/125477883