异常处理
Exception 和 Error 都继承 Throwable 。只有 Throwable 类的对象才能被抛出(throw)或者捕获(catch)。
Throwable 分为可检查(checked)和不检查(unchecked)异常。
不检查异常是指程序运行时抛出的异常,比如 OutOfMemoryError、StackOverflowError 等,这些属于 Error。
可检查异常就是 Exception,比如数组越界(ArrayIndexOutOfBoundsException),空指针(NullPointerException)等异常。
注意事项
- 不要直接捕获 Exception,要精确的捕获具体的异常。
- 不要生吞异常,不要在 catch 语句中掩盖异常的处理,这会使得错误难以被发现和修改。如果不能处理就应该抛出去,让上一层的业务去处理。
- 不要使用 try-catch 去包裹整个代码段,try-catch 语句会产生额外的性能开销,仅捕获有必要的代码段。
- 不要在 finally 中处理返回值
- 不要在 try 语句中使用 return break continue 等操作。
final、finally、finalize 有什么不同?
final
final 可以修饰类、方法、变量,分别有不同的意义。
final 修饰的 class 代表不可以继承扩展,final 修饰的变量不能被修改,final 的方法不能被重写。
我们可以将方法或者类声明为 final ,这样可以明确告诉别人,这些行为不允许被修改。
final 不等同于 immutable
final List strList = new ArrayList<>();
strList.add("hello");
strLsit.add("world");
final 可以保证 strList 这个引用不会被赋值,但是不能约束 strList 的行为。
如果要实现 immutable 的类。需要:
- 将 class 本身声明为 final,这样别人就不能通过扩展来绕过限制。
- 将所有的成员定位为 private 和 final。并且不要实现 setter 方法。
- 构造对象的时候,成员变量使用深度拷贝来初始化。而不是直接赋值。这是一种防御措施,因为无法确定输入的对象不被修改。
- 如果需要实现 getter 方法,或者返回内部状态的方法。使用 copy-on-write 原则,创建私有拷贝。
finally
try-catch-finally 语句中 finally 语句块是一定会被执行的。
可以在 finally 中进行类似 JDBC 关闭连接、保证 unlock 锁等操作。
finalize
finalize 是基础类 java.lang.Object 中的一个方法,它的设计目的是保证该对象在垃圾收前完成特定资源的回收。finalize 机制已经不推荐被使用,并在 jdk9 中开始被标记为 deprecated。
强引用,弱引用,软引用,幻象引用
在 Java 中出了原始数据类型的变量,其他所有都是所谓的引用类型。
强引用
这是我们最常见的不同对象的引用,只要还有一个强引用只想一个对象,就表明对象还“活着”,垃圾收集器不会碰这些对象。
对象创建初始化之后,就是强引用。
特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
软引用
特点:软引用通过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
弱引用
弱引用通过WeakReference类实现。 弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用同样可用于内存敏感的缓存。
幻象引用
不能通过它来访问对象,幻象引用仅提供一种确保对象被 finalize 以后,做某些事情机制。比如通常用来做 Post-Mortem 清理机制
特点:虚引用也叫幻象引用,通过PhantomReference类来实现。无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
String、StringBuffer、StrinBuilder
String
Java 语言中的基础类。
(1) String的创建机理
由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。
举例:String str1 = "123"; //通过直接量赋值方式,放入字符串常量池
String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池
注意:String提供了inter()方法。调用该方法时,如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。
(2) String的特性
[A] 不可变。是指String对象一旦生成,则不能再对它进行改变。不可变的主要作用在于当一个对象需要被多线程共享,并且访问频繁时,可以省略同步和锁等待的时间,从而大幅度提高系统性能。不可变模式是一个可以提高多线程程序的性能,降低多线程程序复杂度的设计模式。
[B] 针对常量池的优化。当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
StringBuffer/StringBuilder
为字符串拼接为产生的类。可以使用 append 或者 add 方法将字符串追加到末尾或者添加到特定位置。
StringBuffer 是线程安全的,但是也会带来性能开销。
StringBuilder 在能力上和 StringBuffer 没有却别,但是去掉了线程安全的部分。有效减小开销,是大多数情况下的首选。
StringBuffer和StringBuilder都实现了AbstractStringBuilder抽象类,拥有几乎一致对外提供的调用接口;其底层在内存中的存储方式与String相同,都是以一个有序的字符序列(char类型的数组)进行存储,不同点是StringBuffer/StringBuilder对象的值是可以改变的,并且值改变以后,对象引用不会发生改变;两者对象在构造过程中,首先按照默认大小申请一个字符数组,由于会不断加入新数据,当超过默认大小后,会创建一个更大的数组,并将原先的数组内容复制过来,再丢弃旧的数组。因此,对于较大对象的扩容会涉及大量的内存复制操作,如果能够预先评估大小,可提升性能。
唯一需要注意的是:StringBuffer是线程安全的,但是StringBuilder是线程不安全的。可参看Java标准类库的源代码,StringBuffer类中方法定义前面都会有synchronize关键字。为此,StringBuffer的性能要远低于StringBuilder。
应用场景
[A]在字符串内容不经常发生变化的业务场景优先使用String类。例如:常量声明、少量的字符串拼接操作等。如果有大量的字符串内容拼接,避免使用String与String之间的“+”操作,因为这样会产生大量无用的中间对象,耗费空间且执行效率低下(新建对象、回收对象花费大量时间)。
[B]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用StringBuffer,例如XML解析、HTTP参数解析与封装。
[C]在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用StringBuilder,例如SQL语句拼装、JSON封装等。