【Effective Java】5~9条依赖注入、不必要对象、过期引用、finalizer、try-with-resources

本文介绍了《EffectiveJava》中的编程原则,包括依赖注入以减少耦合,避免不必要的对象创建以提升效率,消除过期对象引用防止内存泄漏,以及避免使用终结方法和清除方法以确保可预测性和性能。文章通过代码示例讲解了如何使用try-with-resources语句优化资源管理,简化异常处理,并提供了相关最佳实践。
摘要由CSDN通过智能技术生成

看完《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.gcSystem.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
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值