• 《Effective Java》第1章 创建和销毁对象


    第1条 用静态工厂方法代替构造器

    这个静态工厂,与设计模式中的静态工厂不同,这里的静态工厂方法,替换为“静态方法”比较好理解,主要就是建议编写静态方法来创建对象。

    使用静态方法的好处:

    1、静态方法有名称,可以确切地描述功能,比如根据哪些参数,返回哪种类型;

    2、不需要先创建对象实例,再调用方法来创建所需的对象;使用静态方法可以直接使用类名加静态方法名即可调用;

    3、可以返回原类型的任何子类型对象;

    4、可以通过传入不同的参数,获得不同的返回值;

    5、方法返回的对象所属的类,在编写包含该静态方法的类时可以不存在,比如JDBC API;

    6、使用构造方法创建对象时的开销,相对于使用静态方法来说,要大得多,比如Boolean.valueOf(Stirng)与new Boolean(String);

    缺点和不足

    1、静态方法中要想返回对象,那么该对象所属的类必须有可以访问的构造器,否则不能被实例化;

    2、静态方法比较难被发现;

    第2条 遇到多个构造器时考虑使用构建器

    当类的构造器或者静态工厂中具有较多的参数时,或者参数可能后期会增加时,那么在设计类的时候,可以使用Builder模式。

    package cn.ganlixin.effective_java;
    
    public class BuilderTest {
        public static void main(String[] args) {
            final Person person = new Person.Builder().id(1).name("ganlixin").build();
        }
    }
    
    class Person {
        private int id;
        private String name;
      
        private Person(Builder builder) {
            id = builder.id;
            name = builder.name;
        }
    
        public static class Builder {
          	// Builder一般来说包含有外层类相同的属性
            private int id;
            private String name;
          
            public Builder id(int val) { this.id = val; return this; }
            public Builder name(String val)  { this.name = val;  return this; }
            public Person build() { return new Person(this); }
        }
    }
    

      

      上面的代码,使用Lombok的话,只需要使用一个@Builder注解即可:

    package cn.ganlixin.effective_java;
    
    import lombok.Builder;
    
    public class LombokBuilder {
    
        public static void main(String[] args) {
            final Person person = new Person.PersonBuilder().id(1).name("ganlixin").build();
        }
    }
    
    @Builder
    class Person {
        private int id;
        private String name;
    }
    

      

    第3条:用私有构造器或者枚举类型强化Singleton属性

      单例模式:

      1、通过私有化构造器,可以防止客户端使用new关键字创建对象;

      2、提供静态方法获取单例对象;

      以上两点存在的问题,可以使用AccessibleObject.setAccessible()方法通过反射机制调用构造器,可以在构造其中判断是否已经创建实例,如果已创建,则抛出异常;

      3、单例对象的序列化问题;

      使用枚举类型创建单例属性,例子如下:

    public class SingletonEnum {
        public static void main(String[] args) {
            final OneDemo instance = OneDemo.INSTANCE;
            instance.test();
        }
    }
    
    enum OneDemo {
        INSTANCE;
        
        // 定义其他function
        public void test() {
            //  .........
        }
    }
    

      

    第4条:通过私有构造器强化不可实例化的能力

    当我们创建工具类的时候,一般来说工具类不应该被实例化,虽然可以将工具类声明为抽象类来避免被实例化,但是这样并不好,因为抽象类的子类可以实例化!

    解决方案:显示声明工具类的无参构造方法,在其中抛出异常即可。

    class MyUtil {
    
        private MyUtil(){ throw new RuntimeException("工具类不应该被实例化"); }
    
        // 声明工具类中的方法
        public static void myMethod(){}
    }
    

      

     第5条:优先考虑依赖注入来引用资源

      主要就是讲依赖注入。

      问题是这样的:有一个邮件白名单,对于接收到的邮件,需要先进行过滤一遍(匹配白名单),那么可能会写出下面的代码:

    // 邮件过滤器
    class MailChecker{
        
        // 白名单列表,在代码中写死
        private List<String> whiteList = new ArrayList<String>(){{
            add("123"); add("456"); add("789");
        }};
        
        // 进行检验操作
        public boolean isValid(String email) {
            return whiteList.contains(email);
        }
    }
    

      上面的代码其实并不好,原因:

      1、过滤名单写死了;

      2、只能进行邮件过滤;这一点可能会有疑惑,功能专一不好吗?这没问题,但是如果有一个“电话过滤器”是不是又要有一个PhoneChecker呢?

      推荐做法:在过滤器中不写死whiteList,白名单由外部传入,过滤器的功能就是进行过滤而已,改为如下:

    public class UseInject {
        public static void main(String[] args) {
            // 假设phoneList是外界传入的,或者是文件中读入的
            List<String> phoneList = null;
            Checker phoneChecker = new Checker(phoneList);
            phoneChecker.isValid("123");
        }
    }
    
    class Checker{
        private List<String> whiteList;
        
        // 接收外界传入的白名单(可以是电话、邮箱、地址....)
        public Checker(List<String> whiteList) {
            this.whiteList = whiteList;
        }
        
        // 检验操作
        public boolean isValid(String val) {
            return whiteList.contains(val);
        }
    }
    

      如果是使用Spring这些框架,可以直接利用依赖注入,比如:

    public class UseInject {
        
        @Value("${phoneList}")
        private static List<String> phoneList;
    
        public static void main(String[] args) {
            Checker phoneChecker = new Checker(phoneList);
            phoneChecker.isValid("123");
        }
    }
    

      

    第6条:避免创建不必要的对象

      1、虽然Java有自动装箱和自动装箱,但是请尽量使用基本数据类型;

      2、对于提供了静态工厂方法和构造器的类来说,应该优先使用静态方法而非构造器创建对象;

      3、有一些对象不应该被重复创建,比如正则表达式的Pattern实例;

      4、应该避免维护自己的对象池,数据库连接池除外,因为建立数据库连接的代价比较大,创建普通对象的代价则相对来说很小;

      5、不要太死板,有些时候,为了避免创建不必要的对象,会付出更多的代价;

    第7条:消除过期的对象引用

      1、如果类是自己管理内存,那么就需要需要警惕内存泄漏问题,一旦元素被释放掉,则该元素包含的任何对象应用都应该被清空;

      2、缓存导致内存泄漏,可以使用WeakHashMap;

      3、如果提供服务给客户端调用,要警惕客户端的反复调用是否会反复产生对象,但是却没有清理垃圾;

    第8条:避免使用终结方法和清除方法

      1、终结方法(finalizer)和清除方法(cleaner)不能保证会被及时的执行,从一个对象变为不可达开始,到执行这两个方法的事件是任意长的,所以,注重时间的任务不应该由finalizer和cleaner来完成;

      2、永远不应该依赖finalizer和cleaner方法来更新重要的持久状态,因为这两个方法可能没有机会被执行;

      3、System.gc和System.runFuinalization只能增加finalizer和cleaner的执行机会,注意,增加可能性,但不是保证,100%;

      4、finalizer中出现异常,终结过程会终止,可怕吧,想死还死不掉;

      5、降低程序性能、finalizer attack;

    第9条:try-with-resource优先于try-finally

      要使用try-with-resource,需要类实现AutoCloseable接口(大多数类都实现了)。

      下面以书中的例子举例:

    public static void notSuggest() throws IOException {
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("demo.txt"));
            bufferedReader.readLine();
        } finally {
            bufferedReader.close();
        }
        
        // 注意,上面的demo.txt不存在时,报异常FileNotFoundException,然后finally中的close也会报错,但是close的异常会覆盖readLine的异常
        // 所以调用notSuggest()方法,只会看到close的异常Exception in thread "main" java.lang.NullPointerException
    }
    

      改成下面这样就可以捕获所有异常了,但是看起来,代码好臃肿:

    public static void notSuggest2() {
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader("demo.txt"));
            bufferedReader.readLine();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 虽然可以打印出所有的异常信息(FileNotFoundException和close的NullPointerException异常)
        // 按照常识,如果前面一步已经错了,就不应该再走下去,但是FileNotFoundException之后,还是会执行finally中的close,结果又出现异常
    }
    

      推荐使用try-with-resource

    public static void suggest() {
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader("demo.txt"))) {
            bufferedReader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 不需要catch每一个子异常,比如FileNotFoundException,直接写一个IOException或者Exception即可
        // 上面demo.txt不存在,所以会出现java.io.FileNotFoundException,但是没有报第二个异常哦
    }
    

      

     

  • 相关阅读:
    Microsoft Enterprise Library
    TCP拥塞控制算法内核实现剖析(三)
    Linux内核链表实现剖析
    sk_buff 剖析
    TCP拥塞控制算法内核实现剖析(一)
    set time zone Ubuntu
    xml listview
    VSTO rtm assembly
    Assembly and ActiveX
    input a long sentence in a single line of textbox
  • 原文地址:https://www.cnblogs.com/-beyond/p/11531197.html
Copyright © 2020-2023  润新知