每当提到Exeption就会有人跳出来说“Exception not use for flow control”,那到底是什么意思呢?什么情况下Exception就算控制流程了,什么时候就该抛出Exception了呢?
首先什么是Exception?
Definition: An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.
再看什么是“流程”?如果流程是指程序的每一步执行,那异常就是控制流程的,它就是用来区分程序的正常流程和非正常流程的,从上面异常的定义就可以看出。因此为了明确我们应该说”不要用异常控制程序的正常流程“。如何定义正常流程和非正常流程很难,这是一个主观的决定,没有一个统一的标准,只能根据实际情况。网上找个例子:
bool isDouble(string someString) {
try {
double d = Convert.ParseInt32(someString);
} catch(FormatException e) {
return false;
}
return true;
}
这个程序其实不是想convert数字,而是想知道一个字符串是否包含一个数字,通过判断是不是有异常的方式来决定返回true还是false,这是个Smell,这种应该算”异常控制了正常流程“。我们可以通过正则表达式或其他方式来判断。
另外Clean Code上一个例子:
try {
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e) {
m_total += getMealPerDiem();
}
MealExpensesNotFound异常影响了正常的计算m_total的业务逻辑。对于这种情况可以通过一下方式改进:
public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// return the per diem default
}
}
以上两个例子是比较明显的异常控制正常流程,Smell很明显,不会有很大争议,但是实际情况中可能有很多例子没有这么明显,因为都是主观判定的。比如一下代码,算不算异常控制正常流程?
public int doSomething()
{
doA();
try {
doB();
} catch (MyException e) {
return ERROR;
}
doC();
return SUCCESS;
}
看到这样一段程序,如果没有上下文,我们无法判断。但是如果doSomething是想让我们回答yes or no,success or error,我们不应该通过有无异常来判断yes or no,success or error,应该有个单独的方法来判断,这个方法就只做这一件事情。如果doSometing是执行一个操作,那么在这个过程中我们假定是不会出现问题的,否则抛出异常是比较合理的。
1. Java Class Loading Mechanism
首先当编译一个Java文件时,编译器就会在生成的字节码中内置一个public,static,final的class字段,该字段属于java.lang.Class类型,该class字段使用点来访问,所以可以有:
java.lang.Class clazz = MyClass.class
当class被JVM加载,就不再加载相同的class。class在JVM中通过(ClassLoader,Package,ClassName)来唯一决定。ClassLoader指定了一个class的scope,这意味着如果两个相同的包下面的class被不同的ClassLoader加载,它们是不一样的,并且不是type-compatible的。
JVM中所有的ClassLoader(bootstrap ClassLoader除外)都是直接或间接继承于java.lang.ClassLoader抽象类,并且人为逻辑上指定了parent-child关系,实现上child不一定继承于parent,我们也可以通过继承它来实现自己的ClassLoader。
JVM ClassLoder架构,从上到下依次为parent-child关系:
- Bootstrap ClassLoader - 启动类加载器,主要负责加载核心Java类如java.lang.Object和其他运行时所需class,位于JRE/lib目录下或-Xbootclasspath指定的目录。我们不知道过多的关于Bootstrap ClassLoader的细节,因为它是一个native的实现,不是Java实现,所以不同JVMs的Bootstrap ClassLoader的行为也不尽相同。调用java.lang.String.getClassLoder() 返回null。
- sun.misc.ExtClassLoader - 扩展类加载器,负责加载JRE/lib/ext目录及-Djava.ext.dirs指定目录。
- sun.misc.AppClassLoader - 应用类加载器,负责加载java.class.path目录
另外,还有一些其他的ClassLoader如:java.net.URLClassLoader,java.security.SecureClassLoader,java.rmi.server.RMIClassLoader,sun.applet.AppletClassLoader
- 用户还可以自己继承java.lang.ClassLoader来实现自己的ClassLoader,用来动态加载class文件。
ClassLoader特性:
- 每个ClassLoader维护一份自己的命名空间,同一个ClassLoader命名空间不能加载两个同名的类。
- 为实现Java安全沙箱模型,默认采用parent-child加载链结构,除Bootstrap ClassLoader没有parent外,每个ClassLoader都有一个逻辑上的parent,就是加载这个ClassLoader的ClassLoader,因为ClassLoader本身也是一个类,直接或间接的继承java.lang.ClassLoader抽象类。
java.lang.Thread中包含一个public的方法public ClassLoader getContextClassLoader(),它返回某一线程相关的ClassLoader,该ClassLoader是线程的创建者提供的用来加载线程中运行的classes和资源的。如果没有显式的设置其ClassLoader,默认是parent线程的Context ClassLoader。Java默认的线程上下文加载器是AppClassLoader。
ClassLoader工作原理:
了解ClassLoader工作原理,先来看一个ClassLoader类简化版的loadClass()方法源码
1 protected Class<?> loadClass(String name, boolean resolve)
2 throws ClassNotFoundException
3 {
4 synchronized (getClassLoadingLock(name)) {
5 // First, check if the class has already been loaded
6 Class c = findLoadedClass(name);
7 if (c == null) {
8 long t0 = System.nanoTime();
9 try {
10 if (parent != null) {
11 c = parent.loadClass(name, false);
12 } else {
13 c = findBootstrapClassOrNull(name);
14 }
15 } catch (ClassNotFoundException e) {
16 // ClassNotFoundException thrown if class not found
17 // from the non-null parent class loader
18 }
19
20 if (c == null) {
21 // If still not found, then invoke findClass in order
22 // to find the class.
24 c = findClass(name);
25 }
26 }
27 if (resolve) {
28 resolveClass(c);
29 }
30 return c;
31 }
32 }
首先查看该class是否已被加载,如果已被加载则直接返回,否则调用parent的loadClass来加载,如果parent是null代表是Bootstrap ClassLoader,则有Bootstrap ClassLoader来加载,如果都未加载成功,最后由该ClassLoader自己加载。这种parent-child委派模型,保证了恶意的替换Java核心类不会发生,因为如果定义了一个恶意java.lang.String,它首先会被JVM的Bootstrap ClassLoader加载自己JRE/lib下的,而不会加载恶意的。另外,Java允许同一package下的类可以访问受保护成员的访问权限,如定义一个java.lang.Bad,但是因为java.lang.String由Bootstrap ClassLoader加载而java.lang.Bad由AppClassLoader加载,