1 异常
1.1 简介
异常是在我们开发的程序中的一些错误,但是并不是所有的错误都是异常,并且错误有时候是可以避免的。在Java等面向对象的编程语言中异常属于对象。异常出现的情况也是很多,包括
- 用户输入非法的字符,在程序中没有校验,就会出现参数异常;
- 在程序运行时,出现了除数0的变量,也会出现异常;
- 打开某个路径下的文件,而文件不存在也会出现异常信息。
那么我们面对不同的异常该如何处理呢?哪些异常可以让程序继续运行下去哪些异常需要让程序终止运行?这就是我们在编写开发业务逻辑过程中需要注意的地方。
检查时异常:检查时异常是指程序在编译的过程中,进行词法、语法、语句、依赖等编译过程中的异常,程序员是无法预见的异常,是在编译中提示的异常。例如:缺少依赖文件等。
运行时异常:运行时异常是指在编译后,程序运行阶段出现的异常,这类异常在刚步入开发人员的时候出现的比较多,这一类运行时的异常,往往是可以被程序员代码编写所避免的。例如:空指针异常、
错误:是严重的异常,错误在代码中通常被忽略,例如内存溢出等情况。错误在编译的过程中是检测不到的。
1.2 架构图
作者是Java开发者,所以基于Java中的异常进行了一下简单的总结。如下图是根据javaJDK中列出的一些异常信息。
getMessage()
getMessage()方法返回关于发生的异常的详细信息。这个消息在Throwable类的构造函数中初始化了。在我们的程序中可以直接通过异常信息去获取Exception e示例如下:
e.getMessage();
getCause()
getCause()返回一个Throwable 对象代表异常原因。
toString()
toString()方法使用getMessage()的结果返回类的串级名字。
printStackTrace()
printStackTrace()方法打印toString()结果和栈层次到System.err,即错误输出流。一般使用printStackTrace()将异常日志输出到控制台中。
getStackTrace()
getStackTrace()方法返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
1.3 异常处理
既然在我们开发程序的中那么多的异常,我们怎么去捕获这些异常并进行处理呢,以减少不必要的错误,那么现在就开始介绍。
1.3.1 throw/throws
throws/throw关键字的作用是如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。其简单格式如下:一般在方法的末尾使用。
public void testException() throws Exception{
}
也可以使用throw 关键字抛出一个异常。使用throw 关键字抛出的异常,在方法名之后需要有throws的关键字声明异常来接受。
public void testException() throws Exception{
throw new Exception();
}
1.3.2 try/catch
在我们的程序中,针对可能出现的异常代码块可以使用try和catch关键字来捕获异常。try/catch 代码块放在异常可能发生的地方。其中被try/catch代码块中的代码称为保护代码,当保护代码中出现异常之后,可以根据业务需要是否继续执行后续代码,还是针对异常信息进行处理并抛出异常,是一种灵活的异常处理方式,其代码块的格式如下:
try{
// 业务代码
}catch(Exception e){
//Catch 块
}
当然针对try/catch关键字中多个 catch的方式也是可以的,称为多重捕获。其常用的格式如下:
try{
// 业务代码
}catch(异常类型1 e1){
// 业务代码
}catch(异常类型2 e2){
// 业务代码
}catch(异常类型3 e3){
// 业务代码
}
在try中的代码发生异常的情况下,优先匹配第一个catch块中的内容,由近及远挨着匹配,直到匹配到相关异常。
1.3.3 finally
finally关键字用来创建在try/catch代码块最后。无论try/catch代码块是否发生异常finally代码块中的代码总会被执行。在finally代码块中,可以运行清理类型等收尾善后性质的语句。其语法格式如下:
try{
// 业务代码
}catch(Exception e){
//Catch 块
}finally{
// 业务代码
}
2 枚举
2.1 简介
enum的全称为 enumeration, 是 JDK 1.5 中引入的新特性。在Java中,被 enum关键字修饰的类型就是枚举类型。形式如下:
enum Color { RED, GREEN, BLUE }
如果枚举不添加任何方法,枚举值默认为从0开始的有序数值。以 Color 枚举类型举例,它的枚举常量依次为 RED:0,GREEN:1,BLUE:2。
枚举的好处:可以将常量组织起来,统一进行管理。
枚举的典型应用场景:错误码、状态机等。
2.2 本质
尽管 enum看起来像是一种新的数据类型,事实上,enum是一种受限制的类,并且具有自己的方法。创建enum时,编译器会为你生成一个相关的类,这个类继承自 java.lang.Enum。java.lang.Enum类声明:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable { ... }
2.3 方法
在enum中,提供了一些基本方法:
values():返回 enum 实例的数组,而且该数组中的元素严格保持在 enum 中声明时的顺序。
name():返回实例名。
ordinal():返回实例声明时的次序,从0开始。
getDeclaringClass():返回实例所属的 enum 类型。
equals():判断是否为同一个对象。可以使用==来比较enum实例。
2.4 特性
枚举的特性,归结起来就是一句话:
除了不能继承,基本上可以将
enum看做一个常规的类。
但是这句话需要拆分去理解,让我们细细道来。
Java 不允许使用 = 为枚举常量赋值
如果你接触过C/C++,你肯定会很自然的想到赋值符号 =。在C/C++语言中的enum,可以用赋值符号=显示的为枚举常量赋值;但是 ,很遗憾,Java 语法中却不允许使用赋值符号 =为枚举常量赋值。
枚举可以添加普通方法、静态方法、抽象方法、构造方法
Java 虽然不能直接为实例赋值,但是它有更优秀的解决方案:为 enum 添加方法来间接实现显示赋值。创建 enum时,可以为其添加多种方法,甚至可以为其添加构造方法。
注意一个细节:如果要为enum定义方法,那么必须在enum的最后一个实例尾部添加一个分号。此外,在enum中,必须先定义实例,不能将字段或方法定义在实例前面。否则,编译器会报错。
public enum ErrorCodeEn {
OK(0, "成功"),
ERROR_A(100, "错误A"),
ERROR_B(200, "错误B");
ErrorCodeEn(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
枚举可以实现接口
enum可以像一般类一样实现接口
枚举不可以继承
enum 不可以继承另外一个类,当然,也不能继承另一个 enum 。因为 enum实际上都继承自 java.lang.Enum类,而 Java 不支持多重继承,所以 enum不能再继承其他类,当然也不能继承另一个 enum。
2.5 应用场景
2.5.1 组织常量
在JDK1.5 之前,在Java中定义常量都是public static final TYPE a;这样的形式。有了枚举,你可以将有关联关系的常量组织起来,使代码更加易读、安全,并且还可以使用枚举提供的方法。
enum Color { RED, GREEN, BLUE }
enum Color { RED, GREEN, BLUE, }
enum Color { RED, GREEN, BLUE; }
2.5.2 switch状态机
我们经常使用switch语句来写状态机。JDK7以后,switch已经支持 int、char、String、enum类型的参数。
2.5.3 组织枚举
可以将类型相近的枚举通过接口或类组织起来。但是一般用接口方式进行组织。
原因是:Java接口在编译时会自动为enum类型加上public static修饰符;Java类在编译时会自动为 enum类型加上static修饰符。看出差异了吗?没错,就是说,在类中组织 enum,如果你不给它修饰为 public,那么只能在本包中进行访问。
在接口中组织 enum
public interface Plant {
enum Vegetable implements INumberEnum {
POTATO(0, "土豆"),
TOMATO(0, "西红柿");
Vegetable(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
@Override
public int getCode() {
return 0;
}
@Override
public String getDescription() {
return null;
}
}
enum Fruit implements INumberEnum {
APPLE(0, "苹果"),
ORANGE(0, "桔子"),
BANANA(0, "香蕉");
Fruit(int number, String description) {
this.code = number;
this.description = description;
}
private int code;
private String description;
@Override
public int getCode() {
return 0;
}
@Override
public String getDescription() {
return null;
}
}
}
在类中组织 enum
public class Plant2 {
public enum Vegetable implements INumberEnum {...}
public enum Fruit implements INumberEnum {...}
}
2.5.4 策略枚举
EffectiveJava中展示了一种策略枚举。这种枚举通过枚举嵌套枚举的方式,将枚举常量分类处理。这种做法虽然没有switch语句简洁,但是更加安全、灵活。
enum PayrollDay {
MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) {
this.payType = payType;
}
double pay(double hoursWorked, double payRate) {
return payType.pay(hoursWorked, payRate);
}
private enum PayType {
WEEKDAY {
double overtimePay(double hours, double payRate) {
return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
* payRate / 2;
}
},
WEEKEND {
double overtimePay(double hours, double payRate) {
return hours * payRate / 2;
}
};
private static final int HOURS_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay(double hoursWorked, double payRate) {
double basePay = hoursWorked * payRate;
return basePay + overtimePay(hoursWorked, payRate);
}
}
}
2.6 扩展
Java 中提供了两个方便操作enum的工具类——EnumSet 和 EnumMap。
EnumSet是枚举类型的高性能Set实现。它要求放入它的枚举常量必须属于同一枚举类型。EnumMap是专门为枚举类型量身定做的Map实现。虽然使用其它的 Map 实现(如HashMap)也能完成枚举类型实例到值得映射,但是使用 EnumMap 会更加高效:它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。这使得 EnumMap 的效率非常高。
System.out.println("EnumSet展示");
EnumSet<ErrorCodeEn> errSet = EnumSet.allOf(ErrorCodeEn.class);
for (ErrorCodeEn e : errSet) {
System.out.println(e.name() + " : " + e.ordinal());
}
System.out.println("EnumMap展示");
EnumMap<StateMachine.Signal, String> errMap = new EnumMap(StateMachine.Signal.class);
errMap.put(StateMachine.Signal.RED, "红灯");
errMap.put(StateMachine.Signal.YELLOW, "黄灯");
errMap.put(StateMachine.Signal.GREEN, "绿灯");
for (Iterator<Map.Entry<StateMachine.Signal, String>> iter = errMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry<StateMachine.Signal, String> entry = iter.next();
System.out.println(entry.getKey().name() + " : " + entry.getValue());
}