Effective Java(第三版) 学习笔记 - 第二章 创建和销毁对象 Rule5~9
目录
Effective Java(第三版) 学习笔记 - 第二章 创建和销毁对象 Rule5~9
Rule9 try-with-resource优先于try-finally
Rule5 优先考虑依赖注入来引用资源
对于做开发的来说,依赖注入、控制反转已经不是新鲜的名词了,Spring的依赖注入框架也早已被大家日常使用。
只需要记住,静态工具类和Singleton类不适合于需要引用底层资源的类。
顺便提一下书中提到了Java8的Supplier<T>
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
这个可以理解为一个创建指定对象的工厂
/**
* @author xuweijsnj
*/
public class SupplierTest {
private double num = Math.random();
public double getNum() {
return num;
}
public static void main(String[] args) {
Supplier<SupplierTest> s = SupplierTest::new;
SupplierTest s1 = s.get();
System.out.println(s1.getNum());
System.out.println(s1.getNum());
SupplierTest s2 = s.get();
System.out.println(s2.getNum());
}
}
---输出结果
0.308063828592685
0.308063828592685
0.15816737830758765
Rule6 避免创建不必要的对象
这个也是日常开发中代码review经常老生常谈的问题之一了。有兴趣的建议阅读下《重构 改善既有代码的设计》类似的书籍了解。
不变的对象:
//Reusing expensive object for improved performance (Pages 22 and 23)
public class RomanNumerals {
// Performance can be greatly improved! (Page 22)
static boolean isRomanNumeralSlow(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
// Reusing expensive object for improved performance (Page 23)
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeralFast(String s) {
return ROMAN.matcher(s).matches();
}
public static void main(String[] args) {
int numSets = 2000;
int numReps = 10000;
boolean b = false;
long start = System.currentTimeMillis();
for (int i = 0; i < numSets; i++) {
for (int j = 0; j < numReps; j++) {
b ^= isRomanNumeralSlow("MCMLXXVI"); // Change Slow to Fast to see performance difference
}
}
long end = System.currentTimeMillis();
System.out.println((end - start) + " milliseconds.");
// Prevents VM from optimizing away everything.
if (!b)
System.out.println();
}
}
----
isRomanNumeralSlow -> 18525 milliseconds.
isRomanNumeralFast -> 3408 milliseconds.
另一种多余的对象:自动装箱
public class Sum {
private static long sum() {
// long sum = 0L;
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
public static void main(String[] args) {
int numSets = 10;
long x = 0;
long start = System.currentTimeMillis();
for (int i = 0; i < numSets; i++) {
x += sum();
}
long end = System.currentTimeMillis();
System.out.println((end - start) + " milliseconds.");
}
}
----
long sum = 0L; -> 10237 milliseconds.
Long sum = 0L; -> 61812 milliseconds.
Rule7 消除过期的对象引用
Java开发相比C或者C++来说开发人员是比较幸福的,因为我们一般的开发中并不需要专门针对对象回收做特殊的处理,全部交由垃圾回收机制GC负责。
聊到GC,就会涉及到JVM的年轻代Minor GC、老年代Full GC,分代回收机制算法,以及多种垃圾回收器的选择:Serial收集器、ParNew、Parallel收集器、CMS收集器、G1收集器。(这里不做展开介绍)。
那么既然已经有GC帮助我们来清除过期失效对象了,为什么还会提到需要手动消除过期对象引用呢?我们用ArrayList的源码来说明。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
我们都知道ArrayList的底层是用数组实现的,而在需要删除或者清理List中的元素时,都会有elementData[i] = null;这样的代码,并同时附加了注释// clear to let GC do its work
那么为什么要这样做呢?因为当栈中出现不需要的元素对象时,即使栈不再引用这些对象,但是他们也不会被GC回收。因为栈内部维护着这些对象的过期引用,从而甚至有几率导致程序OOM。
一般来说,只要类是自己管理内存,程序员就应该警惕内存泄露问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
Rule8 避免使用终结方法和清除方法
终结方法:finalize
清除方法:cleaner(Java9中用清除方法替代了终结方法)
为什么不推荐用终结方法或清除方法?
因为,我们根本无法控制终结方法和清除方法会被即时的执行。这依赖于JVM的垃圾回收算法。
什么情况下才可以考虑使用终结方法或清除方法?
作为安全网(预防有资源没有关闭)或者是为了终止非关键的本地资源native关键字标注的本地方法。
Rule9 try-with-resource优先于try-finally
Java7中引入了try-with-resource写法,前提是try中的资源实现了AutoCloseable接口。
以往的try-finally
public class Copy {
private static final int BUFFER_SIZE = 8 * 1024;
// try-finally is ugly when used with more than one resource! (Page 34)
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
public static void main(String[] args) throws IOException {
String src = args[0];
String dst = args[1];
copy(src, dst);
}
}
try-with-resource改造
public class Copy {
private static final int BUFFER_SIZE = 8 * 1024;
// try-with-resources on multiple resources - short and sweet (Page 35)
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
public static void main(String[] args) throws IOException {
String src = args[0];
String dst = args[1];
copy(src, dst);
}
}
有什么好处?代码更简洁、清晰。而且不用显示的手写close,避免遗忘关闭资源。
本文技术菜鸟个人学习使用,如有不正欢迎指出修正。xuweijsnj