Java基础之异常机制学习&分析

举报
码农飞哥 发表于 2021/05/29 12:19:37 2021/05/29
【摘要】 Java异常机制学习&分析 处理错误 Java异常层次简要类图 何时声明受查异常 调用一个抛出受查异常的方法,例如, FileInputStream构造器程序运行过程中发现错误,并且利用throw语句抛出一个受查异常程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。Java 虚拟机和...

Java异常机制学习&分析

处理错误

Java异常层次简要类图

何时声明受查异常

  1. 调用一个抛出受查异常的方法,例如, FileInputStream构造器
  2. 程序运行过程中发现错误,并且利用throw语句抛出一个受查异常
  3. 程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。
  4. Java 虚拟机和运行时库出现的内部错误。

如果出现前两种情况之一,则必须告诉调用这个放啊的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都可能是一个死亡陷阱。
如果没有处理器捕获这个异常,当前执行的线程就会结束。
如下所示:

  public Image loadImage(String s) throws IOException { return null; } public Image loadImage2(String s) throws FileNotFoundException,EOFException { return null; }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以
抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。

自定义异常类

public class FileFormatException extends IOException { public FileFormatException() { } public FileFormatException(String msg) { super(msg); }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

捕获异常

以下代码:

 FileInputStream in = new FileInputStream("test.txt"); try { //1
// code that might throw exception //2 } catch (IOException e) { //3 //show error message //4 } finally { //5 //in.close(); } //6


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

有下列3中情况会执行finally子句
1. 代码没有抛出异常,在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句
中的代码,随后,继续执行try语句块之后的第一条语句。也就是说,执行顺序:1,2,5,6
2. 抛出一个在catch子句中捕获的异常。在上面的实例中就是IOException异常。在这种情况下,程序将
执行try语句块中的所有代码,知道发生异常为止。此时,将跳过try语句块中的剩余代码,转去
执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。

如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句,在这里,执行顺序:1,3,4,5,6

如果catch子句抛出了一个异常,异常江北抛回这个方法的调用者。在这里,执行顺序是:1,3,5
3. 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有
语句,知道有异常被抛出位置。此时将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给
这个方法的调用者,在这里,执行顺序:1,5

强烈建议

强烈建议解耦合try/catch和try/finally语句块。这样可以提高代码的清晰度。例如:

   FileInputStream in = new FileInputStream("test.txt"); try { try {
// code that might throw exception } finally { in.close(); } } catch (IOException e) { //show error message }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保
报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误。

finally子句中也有返回的情况

public static void main(String[] args) { System.out.println("n=1时方法mult返回结果:" + mult(1)); System.out.println("n=2时方法mult返回结果:" + mult(2)); } public static int mult(int n) { try { int r = n * n; return r; } finally { if (n == 2) { return 0; } } }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

得到结果是:n=1时方法mult返回结果:1
n=2时方法mult返回结果:0

如结果所示,当n=2时,try语句块的计算结果为r=4,并执行return语句,然而,在方法真正返回值之前,还要执行finally子句。finally子句将使得方法的返回值为0,这个返回值会覆盖原始的返回值4。

### 带资源的try语句
对于以下代码模式:

 open a resource
  try{ work with resource
  }
  finally{ close the resource
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

假设资源属于一个实现了AutoCloseable接口的类。Java SE 7为这种代码提高了一个很有用的快捷方式,
AutoCloseable 接口有一个方法。
我们可以简写成

  try(Resource res=...){ work with res
  } 例如:
  try(InputStream inputStream = request.getInputStream()){ reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset()); }
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

### 分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
前面已经看到过这种列表,当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
e.printStackTrace(); 打印堆栈信息。
例如:以下打印递归阶乘函数的堆栈情况

  public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Enter=" + scanner); int n = scanner.nextInt(); factorial(n); } public static int factorial(int n) { System.out.println("factorial(" + n + ")"); Throwable throwable = new Throwable(); StackTraceElement[] stackTrace = throwable.getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { System.out.println(stackTraceElement); } int result; if (n == 1) { result = 1; } else { result = n * factorial(n - 1); } System.out.println("result=" + result); return result; }

  
 
  • 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

结果是:

  factorial(3) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22) com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16) factorial(2) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31) com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16) factorial(1) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31) com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31) com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16) result=1 result=2 result=6
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用异常机制的技巧

  1. 异常处理不能代替简单的测试
  2. 不要过分地细化异常
  3. 利用异常层次结构

    不要只抛出RuntimeException异常。应该寻找更加适当的子类或者创建自己的异常类

    不要只捕获Throwable异常,否则,会使程序代码更难读,更难维护。

  4. 不要压制异常

  5. 在检测错误时。”苛刻” 要比放任更好

    当用无效参数滴啊用一个方法时,返回一个虚拟的数值,还是抛出一个异常,哪种处理方式更好呢?
    例如:当栈为空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackExceptin
    异常要比后面抛出一个NullPointExceptin异常更好。

  6. 不要羞于传递异常

    很多程序员都感觉应该捕获抛出的全部异常。如果调用了一个抛出异常的方法,例如FileInputStream构造器或
    readLine方法,这些方法就会本能地捕获这些可能产生的异常。其实,传递异常要比捕获这些异常更好:

      public void readStuff(String filenam) throws IOException{ InputStream in=new InputStream(filenam); ... }  
    
        
       
    • 1
    • 2
    • 3
    • 4
    • 5

    让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。

    PS: 规则5,6可以归纳为”早抛出,晚捕获”,抛出不能处理的异常,捕获可以处理的异常

    使用断言

    断言的概念

    假设确信某个属性符合要求,并且代码的执行依赖于这个属性。
    断言的关键字是assert,这个关键字有两周形式:

    assert 条件:和assert 条件:表达式;

    这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,
    表达式将传入AssertError的构造器,并转换成一个消息字符串

    启动和禁用断言

    在默认情况下,断言被禁用。可以在运行程序时用-enableassertions或者-ea选项启用。
    命令是 java -enableassertions MyApp

    需要注意的是,在启用或禁用断言时不必重新编译程序。启用或禁用断言时 类加载器的功能,当断言被禁用时,
    类加载器将跳过断言代码,因此不会降低程序的运行速度。

    使用断言完成参数检查

    什么时候应该选择使用断言呢?请记住下面几点:

  7. 断言失败是致命的,不可恢复的错误。
  8. 断言检查只用于开发和测试阶段

    记录日志

  9. 基本日志

    可以使用全局日志记录器(global logger) 并调用其info方法。
    Logger.getGlobal().info("日志测试");
    设置日志级别:
    Logger.getGlobal().setLevel(Level.INFO);

  10. 高级日志

    可以自定义日志记录器,可以调用getLogger方法创建或获取记录器:

    private static final Logger myLogger=Logger.getLogger("com.jay.exception.LoggerTest");

    未被任何变量引用的日志记录器可能会被垃圾回收机制回收。为了防止这种情况发生,要像
    上面的例子中一样,用一个静态变量存储日志记录的一个引用。

    通常,有以下7个日志记录器级别:

    • SERVER
    • WARNING
    • INFO
    • CONFIG
    • FINE
    • FINER
    • FINEST
      默认情况下,只记录前三个级别。也可以设置其他的级别。例如:
      logger.setLevel(Level.FINE)
  11. 修改日志管理器配置
    在默认情况下,配置文件存在于 jre/lib/logging.propertiest,要想使用另一个配置文件,
    就要将 java.util.logging.config.file 特性设置为配置文件的存储位置。并用下列命令启动应用程序
    java -Djava.util.logging.config.file=configFile MainClass

文章来源: feige.blog.csdn.net,作者:码农飞哥,版权归原作者所有,如需转载,请联系作者。

原文链接:feige.blog.csdn.net/article/details/79129901

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。