zoukankan      html  css  js  c++  java
  • Effective Java 读书笔记(一):创建和销毁对象

    1 构造器 => 静态工厂方法

    (1)优势

    • 静态工厂方法有名字
    • 静态工厂方法不必在每次被调用时都产生一个新的对象
    • 静态工厂方法能返回原返回类型的任意子类型的对象
    • 静态工厂方法根据调用时传入的不同参数而返回不同类的对象
    • 静态工厂方法返回对象的类不需要存在(SPI架构)

    (2)限制

    • 没有公有或者保护构造方法的类不能子类化(但是可以鼓励我们使用组合模式,而不是继承模式)
    • 静态工厂方法难以发现

    (3)常用静态工厂方法命名

    • from:传入单个参数,返回该类型实例
    Date d = Date.from(instant);
    
    • of:传入多个参数,返回一个包含这些参数的该类型实例
    Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
    
    • valueOf:from和of的替换方案
    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
    
    • instance or getInstance:创建一个由参数(如果有的话)描述的实例
    StackWalker luke = StackWalker.getInstance(options);
    
    • create or newInstance:类似instance或getInstance, 但保证每次调用都返回新实例
    Object newArray = Array.newInstance(classObject, arrayLen);
    
    • getType:类似getInstance,但一般在工厂方法包含在不同类的情况下使用。Type是工厂方法返回的对象的类型。
    FileStore fs = Files.getFileStore(path);
    
    • newType:类似于newInstance,但一般在工厂方法包含在不同类的情况下使用。
    BufferedReader br = Files.newBufferedReader(path);
    
    • type:getType和newType简洁的替换方式
    List<Complaint> litany = Collections.list(legacyLitany);
    

    2 构造器 => 构建者

    (1)问题

    public class NutritionFacts {
        private final int servingSize; // (mL) required 
        private final int servings;    // (per container) required
        private final int calories;    // (per serving) optional    
        private final int fat;         // (g/serving) optional
        private final int sodium;      // (mg/serving) optional
        private final int carbohydrate; // (g/serving) optional
        public NutritionFacts(int servingSize, int servings) { 
            this(servingSize, servings, 0);
        }
        public NutritionFacts(int servingSize, int servings, int calories) {
            this(servingSize, servings, calories, 0); 
        }
        public NutritionFacts(int servingSize, int servings, int calories, int fat) {
            this(servingSize, servings, calories, fat, 0); 
        }
        public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
            this(servingSize, servings, calories, fat, sodium, 0); 
        }
        public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, 
            int carbohydrate) {
            this.servingSize = servingSize; this.servings = servings;
            this.calories = calories
            this.fat = fat
            this.sodium = sodium
            this.carbohydrate = carbohydrate;
        } 
    }
    
    • 构造器方法数量迅速膨胀,因为有大量的可选项
    • 可伸缩构造器(使用可变参数)是可行,只是当有很多参数时,会让客户端代码很难编写,而且代码也很难阅读。

    (2)替代方案:JavaBeans模式

    public class NutritionFacts {
        private int servingSize = -1; // Required; no default value 
        private int servings = -1; // Required; no default value
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
        public NutritionFacts() {}
        // Setters
        public void setServingSize(int val) { 
            servingSize = val; 
        } 
        public void setServings(int val) { 
            servings = val; 
        }
        public void setCalories(int val) {
            calories = val;
        }
        public void setFat(int val) {
            fat = val;
        }
        public void setSodium(int val) {
            sodium = val;
        }
        public void setCarbohydrate(int val) { 
            carbohydrate = val; 
        }
    }
    

    缺点:

    • 构造过程被分到了多个调用中,一个JavaBean在其构造过程中可能处于不一致的状态。

    • 类无法仅仅通过检查构造器参数的有效性来保证一致性。

    (3)替代方案:Builder模式

    • 例1
    // Builder Pattern
    public class NutritionFacts {
        private final int servingSize;
        private final int servings;
        private final int calories;
        private final int fat;
        private final int sodium;
        private final int carbohydrate;
    
        public static class Builder {
            // Required parameters
            private final int servingSize;
            private final int servings;
            // Optional parameters - initialized to default values
            private int calories = 0;
            private int fat = 0;
            private int sodium = 0;
            private int carbohydrate = 0;
            public Builder(int servingSize, int servings) {
                this.servingSize = servingSize;
                this.servings = servings;
            } 
            public Builder calories(int val){ 
                calories = val; return this; 
            }
            public Builder fat(int val){ 
                fat = val; return this; 
            }
            public Builder sodium(int val){ 
                sodium = val; return this; 
            }
            public Builder carbohydrate(int val){ 
                carbohydrate = val; return this; 
            }
            public NutritionFacts build() {
                return new NutritionFacts(this);
            }
        } 
        private NutritionFacts(Builder builder) {
            servingSize = builder.servingSize;
            servings = builder.servings;
            calories = builder.calories;
            fat = builder.fat;
            sodium = builder.sodium;
            carbohydrate = builder.carbohydrate;
        }
    }
    
    // 使用
    NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
                                                .calories(100)
                                                .sodium(35)
                                                .carbohydrate(27)
                                                .build();
    
    • 例2
    public abstract class Pizza {
        public enum Topping { 
            HAM, MUSHROOM, ONION, PEPPER,SAUSAGE 
        }
        final Set<Topping> toppings;
        abstract static class Builder<T extends Builder<T>> {
            EnumSet<Topping> toppings =
            EnumSet.noneOf(Topping.class);
            public T addTopping(Topping topping) {
                toppings.add(Objects.requireNonNull(topping));
                return self();
            } 
            abstract Pizza build();
            protected abstract T self();
        } 
        Pizza(Builder<?> builder) {
            toppings = builder.toppings.clone(); // See Item 50
        }
    }
    
    public class NyPizza extends Pizza {
        public enum Size { 
            SMALL, MEDIUM, LARGE 
        }
        private final Size size;
    
        public static class Builder extends Pizza.Builder<Builder> {
            private final Size size;
            public Builder(Size size) {
                this.size = Objects.requireNonNull(size);
            } 
            public NyPizza build() {
                return new NyPizza(this);
            } 
            protected Builder self() { 
                return this; 
            }
        } 
    
        private NyPizza(Builder builder) {
            super(builder);
            size = builder.size;
        }
    } 
    
    NyPizza pizza = new NyPizza.Builder(SMALL)
        					.addTopping(SAUSAGE)
        					.addTopping(ONION).build();
    

    优势:

    • builder能拥有多个可变参数(例1)
    • builder能将传入到不同方法里的参数聚合起来然后传入单个域里(例2)

    缺点:

    • 多创建一个Builder对象的内存开销

    3 使用枚举强化单例模式

    (1)饿汉

    public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        private Elvis() {}
        public void leaveTheBuilding() {} 
    }
    
    public class Elvis {
        private static final Elvis INSTANCE = new Elvis();
        private Elvis() {}
        public void leaveTheBuilding() {} 
        
        public static Elvis instance(){
            return INSTANCE;
        }
    }
    

    (2)内部类

    public class Elvis {
        private Elvis() {}
        public void leaveTheBuilding() {} 
        
        // 通过类加载机制来保证线程安全
        private static class Holder{
            private static final Elvis INSTANCE = new Elvis();
        }
        
        public static Elvis instance(){
            return Holder.INSTANCE;
        }
    }
    

    (3)双重检验锁

    public class Elvis {
        private Elvis() {}
        public void leaveTheBuilding() {} 
        
        private static volatile Elvis INSTANCE;
        
        public static Elvis instance(){
            if(INSTANCE == null){
                synchronized(Elvis.class){
                    if (INSTANCE == null) {  
               			INSTANCE = new Elvis();  
          			} 
                }
            }
            return INSTANCE;
        }
    }
    

    (4)枚举

    public enum Elvis {
        INSTANCE;
        public void leaveTheBuilding() {} 
    }
    

    (5)总结

    • 第2、3种具备懒加载

    • 第4种具备禁止反序列化创建对象问题

    • 第1~3种如果实现了Serializable接口的补偿措施

      // 方法1
      private static Class getClass(String classname) throws ClassNotFoundException {
      	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      	if(classLoader == null)     
               classLoader = Singleton.class.getClassLoader();     
      
            return (classLoader.loadClass(classname));     
         }     
      }  
      
      // 方法2
      // 重写readReslove方法,需要将所有成员变量声明为transient
      private Object readResolve() {     
          return INSTANCE;     
      }    
      

    参考:

    4 私有化构造器强化不可实例化的能力

    public class UtilityClass {
    	// Suppress default constructor for noninstantiability
        private UtilityClass() {
            throw new AssertionError();
        }
    	 // Remainder omitted 
    }
    

    5 优先使用依赖注入而不是硬连接资源

    静态工具类和Singleton对于类行为需要被底层资源参数化的场景是不适用的。

    (1)构造器注入

    public class SpellChecker {
        private final Lexicon dictionary; // 所需的底层资源
        
        public SpellChecker(Lexicon dictionary) {
            this.dictionary = Objects.requireNonNull(dictionary);
        } 
        
        public boolean isValid(String word) { ... }
        public List<String> suggestions(String typo) { ... }
    }
    

    (2)Setter注入

    public class SpellChecker{
        private Lexicon dictionary; // 所需的底层资源
        
        void setDictionary(Lexicon dictionary){
            this.dictionary = dictionary;
        }
        
        public boolean isValid(String word) { ... }
        public List<String> suggestions(String typo) { ... }
    }
    

    (3)接口注入

    public interface DictionaryDependent{
        void setDependence(Lexicon dictionary);
    }
    
    public class SpellChecker implement DictionaryDependent{
        private Lexicon dictionary; // 所需的底层资源
        
        void setDependence(Lexicon dictionary){
            this.dictionary = dictionary;
        }
        
        public boolean isValid(String word) { ... }
        public List<String> suggestions(String typo) { ... }
    }
    

    参考:

    6 避免创建不必要的对象

    • String s = "bikini"; ====> String s = new String("bikini");
    • Boolean.valueOf(String) :内部只维护了两个对象TRUEFALSE
    • 判断是否为数字
    // 每次都生成Pattern对象,造成内存浪费
    static boolean isRomanNumeral(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); 
    }
    
    public class RomanNumerals {
        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 isRomanNumeral(String s) { 
            return ROMAN.matcher(s).matches();
        } 
    }
    
    • Map.keySet():每次返回同一个实例,懒加载模式
    • 注意自动装箱问题
    • 尽量不要维护对象池,除非对象创建开销太大,如:JDBC数据库连接池

    7 消除过时的对象引用

    (1)内存泄漏存在情况与解决

    • 一个类自己管理它的内存(没用的对象没有置空):显式置null
    • 缓存相关:
      • 设置淘汰策略:设置超时清除
      • 自制回收线程
      • 内存占用做限制
      • 使用WeakHashMap容器
    • 监听器相关:
      • 使用WeakHashMap容器

    (2)WeakHashMap的实现原理

    • Entry继承WeakReference类:下一次垃圾回收就会回收掉该对象
    • WeakHashMap内部一个ReferenceQueue:被回收的对象将放入该队列中
    • 任何对WeakHashMap的操作都会进行一次同步操作:ReferenceQueue与Entry[]的同步操作,把Entry过期对象清除,ReferenceQueue清空。
    // 回收操作和把对象放入引用队列由JVM处理,WeakHashMap只需要创建WeakReference时,把ReferenceQueue放入即可
    
    // 同步操作:该方法被WeakHashMap中的所有操作涉及,代表只要进行操作就会进行同步,可能你会担心性能问题,但是实际上如果queue中没有数据时,直接就返回了。
    private void expungeStaleEntries() {
        // 循环清除queue中的元素
        for (Object x; (x = queue.poll()) != null; ) {
            // 防止并发
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);
    
                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        // 协助GC操作,清除数组中的元素
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
    

    8 避免使用finalize方法

    • finalize的调用时机无法把握

      • finalizer线程优先级低,可能还没回收就发生OOM异常
      • System.gc也无法保证一定会执行
    • 导致严重的性能损失

    • 类暴露于终结方法攻击:

      • 在终结过程中若有未被捕获的异常抛出,则抛出的异常会被忽略,而且该对象的终结过程也会终止。
      • 当构造器或者序列化中抛出异常,恶意子类的终结方法可以运行在本应夭折的只构造了部分的对象上(强行救活父类对象)。此时子类就可以调用该对象上的任意方法,但实际上该对象应该不存在才对。
      • final类能免疫于此类攻击,因为没有类能对final类进行恶意继承。
      • 为了防止非final类遭受终结方法攻击,我们可以写一个什么都不做而且是final的终结方法。
    • 替代方案:继承AutoCloseable接口,close方法在被关闭后还被调用,就要抛出一个IllegalStateException异常。

    public class Room implements AutoCloseable {
        private static final Cleaner cleaner = Cleaner.create();
        
        // Resource that requires cleaning. Must not refer to Room!
        private static class State implements Runnable {
            int numJunkPiles; 
            // Number of junk piles in this room
            State(int numJunkPiles) {
                this.numJunkPiles = numJunkPiles;
            }
            
            // Invoked by close method or cleaner
            @Override 
            public void run() {
                System.out.println("Cleaning room");
                numJunkPiles = 0;
            }
        } 
        // The state of this room, shared with our cleanable
        private final State state;
        // Our cleanable. Cleans the room when it’s eligible for gc
        private final Cleaner.Cleanable cleanable;
        public Room(int numJunkPiles) {
            state = new State(numJunkPiles);
            cleanable = cleaner.register(this, state);
        } 
        @Override 
        public void close() {
            cleanable.clean();
        }
    }
    

    9 优先使用try-with-resources而不是try-finally

    • try-finally
    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(); 
        }
    }
    
    • try-with-resources
    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); 
        }
    }
    
  • 相关阅读:
    [学习笔记&教程] 信号, 集合, 多项式, 以及各种卷积性变换 (FFT,NTT,FWT,FMT)
    [学习笔记] CDQ分治&整体二分
    [日常] NOIp 2018 滚粗记
    [学习笔记] 模拟退火 (Simulated Annealing)
    [日常] NOIWC 2018爆零记
    [日常] PKUWC 2018爆零记
    [日常] 最近的一些破事w...
    [BZOJ 1877][SDOI2009]晨跑
    [COGS 2583]南极科考旅行
    [日常] NOIP 2017滚粗记
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/10179767.html
Copyright © 2011-2022 走看看