《Java核心技术 卷1》 第七章 异常、断言和日志

本文详细介绍了Java中的异常处理机制,包括异常分类、声明检查型异常、如何抛出异常以及创建异常类。同时,讨论了捕获异常的策略,如捕获多个异常、异常链和finally子句的使用。此外,文章还提到了堆栈轨迹分析的重要性和异常处理的技巧。最后,简述了日志的基本概念,强调了日志在调试和优化程序中的优势,并简要介绍了简单日志和高级日志的实现。
摘要由CSDN通过智能技术生成

对于异常情况,Java使用了一种称为异常处理(exception handing)的错误捕获机制

处理错误

在开发过程中,如果由于出现错误而使得某些操作没有完成,程序应该:

  • 返回到一种安全状态,并能够让用户执行其他的命令。或者
  • 允许用户保存所有的工作,并以妥善的方式终止程序。

在Java中,如果某个方法不能够采用正常的途径完成,可以通过另外一个途径退出。这种情况下,方法不返回任何值,而是抛出(throw)一个封装了错误信息的对象

需要注意的是,这个方法会立刻退出,并不是返回正常值。也不会继续调用这个方法剩下的代码。取而代之的是,异常处理机制开始搜索能够处理这种异常状况的异常处理器(exception handler)

异常分类

在Java中,异常对象都是继承自Throwable类。

Error
Throwable
Exception
Runtime Exception
others

Throwable有两个子类:分别是Error和Exception

  • Error:描述了Java运行时系统的内部错误和资源耗尽错误。这种错误除了通知用户,并尽力妥善的终止程序之外,几乎没有其他办法。

  • Exception:Exception是我们需要关注的重点。Exception又分解成两支

    • Runtime Exception:由变成错误导致的异常

      派生于Runtiome Exception的异常包括:

      • 错误的强制类型转换
      • 数组访问越界
      • 访问null指针
    • other:其他异常

      • 试图超越文件末尾继续读取数据
      • 试图打开一个不存在的文件
      • 试图查找一个不存在的Class对象

Java语言中,规定派生于Error或者Runtiome Exception的异常称为非检查型异常(unchecked ),所有其他类型的异常称为检查型异常(checked)。

编译器会检查是否所有的检查型异常都提供了异常处理器。

声明检查型异常

定义一个方法,除了定义方法的形参、返回值和方法体外,还要告诉编译器这个方法可能发生什么错误。

我们需要再方法的首部指出这个方法可能抛出的异常,使用throws子句

public void method1() throws NullPointerException, IOException, FileNotFoundException {
    System.out.println("hello exception");
}

使用throws子句声明可能抛出的异常时,如果该方法可能抛出多种检查型异常,就在throws子句中列出所有的异常类,并用逗号隔开。

如果子类覆盖了父类的一个方法,子类方法中声明的检查型异常不能比父类方法中声明的异常更通用(子类方法可抛出更特定的异常或者不抛出异常)。如果父类方法并未抛出检查型异常,子类方法也不能抛出任何检查型异常。

如何抛出异常

抛出异常使用throw关键字。

抛出异常非常容易:

  1. 找到合适的异常类
  2. 创建这个异常类的对象
  3. 将对象抛出
public void method2() throws NullPointerException {
    User user = new User(1,"test");
    if(user == null){
        // 创建异常对象并抛出
        throw new NullPointerException("空指针");}
}

一旦方法抛出了异常,这个方法就不能返回到调用者。也就是说,不必操心建立一个默认的返回值或错误码。

创建异常类

当代码中遇到任何标准异常类都无法描述的问题时,我们可以创建自定义异常。

自定义异常类只需继承Exception类或者Exception的子类并提供两个构造方法:无参构造器和一个带描述信息的有参构造器。

/**
 * 自定义异常
 */
public class MyException  extends Exception{

    // 无参构造器
    public MyException() {
    }

    // 带描述的构造器
    public MyException(String message) {
        super(message);
    }
}

捕获异常

除了使用throw向上抛出异常,也可以将异常进行捕获。

捕获异常

如果发生了某个异常,但是没有再任何地方进行捕获,程序就会终止,并在控制台上输出异常的类型和堆栈轨迹等。

要对异常进行捕获,需要使用try/catch语句块:

try
{
	// 可能发生异常的代码
} catch(/*异常对象*/)
{
	// 处理方式
}

如果try语句块中的代码抛出了catch子句指定的异常类,那么:

  1. 程序将跳过try语句块的剩余代码
  2. 程序执行相对应的catch子句中的处理器代码

如果try语句中没有发生任何异常,那么程序将跳过所有catch子句

如果try语句中抛出了catch子句没有声明的异常类,那么这个方法会立即退出

关于抛出与捕获异常如何选择

一般经验是,捕获那些你知道如何处理的异常;而向上抛出你不知道如何处理的异常

捕获多个异常

一个try语句块中可以捕获多种异常,并对不同类型的异常有不同的处理

try
{
	// 可能发生异常的代码
} catch(/*异常对象1*/)
{
	// 处理方式1
}catch(/*异常对象2*/)
{
	// 处理方式2
}catch(/*异常对象3*/)
{
	// 处理方式3
}catch(/*异常对象4*/)
{
	// 处理方式4
}

异常对象可能包含有关异常性质的信息。想要获得这个对象的更多信息,可以使用:

getMessage() // 获得详细信息
getClass().getName()	// 获得异常对象的实际类型

再次抛出异常与异常链

我们可以再catch子句中抛出一个异常。通常,这么做是想改变异常的类型。

try
{
	// 可能发生异常的代码
} catch(/*异常对象*/)
{
	throw new OthreException(); // 向上抛出新的异常
}

finally子句

代码抛出异常时,就会停止当前方法的剩余代码并退出该方法。但是我们经常会需要即使抛出了异常,也需要完成某些代码,比如资源清理等。这里可以使用finally子句。

不管是否有异常被捕获,finally子句中的代码均会执行。

try
{
	// 可能发生异常的代码
} catch(/*异常对象*/)
{
	// 处理方式
} finally
{
	// 关闭资源等必须执行的代码
}

这个程序可能再一下三种情况执行finally子句:

  1. 代码没有抛出异常
  2. 代码抛出了异常,并被catch捕获
  3. 代码抛出了异常,但是没有被任何catch子句捕获

总之,无论try语句块是否遇到异常,finally子句均会执行。

分析堆栈轨迹

堆栈轨迹(stack trace)是程序执行过程中某个特定点上所有挂起的方法调用的一个列表。

当Java程序因为一个未捕获异常而终止时,就会显示堆栈信息。

可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。

异常使用技巧

  1. 异常处理不能代替简单的测试
  2. 不要过分细化异常
  3. 充分利用异常的层次结构
  4. 不要压制异常
  5. 再检测错误时候,”苛刻“比放任更好
  6. 不要羞于传递异常

使用断言

建设中。。。

日志

我们都熟悉在有问题的代码中插入一些System.out.print代码来帮助观察程序的行为。当问题代码调试正常后,我们需要手动删除这些System.out.print代码;如果代码又出现问题,我们又需要再次添加进去。这种做法效率非常底下。

日志就是为了解决这个问题而设计的,日志在使用过程中有如下优点:

  • 可以很容易的取消全部日志记录,或者取消某个级别一下的日志,而且可以很容易的再次打开。
  • 可以很简单的禁止日志记录。因此,将这些日志代码留在程序中的开销很小
  • 日志记录可以被定向到不同的处理器。如在控制台显示、写至文件等。
  • 日志记录和处理器都可以对日志进行过滤。过滤器可以根据过滤器实现器指定的标准丢弃那些无用的记录项。
  • 日志记录可以采用不同的方式格式化。
  • 应用程序可以使用多个日志记录器。他们使用的包名类似的有层次结构的名字。
  • 日志系统的配置由配置文件控制

简单日志

生成最简单的日志,可以使用全局日志记录器。作用等于System.out.print,代码执行后在控制台显示

Logger.getGlobal().info("基本日志");

如果在合适的地方(如main方法最前面)调用:

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

将会取消所有日志。

高级日志

一个专业的程序中,不会将所有的日志都记录到一个全局日志记录器中。而是使用具有层次性的日志记录器。

可以调用getLogger方法创建日志记录器:

private static final Logger logger = Logger.getLogger("priv.ihch17.logger.higherLogger");

未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,需要使用静态变量存储日志记录器的引用。

通常,日志有以下7个级别,从高到低分别为:

  • SERVER
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

默认情况下,只记录前三个级别。也可以通过以下设置,记录降低记录的级别:

logger.setLevel(Level.FINE)

也可以使用Level.All开启所有日志记录,或者使用Level.OFF关闭所有级别。

日志技巧

  1. 对于一个简单的应用,选择一个日志记录器。可以将日志记录器命名为主应用包一样的名字。如果有大量日志记录活动,可以将记录器增加static属性。
  2. 默认的日志配置会将级别高于等于INFO的所有消息记录到控制台上。用户可以根据实际需求修改这一配置。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值