看完《Effective Java》感觉写的相当好,中间大部分都以作为参考手册,故整理出来。结合框架源码,jdk源码,自己的demo,给大家更多的示例,便于大家理解。每一条的介绍为个人理解,如果有任何不对,希望指出。对于博客有任何建议也希望给出建议。
5.优先考虑依赖注入来引用资源
依赖注入:把依赖的资源通过构造器传入给实例对象。用过spring的应该都知道他的好处了,但是
- 例子
public interface Computer {
public void program();
}
class WindowsComputer implements Computer{
@Override
public void program() {
System.out.println("windows");
}
}
class MacComputer implements Computer{
@Override
public void program() {
System.out.println("Mac");
}
}
/**
* 依赖注入:创建实例的时候,通过构造器传入实例
*
*/
public class DependencyDemo {
private Computer computer;
public DependencyDemo(Computer computer){
this.computer=computer;
}
public void function(){
computer.program();
}
}
6.避免创建不必要的对象
最好能重用单个对象,避免在每次需要的时候都创建一个新对象String s=new String("demo");
文中举了这个例子。这样会莫名的创建额外的一个对象,应当改为以下方式String s="demo";
,应当使用静态工厂方法优先于构造器,以避免创建不必要的对象。像Integer.valueOf
-128~127就会复用,如果使用了构造器,每次就会new一个对象了
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public Integer(int value) {
this.value = value;
}
此外也要避免自动装箱,优先使用基本数据类型而不是装箱基本类型,像下面例子,基本数据类型和对应对象之间差了6倍,不过在实际开发中用到的计算还是很少的,有数值类型计算也一般用BigDecimal
,但是用到的话的确要注意。
private static void autoboxing() {
//13ms
Integer i=1;
//2ms
//int i=1;
long start=System.currentTimeMillis();
for (int j = 0; j < 1000000; j++) {
i+=1;
}
long end=System.currentTimeMillis();
System.out.println(end-start);
}
7.消除过期的对象引用
书中给了这个例子(反例就用书中的例子了),Stack中的pop方法中返回对象后,elements仍然引用着返回的对象。但是在其他调用者看来应该是不存在了,日后就有肯能出现OOM。
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size ++] = e;
}
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapacity(){
if (elements.length == size){
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
书中提到了一个概念。过期引用,永远不会再被解除的引用。回到正题,但是在日常开发中,应当避免这样。应当修改为,这样GC就会进行回收。根可达算法,由于没有引用指向,就会进行回收
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
Object o=elements[--size];
elements[size] =null;
return o
}
但是就算这样还是可能OOM,比如,就是有些场景,会占用大量引用不放,这种场景也是要小心的。此外文章提倡使用WeakHashMap
它是弱引用,里面键的存在,不会阻止值被丢弃。(不过我还没用过,回头研究下)
8.避免使用终结方法和清除方法
这条大致就是避免使用finalizer(java9过时)和cleaner(java9)。finalizer(java9过时)和cleaner(java9),因为这两个方法不可预测(不能保证会被执行),运行缓慢。即使调用了System.gc
和System.runFinalization
也不能保证被执行。
如果需要确保一些资源终止(输入输出流),一般使用try-catch-finally,或者实现AutoCloseable
利用try-with-resources。
这部分实在找不到啥合适的例子,也不常用就不整理例子了。
9.try-with-resources 优先 try-catch-finally
try-catch-finally地狱,最典型的应该就是输入输出流了,尝试回忆了下学校里面的写法,最原始的,也可以中间的try-catch省略,把关闭的写到一起,写个小脚本基本没有啥问题,不过之前又一次用代码检测软件会爆出来违规。这下面也就是现写的一个例子,基本实际开发中还是去找工具类的。这里主要体现繁琐,因为close也有可能出现异常,所以又是try-catch包裹一层,简直是地狱。
public static void main(String[] args) {
File fileInput = new File("/Users/chenhui/Downloads/aa.txt");
String path="/Users/chenhui/Downloads/testtttt";
String name="bb.txt";
File pathFile = new File(path);
if(!pathFile.exists()){
pathFile.mkdirs();
}
String relFilePath = path + File.separator + name;
File fileOutput = new File(relFilePath);
if (!fileOutput.exists()) {
try {
fileOutput.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
FileInputStream fileInputStream=null;
FileOutputStream fileOutputStream=null;
try {
fileInputStream = new FileInputStream(fileInput);
try{
fileOutputStream = new FileOutputStream(fileOutput);
byte[] b = new byte[1024];
int len;
while ((len = fileInputStream.read(b)) != -1){
fileOutputStream.write(b,0,len);
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//输出完毕,关闭输出流
fileOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
//空指针异常
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果用java7的try-resource来实现。
private static void extra2() {
File fileInput = new File("/Users/chenhui/Downloads/aa.txt");
String path="/Users/chenhui/Downloads/testtttt";
String name="bb.txt";
File pathFile = new File(path);
if(!pathFile.exists()){
pathFile.mkdirs();
}
String relFilePath = path + File.separator + name;
File fileOutput = new File(relFilePath);
if (!fileOutput.exists()) {
try {
fileOutput.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try(FileInputStream fileInputStream= new FileInputStream(fileInput);
FileOutputStream fileOutputStream= new FileOutputStream(fileOutput);
){
byte[] b = new byte[1024];
int len;
while ((len = fileInputStream.read(b)) != -1){
fileOutputStream.write(b,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
因为实现了Closeable
可以自动帮我们关闭流,用起来蛮方便的,反编译一下,就可以看到这是个语法糖,关闭流的问题可能会很多,官方帮我们搞定了,方便我们操作,你只要实现Closeable
private static void extra2() {
File var0 = new File("/Users/chenhui/Downloads/aa.txt");
String var1 = "/Users/chenhui/Downloads/testtttt";
String var2 = "bb.txt";
File var3 = new File(var1);
if (!var3.exists()) {
var3.mkdirs();
}
String var4 = var1 + File.separator + var2;
File var5 = new File(var4);
if (!var5.exists()) {
try {
var5.createNewFile();
} catch (IOException var39) {
var39.printStackTrace();
}
}
try {
FileInputStream var6 = new FileInputStream(var0);
Throwable var7 = null;
try {
FileOutputStream var8 = new FileOutputStream(var5);
Throwable var9 = null;
try {
byte[] var10 = new byte[1024];
int var11;
while((var11 = var6.read(var10)) != -1) {
var8.write(var10, 0, var11);
}
} catch (Throwable var40) {
var9 = var40;
throw var40;
} finally {
if (var8 != null) {
if (var9 != null) {
try {
var8.close();
} catch (Throwable var38) {
var9.addSuppressed(var38);
}
} else {
var8.close();
}
}
}
} catch (Throwable var42) {
var7 = var42;
throw var42;
} finally {
if (var6 != null) {
if (var7 != null) {
try {
var6.close();
} catch (Throwable var37) {
var7.addSuppressed(var37);
}
} else {
var6.close();
}
}
}
} catch (FileNotFoundException var44) {
var44.printStackTrace();
} catch (IOException var45) {
var45.printStackTrace();
}
}
但是try-catch-finally除了流之外还是十分常用的
举个栗子Mybatis的org.apache.ibatis.session.SqlSessionManager.SqlSessionInterceptor
捕获方法调用异常,mybatis其他地方也有用到(太长时间没看,有点忘了),比如sql执行如果出问题,要确保连接关闭,finally可以自己定义还是很爽的,(胆子大也可以整片全try住,保证不抛异常🐶)。
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var12) {
throw ExceptionUtil.unwrapThrowable(var12);
}
} else {
SqlSession autoSqlSession = SqlSessionManager.this.openSession();
Object var7;
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var7 = result;
} catch (Throwable var13) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var13);
} finally {
autoSqlSession.close();
}
return var7;
}
}
}
- 往期链接
- 1~4条:https://blog.csdn.net/goligu/article/details/118868033