IO 异常,属于检查型异常,必须通过 try catch 代码块捕获才能通过编译阶段,这也就没什么好介绍的了。
内存溢出错误,这类问题属于 Error,不属于 Exception,所以不要期待解决这类问题仅仅通过捕获就可以处理。
针对 Error 这类问题,我们没法捕获处理,只能是从避免的角度出发,分析出现的原因,尽量不用出现这类问题。
造成内存溢出的问题,有多种,大概就是图片问题、内存泄漏问题。
针对图片使用的优化处理,网上很多,各种压缩、降分辨率等等方式。
针对内存泄漏,一是开发期间遵守规范的代码行为,尽量避免写出有内存泄漏的隐患;二是发生内存泄漏后,借助相应工具进行定位分析。
这类错误很严重,表示程序陷入了死循环当中,原因也就是你写了有问题的代码。
因此,当出现这类问题,最好尽快定位处理。
这类问题,通常出现的场景是:编译阶段没问题,但程序运行期间却出现该问题。
原因一般是由于打包时,jar 出现问题,部分类没有打包进去,导致的问题。
这个异常,同样属于相关类找不到的问题,但出现的场景通常是由于程序中使用了反射,或者动态加载之类的方式,使用了错误的类名,导致的问题。
还有可能是由于混淆导致。
这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码,软件工程是门协作艺术,所以我们有义务让自己的代码能够直观的体现出尽量多的信息,而泛泛的 Exception 之类,恰恰隐藏了我们的目的。另外,我们也要保证程序不会捕获到我们不希望捕获的异常。比如,你可能更希望 RuntimeException 被扩散出来,而不是被捕获。
进一步讲,除非深思熟虑了,否则不要捕获 Throwable 或者 Error,这样很难保证我们能够正确处理异常。
如果我们不把异常抛出来,或者也没有输出到日志之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。
try-catch 代码段往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句 (if / else, switch)要低效
Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作,如果发生的非常频繁,这个开销可就不能被忽略了。
按照我们程序员的惯性认知:当遇到 return 语句的时候,执行函数会立刻返回。但是,在 Java 语言中,如果存在 finally 就会有例外。除了 return 语句, try 代码块中的 break 或 continue 语句也可能使控制权进入 finally 代码块。
请勿在 try 代码块中调用 return, break, continue 语句。万一无法避免,一定要确保 finally 的存在不会改变函数的返回值。
函数的返回值有两种类型:值类型和对象引用,对于对象引用,要特别小心,如果在 finally 代码块中对函数返回的对象成员属性进行了修改,即使不在 finally 块中显示调用 return 语句,这个修改也会作用于返回值上。
final 是 java 中的关键字,可用于修饰类,方法,变量。
当修饰类时,表明这个类不可被继承。Java 中有一些核心类都被 final 修饰了,比如 String,System。当考虑到安全性原因时,可以将该类设计成 final。
当修饰方法时,表明该方法不可被重写。一般是某些流程控制不希望被修改掉时,可以将这些方法声明成 final,比如 View 中的 measure(),requestFocus(),findViewById()。
当修饰变量时,表明该变量为常量,不允许被重新赋值,因此声明成 final 的变量都需要显示的进行赋值,否则编译会报错。
finally 是确保 try-catch 方式最后执行的一种机制,通常的用法都是在 finally 里进行一些资源的关闭,回收。比如 IO 流的关闭等等。
建议最好不要利用 finally 来控制流程,也不要在 finally 中有返回值,否则很容易影响正常流程,导致流程结构特别杂乱。
另外,有些特殊情况下,finally 中的代码并不会被执行到,比如:
//1.try-catch异常退出
try {
System.exit(1)
} catch {
....
} finally {
//不会执行到这里
Log.d("finally", "finally");
}
//2.无限循环
try {
while(true) {
...
}
} finally {
//不会执行到这里
Log.d("finally", "finally");
}
//3. 线程被杀死
//当执行 try-catch 时,线程被杀死了,那么 finally 里的代码也无法被执行到 总之,finally 通常情况下都会最后被执行到,所以最好不要在这里有 return 之类的语句来影响正常流程。但在某些特殊的场景下,finally 并不会被执行到,了解一下即可。
这个是 Object 中的一个方法,方法注释说了很多,大概就是讲这个方法是由垃圾收集器即将要回收该对象时会调用该方法,用户可在这里做一些最后的资源释放工作。
以上是概念定义,但说实话,没用过该方法,而且作者也说了,不推荐使用 finalize 机制来做资源回收,并且在 JDK 9,这个方法已经被标志为 deprecated 废弃的方法了。
作者有提到说,因为我们无法保证 finalize 什么时候执行,执行是否符合预期,使用不当还会影响性能,导致程序死锁、挂起等问题。
那么,有其他方案来替代 finalize 处理回收资源的工作么?有,Cleaner 机制,这个我没接触过,作者提了这个替代方案。另外,作者也说了,回收资源最好就是资源用完后就随手清除,或者结合 try-catch-finally 机制回收。不管是 finalize 或者 Cleaner 机制,最好都只将它看成是最后一道防线,一旦将主要的回收工作依赖于这两个机制的话,很容易出现各种问题。
为什么局部内部类和匿名内部类只能访问局部final变量?
先来看这么段代码:
//参数 msg 必须声明为 final 类型
public void notifyChange(final String msg) {
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText(msg);
}
})
} 在这里,post() 方法的参数是一个匿名内部类,在内部类中如果要使用外部 notifyChange() 方法的参数 msg,那么必须将 msg 类型声明成 final,否则编译器会保错。
这种场景非常常见的吧,不管是类似上述的 Ui 场景,还有网络访问时也经常需要通过回调通知上层,此时也就经常出现这种场景了。
那么,有考虑过,为什么内部类只能访问局部 final 变量么?
如果懂得反编译 class 文件的,那么应该就很清楚了。我也不懂,理解这点是通过阅读其他大神分析的文章,以下是我的理解:
首先,变量都是有生命周期的,成员变量的生命周期就跟随着对象的整个生命周期,而局部变量的生命周期则是非常有限。
比如方法内部的局部变量,它的生命周期就是在这个方法执行结束就终止。同样,方法的参数也是局部变量,它是生命周期也同样是到该方法执行结束。
另外,内部类的执行时机有时是会在外部方法执行结束之后。就拿上述例子来说,post() 中 Runnable 的执行时机,肯定是在外部 notifyChange() 方法执行完之后的。
那么,问题来了。内部类 Runnable 的执行需要使用到外部方法 notifyChange() 的参数,但当它执行的时候,这个参数的生命周期早已结束,已经被回收掉了。既然已经被回收了,内部类又是怎么使用外部的这个局部变量呢?
有大神反编译了 class 文件后,给出了结论,原来内部类使用外部的局部变量时,是通过 copy 一份过来。也就是说,其实内部类此时使用的是自己内部定义的局部变量了,只是这个变量的值是复制外部那个局部变量的而已。
这也就解释了,为什么外部的局部变量明明已经被回收了,内部类仍旧可以使用,因为内部类此时使用的并不是外部类的局部变量引用了。
但到这里,新的问题就来了:既然内部类使用的局部变量本质上跟外部的局部变量是相互独立的两个变量,那如果在内部类中修改了这个局部变量的值会出现什么情况?是吧,数据的不一致性。
基于此,java 编译器就直接限定死,内部类使用外部的局部变量时,必须将其限制为 final 类型,确保该变量不允许进行更改。
这样一来,其实也就顺便理解了,为什么成员变量可以直接在内部类中使用,因为成员变量的声明周期很长,不存在局部变量的问题。
以上内容,是在大神的文章里被醍醐灌顶了,感谢大神,原文链接放出来:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1446/viewspace-2805892/ ,如需转载,请注明出处,否则将追究法律责任。