Java 异常处理、初始化块、final关键字、运算符优先级、循环语句


 

异常处理

java的异常体系

在这里插入图片描述
Error、Exception的区别:Error是程序本身无法处理的错误,Excepttion是程序本身可以处理的异常,捕获处理后系统可能会恢复。

Exception 异常可分为2大类

  • CheckedException 编译时异常:检查是否存在语法问题,存在此种异常时通不过编译;
  • RuntimeException 运行时异常:在程序运行期间触发,通常是代码逻辑错误、不完善,或者外部环境问题导致。如果未捕获处理运行时异常,会一直往上层抛,抛到 Thread.run() 导致线程退出,如果是主线程会导致jvm终止、应用终止。

可以继承 Exception、RuntimeException 之类的异常实现自定义异常。

 

执行顺序
try{
    int i = 1 / 0;
}catch (Exception e){
    System.out.println("catch...");
}finally {
    System.out.println("finally...");
}
System.out.println("other...");

try中没有发生异常:try -> finally -> finally之后的代码

try中发生了异常:

  • 不再执行try中后续的代码,转到执行catch
  • 如果catch中直接处理完了异常,则执行finally -> finally之后的代码;如果catch中throw往外抛了异常,则不再执行catch中后续的代码,直接执行finally,执行finally后不会再执行finally块之后的代码,而是跳到外层的catch中捕获处理异常(如果有外层的异常处理)。
     

在方法体中没有捕获处理异常,会跳出该方法的栈帧,导致方法体中后续代码都不会执行。

在循环体中,慎用throw直接抛出异常,如果循环体中的异常直接抛到了循环体外,会导致循环终止,后续批次都不会执行。

比如使用循环批量推送短信,某次循环时发生异常,没有在循环内处理异常,让异常抛到了循环外,则循环会被终止,循环中后续批次的消息都不会发送。

循环这种批量操作的场景,往往要尽量避免循环被异常终止,保证循环的继续执行,在循环体中可能发生异常的地方使用try…catch捕获处理掉异常。

 

多个catch
try {
    User user = null;
    Long userId = user.getUserId();
  //只会被这个catch处理
} catch (NullPointerException e) {
    log.error("NullPointerException", e);
  //不会再被这个catch处理
} catch (Exception e) {
    log.error("Exception", e);
}

有多个catch时

  • 后续catch捕获的异常范围要比之前的大,否则编译会报错;
  • 异常只会被第一个适配的ctach捕获处理,不会再被后续的catch捕获处理,即使后续的catch也适配。
     

如果多种异常的捕获处理逻辑相同,也可以这样写

try {
    //...
} catch (AException | BException e) {
    //...
}

 

异常块的优化

try…catch块对性能的影响主要体现在2方面

  • 会影响指令重排序
  • 发生异常时会生成Exception对象,需要保存栈快照等信息,开销大
     

优化点

  • 不要大量使用try-catch
  • 减少try中的代码量,能放到try外面就尽量放到外面

 

异常的打印

catch中打印异常的常见方式

// printStackTrace,idea代码提示默认就是此种
e.printStackTrace();

// log打印整个异常对象
log.error(e);
log.error("xxx执行出错", e);
log.error("xxx执行出错,请求参数userReqBo={}", JSON.toJSONString(userReqBo), e)

这2种都会打印出错误信息,即 e.getMessage() 的内容,以及对应的栈帧(所在的类、方法、行号),所以都没必要再打印 e.getMessage()。

 

初始化块

初始化块和成员变量、成员函数一个级别,一般用于初始化。

class Xxx {
    // 普通初始化块
    {

    }


    // 静态初始化块
    static {

    }


    // ......

}

一个类中可以有多个初始化块,如果都是static初始化块或者普通初始化块,越靠前的越先执行。

static初始化快只能访问类的静态成员,只在JVM加载类的class对象时执行1次,常用于初始化类的静态成员、公共资源。

编译时,会把普通初始化块中的代码放到构造函数函数体内的最前面。每次创建对象时,在执行构造函数之前,都会先执行普通初始化快。普通初始化快常用与初始化对象。

 

final关键字

final可修饰类、变量(包括成员变量、局部变量)、方法

  • 被final修饰的类不能被继承
  • 被final修饰的变量,声明时就必须赋值,后续不能修改其值,即只读。基本类型、String类型不能改变值,引用类型不能修改引用(内存地址),但可以修改引用指向的对象本身。
  • 被final修饰的方法不能被重写
     

形参属于局部变量,也可以用final修饰,调用方法传递实参时初始化该final变量,方法执行过程中,该final变量的值都不能被修改。

用final修饰形参时,方法体中不能修改该参数的值(即不能有赋值操作),否则通不过编译。

public void xxx(final String str1, final String str2, String str3) {

}

 

运算符优先级

运算符结合性
[ ] . ( ) 访问数据元素、调用成员、函数调用从左向右
! ~ ++ – + - 单目运算符从右向左
(type) expr 小括号方式的类型转换从右向左
* / % 乘除取模从左向右
+ - 加减从左向右
<< >> >>> 位运算从左向右
< <= > >= instanceof 比较判断从左向右
== != 等价判断从左向右
&从左向右
^从左向右
|从左向右
&&从左向右
||从左向右
? : 三目运算符从右向左
= += -= *= /= %= &= |= ^= <<= >>= >>= 赋值从右向左
,从左到右

 

常见运算符的优先级

单目 > 算术运算 > 位运算 > 条件判断 > 逻辑运算 > 三目 > 赋值

 

>>>是无符号右移,和>>右移一样,只是不考虑最高位的正负,不管最高位原来是0还是1,都直接取0。

 

循环语句

Map<String, String> map;


int i = 0;
while (i < map.size()) {
    //...
}


do {
    //...
} while (i < map.size());


for (int j = 0; j < map.size(); j++) {
    //...
}


for (String key : map.keySet()) {
    //...
}

如果循环判断条件中使用了函数

  • while、do…while、for 三个循环语句,每次执行判断时都会重新调用函数进行计算,示例中每次判断条件都会调用1次 map.size() 。优点:如果计算的是公共变量,循环期间可以及时检测到该变量的修改;缺点:降低性能。
  • 增强for循环会保存函数返回值,后续判断循环条件时直接使用保存的函数返回值,不重新调用函数进行计算。优点:提高了性能;缺点:如果计算的是公共变量,循环期间不能及时检测到该变量的修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值