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

简介: 如果出现前两种情况之一,则必须告诉调用这个放啊的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都可能是一个死亡陷阱。

Java异常机制学习&分析

处理错误

Java异常层次简要类图

何时声明受查异常

1调用一个抛出受查异常的方法,例如, FileInputStream构造器

2程序运行过程中发现错误,并且利用throw语句抛出一个受查异常

3程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。

4Java 虚拟机和运行时库出现的内部错误。

如果出现前两种情况之一,则必须告诉调用这个放啊的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都可能是一个死亡陷阱。

如果没有处理器捕获这个异常,当前执行的线程就会结束。

如下所示:

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


如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以

抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。


自定义异常类

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

捕获异常

以下代码:

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


有下列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
        }


内层的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;
            }
        }
    }


得到结果是: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
  }


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

AutoCloseable 接口有一个方法。

我们可以简写成

try(Resource res=...){
    work with res
  }
  例如:
  try(InputStream inputStream = request.getInputStream()){
              reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset());
          }


### 分析堆栈轨迹元素

堆栈轨迹(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;
     }


结果是:

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利用异常层次结构


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


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


4不要压制异常


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


当用无效参数滴啊用一个方法时,返回一个虚拟的数值,还是抛出一个异常,哪种处理方式更好呢?

例如:当栈为空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackExceptin

异常要比后面抛出一个NullPointExceptin异常更好。


6不要羞于传递异常


很多程序员都感觉应该捕获抛出的全部异常。如果调用了一个抛出异常的方法,例如FileInputStream构造器或

readLine方法,这些方法就会本能地捕获这些可能产生的异常。其实,传递异常要比捕获这些异常更好:

public void readStuff(String filenam) throws IOException{
        InputStream in=new InputStream(filenam);
        ... 
  }


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


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


使用断言

断言的概念

假设确信某个属性符合要求,并且代码的执行依赖于这个属性。

断言的关键字是assert,这个关键字有两周形式:


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


这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,

表达式将传入AssertError的构造器,并转换成一个消息字符串


启动和禁用断言

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

命令是 java -enableassertions MyApp


需要注意的是,在启用或禁用断言时不必重新编译程序。启用或禁用断言时 类加载器的功能,当断言被禁用时,

类加载器将跳过断言代码,因此不会降低程序的运行速度。


使用断言完成参数检查

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


断言失败是致命的,不可恢复的错误。

断言检查只用于开发和测试阶段


记录日志

基本日志


可以使用全局日志记录器(global logger) 并调用其info方法。

Logger.getGlobal().info("日志测试");

设置日志级别:

Logger.getGlobal().setLevel(Level.INFO);


高级日志


可以自定义日志记录器,可以调用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


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
13天前
|
Java 调度
Java中常见锁的分类及概念分析
Java中常见锁的分类及概念分析
15 0
|
14天前
|
设计模式 前端开发 安全
Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
【4月更文挑战第9天】Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
15 1
|
13天前
|
Java
Java中ReentrantLock中tryLock()方法加锁分析
Java中ReentrantLock中tryLock()方法加锁分析
12 0
|
4天前
|
JavaScript Java 测试技术
基于Java的驾考自主学习预约平台的设计与实现(源码+lw+部署文档+讲解等)
基于Java的驾考自主学习预约平台的设计与实现(源码+lw+部署文档+讲解等)
15 0
|
5天前
|
JavaScript Java 测试技术
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
25 1
|
5天前
|
JavaScript Java 测试技术
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
20 0
|
11天前
|
Java 存储
键值之道:深入学习Java中强大的HashMap(二)
键值之道:深入学习Java中强大的HashMap
20 0
键值之道:深入学习Java中强大的HashMap(二)
|
12天前
|
Java
Java中关于ConditionObject的signal()方法的分析
Java中关于ConditionObject的signal()方法的分析
21 4
|
12天前
|
Java
Java中关于ConditionObject的分析
Java中关于ConditionObject的分析
17 3
|
13天前
|
JavaScript Java 测试技术
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
30 0
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)

热门文章

最新文章