前言:
异常就是程序在执行过程中遇到了不符合设定的不正常情况。使用异常有助于保证程序的健壮性,屹立不倒。除此之外还浅谈了一下断言和日志的知识
目录
一、异常
1.异常分类
所以异常都是一个类,并且派生于一个Throwable类,由这个类衍生处理两个分支:Error和Exception。
![](https://img-blog.csdnimg.cn/93956b7be8b846858965528db2107012.png)
Error类:
层次结构描述了Java运行时系统的内部错误和资源耗尽错误。
Exception类:
这类异常比较常见,是应该被重视的。由它分出两个层次,一个为运行时异常,一个为非运行时异常,也称为编译时异常
对于编译时异常,需要我们在编写程序阶段必须预先对这些异常进行处理,如果不处理,编译器报错。这个可以在编译阶段就能被发现
public class RuntimeExceptionDemo {
public static void main(String[] args) {
//test方法会有异常抛出,没有进行预处理,编译器报错,属于编译时异常
test();
}
public static void test() throws Exception{
......
}
}
对于运行时异常,在编写程序阶段可以预先处理。运行时异常,是只有在运行时才能发现的异常。以下是例子
public class RuntimeExceptionDemo {
public static void main(String[] args) {
int i = 0;
int j = 1;
//0不能作为除数,报异常ArithmeticException
System.out.println(j/i);
}
}
![](https://img-blog.csdnimg.cn/5dc96bf77a594d888855c6419ea08081.png)
二者的区别:
- 编译时异常一般发生的概率
比较高
。- 运行时异常一般发生的概率
比较低
。- 编译时异常发生概率较高,需要在运行之前对其进行
预处理
。- 运行时异常发生概率较低,没必要提前进行预处理。
说明:
public class RuntimeExceptionDemo {
public static void main(String[] args) {
int i = 0;
int j = 1;
//0不能作为除数,报异常ArithmeticException
int temp = j/i;
System.out.println("Hello world");
}
}
在该程序中,是main方法中进行的, 由于出现了一个异常,这个异常将会被抛给main方法,由于main方法没有处理它,将抛给JVM,JVM就就将它给终止掉了,后面System.out.println("Hello world");是不会执行的
2.异常处理
1)throws(抛出异常)
抛出异常语法:方法名 throws 异常
public FileInputStream(String name) throws FileNotFoundException
这种异常的处理方式就是上报(推卸责任),将异常上报给调用它的方法,异常发生之后的代码不会执行
2)try...catch(捕获异常)
在这里就是自己将异常处理,而不会再次往上抛,在处理完异常后,会继续执行代码。特别注意,try块内的代码,在异常之后是无法执行的,而块外的代码在处理完异常后会正常执行
public class RuntimeExceptionDemo {
public static void main(String[] args) {
int i = 0;
int j = 1;
try {
int temp = j / i;
//catch内部为异常类型,e就是运行时JVM创建异常的对象引用
}catch (Exception e){
//此处为处理异常,选择将异常抛出作为处理
e.printStackTrace();
}
System.out.println("Hello world");
}
}
注意:
- 如果try语句块中的代码没有抛出任何异常,那么程序将跳过catch语句。
- 如果方法中的任何代码抛出的了catch语句中没有声明的一个异常类型,那么这个方法会立即退出,原因很简单,没有捕获到就被往上抛,抛着抛着就到JVM上去了。
3)补充说明
- 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
- 一般不建议在main方法上使用throws,因为这个异常如果真正的发生了,一定会抛给JVM。JVM有终止。
- 一般main方法中的异常建议使用try…catch进行捕捉。
3.抛出or捕获
- 对于希望调用者来进行处理,那么就向上抛出
- 其他情况就原地捕获解决
4. 深入try....catch
- catch内部的括号中填写的类型可以是具体的异常的类型,也可以是父类(本层或者更高层)
- try....catch 结构中可以有多个catch 语句
- 使用多个catch结构时,必须遵循异常范围是逐渐变大的
public static void main(String[] args) {
try{
FileInputStream f = new FileInputStream("D:\\aa\\pz.txt");
} catch (FileNotFoundException e){
e.printStackTrace();
} catch (Exception e){
e.printStackTrace();
}
}
5.异常的两重要方法
方法名 | 作用 |
---|---|
String getMessage() | 返回异常的详细消息字符串 |
void printStackTrace() | 追踪堆栈异常信息(采用异步线程) |
6.创建异常类
在遇到无法描述的异常时,需要你自己根据实际情况进行创建异常类。创建异常类需要继承Exception类或者它的一个子类。一般异常都包含两个构造器,一个为无参构造器,一个有描述的构造器(通过super调用超类Throwable的toString())。
创建操作
//自定义异常类
class TestException extends Exception{
public TestException(){
}
public TestException(String message){
super(message);
}
}
使用实例
public class RuntimeExceptionDemo {
public static void main(String[] args) {
Test test = new Test();
try {
test.isOverZero(-1);
} catch (TestException e) {
e.printStackTrace();
}
}
}
class Test{
public void isOverZero(int i) throws TestException{
if(i<0){
//注意是throw ,不是 throws
//这两个东西不一样
throw new TestException();
}
}
}
class TestException extends Exception{
public TestException(){
}
public TestException(String message){
super(message);
}
}
7.finally关键字
1)finally 说明
finally 与try...catch 语句连用,不能单独使用
finally用于在异常发生后回收资源,因为try中发生异常,后面的代码就无法执行,如果要完成资源的清理就比较麻烦,例如:流的关闭。finally里面的代码是一定会执行的,无论是否出现异常。
try{
......
}catch{
......
}finally{
//关闭资源等回收资源的操作
xxx.close();
}
2)特殊情况
就如我们所知,return表示的是一个程序将结束,而finally又是try后必须执行的代码块,假如执行了try里面的return语句,就不符合规则了,所以在try语句return前,会跑到finally块里面去执行代码,而finally代码是执行return,就直接停止方法了,这种结果就会造成finally的结果将try的结果进行一个覆盖
public static int num(){
try {
return 5;
} catch (Exception e) {
e.printStackTrace();
}finally {
return 0;
}
}
3)try with resource语句
和上面的try不一样的是try()自己来了资源,
try(Scanner in = new Scanner()){
}
允许多个资源在括号中,注意分号
try(Scanner in = new Scanner();var file = new FileInputStream("D://aa//pz.txt");)
{
}
使用这个语句块,可以携带catch和finally,但是与上面的try不一样的是,这个try会自动关闭资源 ,自动执行in.close() 、file.close();
三、断言
1.断言概念
断言就是断定一条语句是否如希望的那样,为true就正常进行,为false就报异常AssertionError.
2.断言语法
第一种;
assert condition;
第二种:
assert condition: expression;
“表达式”(expression)部分的唯一目的是产生一个消息字符串。
AssertionError对象并不存储具体的表达式值(条件并不会自动地生成为错误报告中的一部分),因此以后无法得到这个表达式值。(如果使用表达式的值,就会鼓励程序员尝试从断言失败中恢复程序的运行,这不符合断言机制的初衷。)
断言机制允许在测试期间向代码插入一些检查,而在生产代码中会自动删除这些检查。
3.断言的启用和禁用
在默认情况下,断言是禁用的。可以在运行程序是用 -enableassertions 或 -ea 选项启用断言:
java -enableassertions MyApp
需要注意的是,不必从新编译程序来启用或禁用断言。启用或禁用断言是**类加载器(class loader)**的功能。禁用断言时,类加载器会除去断言代码,因此,不会降低程序的运行速度。
可以在某个类或整个包中启用断言,例如:
java -ea:MyClass -ea:com.mycompany.mylib MyApp
这条命令将为 MyClass类以及 com.mycompany.mylib包和它的子包中的所有类打开断言。选项 -ea 将打开无名包中所有类的断言。可以使用 disableassertions 或 -da 在特定类和包中禁用断言:
java -ea:... -da:MyClass MyApp
有些类不是由类加载器加载,而是直接由虚拟机加载的。可以使用这些开关有选择地启用或禁用那些类中的断言。
不过,启用和禁用所有断言的 -ea 和 -da 开关不能应用到那些没有类加载器的“系统类”上。对于这些系统类,需要使用-enablesystemassertions/-esa 开关启用断言。也可以通过编程控制类加载器的断言状态。
5.使用断言完成参数检查
在Java语言中,给出了3中处理系统错误的机制:
- 抛出一个异常。
- 日志。
- 使用断言。
什么时候应该使用断言呢?请记住下面几点:
- 断言失败是致命的、不可恢复的错误。
- 断言检查只是在开发和测试阶段打开(这种做法有时候被戏称为“在靠近海岸时穿上救生衣,但在海里就把救生衣抛掉”)。
因此,不应该使用断言向程序的其他部分通知发生了可恢复性的错误,或者,不应该利用断言与程序用户沟通问题。断言只应该用于测试阶段确定内部错误的位置。
在方法的开头使用断言判断方法的参数是否合法。计算机科学家将这种约定称为前置条件。如果调用者在调用这个方法时没有满足这个前置条件,断言会失败。
四、日志
每个 Java 程序员都很熟悉在有问题的代码中插人一些 System , out . println 方法调用来帮助规寥程序的行为。当然,一旦发现问题的根源,就要将这些 print 语句从代码中删去。如果下来又出现了问题,就需要再捕人儿个调用 System , out . println 方法的语句。日志 API 就是为了解决这个问题而设计的。下面先讨论这个 API 的主要优点。
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别以下的日志,而且可以很容
- 易地再次打开日志开关。
- 可以很简单地禁止日志记录,因此、将这些日志代码留在程序中的开销很小。日志记录可以被定向到不同的处理器,如在控制台显示、写至文件
- 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤器实现器指定的标准丢弃那些无用的记录项
- 日志记录可以采用不同的方式格式化,例如,纯文本或 XMI
- 应用程序可以使用多个日志记录器, 他们使用与包名类似的有层次结构的名字如, com . mycompany . myapp 。
- 日志系统的配置由配置文件控制。
1.修改日志配置文件
JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖,比如, java -Djava.util.logging.config.file=myfile
2.基本日志
执行代码:
public static void main(String[] args) {
Logger.getGlobal().info("record");
}
}
结果信息:
在这里是调用了全局记录器(global logger)并调用了info方法,将信息写入到记录中
3.高级日志
1)自定义日志记录器
使用getLogger静态方法。参数是日志记录器的名字,是一个层次结构:包名 + 程序名(例子中是:com.company + myapp)
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
日志等级(由高到低)
默认日志配置会记录INFO更高级别的所有日志,因此应该使用CONFIG、FINE、FINER和FINEST、级别来记录那些有助于诊断但对用户意义不大的调试信息
也就是说,高级别的会被记录下来,低级别会被丢掉(高低是相对的说法)
private static Logger logger = Logger.getLogger("record");
public static void main(String[] args) {
logger.setLevel(Level.INFO);
logger.finest("finest");
logger.finer("finer");
logger.fine("fine");
logger.config("config");
logger.info("info");
logger.warning("warning");
logger.severe("server");
}
运行结果:
可以使用log方法指定级别,例如:
logger.log(Level。FiNE, message);
笔记自用