java的“值传递” + 异常抛出

方法调用(call by)(相当于c++中的“函数调用”),根据参数传递的情况分为值调用( call by reference ) 和引用调用( call by value ) 。

Java的对象参数传递仍然是值调用,这里强调的是“对象”的参数传递。

Java中除了primitive type以外一切都是对象。primitive type只有以下几种:QQ图片20151027211946

注意,就连我们熟知的数组或者String也都是对象。Java没有指针,所以对primitive type实现著名的swap函数就显得有些困难,一种可行的做法是先特地把它们包装成对象。

 

Java中对象变量名实际上代表的是对象在堆中的地址(专业术语叫做对象引用 )。在Java方法调用的时候,参数传递的是对象的引用。重要的是,形参和实参所占的内存地址并不一样,形参中的内容只是实参中存储的对象引用的一份拷贝。这正是“值传递”的表现形式。

通过传递对象引用,形参和实参指向了同一个地址。所以修改形参的成员变量,相当于修改了实参的成员变量

 

试着对一个对象实现swap。

交换失败。前面说到形参和实参都是对象引用,本质上是引用,那么上面那段代码只是把形参的两个引用交换了一下(相当于c++中,只是交换了指针,而不是交换指针所指向的内容),对实参的情况毫无影响。下面用图给出直观的描述:QQ图片20151027213817

由此可进一步理解,为什么说JAVA本质上仍然是值传递。

 

下面讲讲异常抛出。

若要实现以下功能:

QQ图片20151027214740

第一种方法,使用异常抛出:

第二种写法,不使用异常抛出:

显然第二种写法更简明清晰,也是我在学异常抛出之前做错误处理一贯的方式。这难道说明异常抛出反而是累赘吗?不是的,这实际上是一个失败的例子。

可以看到,在第一种写法中,既没有用到JAVA的“重新抛出异常”(就是在这个类的方法内部不处理异常,而是交给调用它的那个类的方法去处理,从里面一直往外抛, 简单说就是谁调用谁处理异常,我不管),而且也只有一层代码,事实上这种情况下很少强行使用异常抛出。

异常是有层级关系的错误处理机制,是专门抽象出来的一个用来处理错误的语法。子函数在出现异常的时候,可以自己处理掉,也可以抛给调用者处理,还可以自己处理一部分调用者处理一部分。处理异常的代码可以非常清晰地与逻辑主体隔绝开来。并且,Exception被捕获的话就不会终止程序,相比之下error则会使之宕掉。

可以设想这么一个场景,你在某个地方调用一个函数,这个函数的作用,是接受学生成绩并做相应的处理。但是,如题所说,输入的学生成绩可能是错误的。那么你在调用的地方怎么获取这个信息呢?两种办法,一种是用返回值表征状态,一种是用异常处理机制。此例中返回值可以有两个枚举值,0表示正常,1表示成绩不在0到100的闭区间内。

然而对于更一般的情况来说,返回值能表示的信息很有限,也就是,只能表示错误状态,却难以将更多的错误信息表达出来。比如如果接受的是一个数组,那么你可能还需要知道脱离0到100范围的数的下标是多少。出现别的错误时,你可能又想获取别的信息,这种情况下返回值就很难做到了。

所以后来我把代码改成了这个样子:

这样才不会背离“异常处理”的原则太远。

 

初初接触异常抛出,除了明白基本概念,我感觉自己还需要知道什么时候该用以及该怎么用,在网上找到了相关有营养的建议。

1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程。谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。

2.切忌使用空catch块。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。

3.不要将提供给用户看的信息放在异常信息里。因为业务逻辑跟用户界面要解耦合,展示给用户的错误提示信息若写死在里面了,万一要改成英文版的,不是又得改下程序;万一你觉得这个promt不合适,想换句好听点的话,又得改下程序;万一你要支持多语言,你还得在代码里面敲进N多种语言,再用一个swtich去拿…

其中一种做法是拿一个 txt 文件或者 ini 文件之类的,把错误编码跟错误提示文本存起来。然后主程序再跑去文件里面根据错误编码找到具体的对应提示文本,这就是一种配置文件。如下图:

QQ图片20151028224530

4.避免多次在日志信息中记录同一个异常。只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。

5.异常处理尽量放在高层进行。尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。

6.在finally中释放资源。如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。

 

reference:

Java的8种基本数据类型(primitive Type)

【解惑】Java方法参数是引用调用还是值调用?

JAVA异常重新抛出

Java异常处理和设计

以及,与@erueat的聊天

0 等你来赞