zoukankan      html  css  js  c++  java
  • 《Effective Java 2nd》第2章 创建和销毁对象

    目录

    第2章 创建和销毁对象

    第1条:考虑使用静态工厂方法代替构造器

    通过使用静态工厂方法而不是使用构造器来创建类。

    举例:Boolean.valueOf(boolean)方法,将boolean转换为Boolean对象引用。

    有以下优势:

    1)静态工厂方法有名称,可以表示方法的意思

    2)不必在每次调用的时候都创建新对象(对象缓存能力)。

    不可变类可预先构建实例,缓存起来重复使用。

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

    4)在创建参数化类型的时候代码更简洁。

    //我们平时创建list
    List<String> list = new Arraylist<>();
    //使用google 工具类
    List<String> list = Lists.newArrayList();
    静态工厂方法常用名称:

    valueOf、of、getInstance、newInstance、getType、newType(如newArrayList)

    第2条:遇到多个构造器参数时考虑用构建器

    当有很多个构造参数,且有几个参数是可选的,考虑使用Builder

    public class NutritionFacts {
      /**
      必填参数
      */
      private final int servingSize;
      private final int servings;
      /**
      可选参数
      */
      private final int fat;
      private final int sodium;
      public static class Builder { 
          private final int servingSize;
          private final int servings;
          private int fat = 0;
          private int sodium = 0;
          public Builder(int servingSize, int servings) {
             this.servingSize = servingSize;
             this.servings = servings;
          }
          public Builder fat(int fat) {
             this.fat = fat;
             return this;
          }
          public Builder sodium(int sodium) {
             this.sodium = sodium;
             return this;
          }
          public P build() {
             return new P(this);
          }
      }
      private NutritionFacts(Builder builder) {
          this.servingSize = builder.servingSize;
          this.servings = builder.servings;
          this.fat = builder.fat;
          this.modium = builder.modium;
      }
    }

    客户端代码

    NutritionFacts p = new NutritionFacts.Builder(200, 8).fat(100).sodium(35).build();

    更高级使用:

    public interface Builder<T> {
      public T build();
    }
    public static class NutritionFacts.Builder implements Builder<P>

    这样可以将Builder<NutritionFacts>传给方法,并结合抽象工厂创建NutritionFacts实例。

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

    举例1:使用私有构造器

    public class Singleton {
      private static final Singleton INSTANCE = new Singleton();
      private Singleton() {} //私有构造器
      //other code
    }

    为了保证Singleton类是可序列化的

    1)声明加上implents Serializable

    2) 所有实例域都是transient

    3)提供一个readResolve方法

    private Object readResolve() {
       return INSTANCE;
    }

    举例2:使用枚举

    public enum Singleton {
       INSTANCE;
    }

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

    主要用于在写工具类的时候。

    public class XXXUtils {
    ​
        private XXXXUtils() {
        }
        //other code
    }

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

    当你应该重用现有对象的时候,不要创建新的对象。

    举例1:

    m方法会被频繁调用时,会创建n多的String实例。

    public String m() {
       String s = new String("str");
       //other code
    }

    改进后,m方法被频繁调用,但是s会被复用。

    public String m() {
       String s = "str";
    }

    举例2:

    求所有Integer的和,因声明为Long sum,而不是long sum,程序将创建约2的31次方个Long实例。

    public static void main(String[] args) {
       Long sum = 0L;
      for (long i = 0; i < Integer.MAX_VALUE; i++)
      {
        sum += i; //这里每次都会创建一个Long实例。要当心无意识的自动装箱。
      }
      System.out.println(sum);
    }

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

    主要讲了内存泄露问题。

    举例1:类自己管理内存,易导致内存泄露。

    下面是简单的栈实现,程序每次测试都会通过,但是有个隐藏问题——”内存泄露“。

    栈先增长,再收缩,栈中弹出的对象不会被当做垃圾回收,即使使用栈的程序不再引用这些对象。

    public class Stack {
      private Object[] elements;
      private int size;
      private static final int DEFAULT_INIT_CAPACITY = 16;
      public Stack() {
        elements = new Object[DEFAULT_INIT_CAPACITY];
      }
      public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
      }
      public Object pop() {
        if (size == 0) {
          throw new EmptyStackException();
        }
        Object result = elements[--size];
        //解决内存泄露的方法
        //elements[size] = null; 清空过期引用
        return result;
      }
    }

    为何会出现内部泄露?

    Stack通过数组保存数组元素,相当于自己管理内存。只要是类自己管理内存,就应该警惕内存泄露问题。

    举例2:内存泄露的另一个常见来源是缓存

    缓存内存泄露:把对象引用放到缓存中,容易被遗忘,不再有用之后仍留在缓存中。

    情形1:只要在缓存外有对某个项的键的引用,该项就有意义。

    解决方法:使用WeakHashMap。

    (记住只有当缓存项生命周期由该键的外部引用而不是值决定时,WeakHashMap才有意义)

    情形2:缓存项生命周期是否有意义,不是很容易确定

    解决方法:后台线程定期清理 or 缓存添加新条目时顺便清理(LinkedHashMap.removeEldestEntry()方法)

    情形3:更复杂的缓存

    解决方法:使用java.lang.ref

    举例3:内存泄露的第三个常见来源是监听器和其他回调

    比如注册回调,但是没有显式地取消回调。解决方法:保存它们的弱引用(weak ref),如只将它们保存为WeakHashMap的键。

    第7条:避免使用终结方法

    终结方法就是finalize()方法。

    Java语言规范不保证终结方法会被及时地执行,更不保证一定会被执行。

    System.gc()增加了终结方法被执行的机会,但不保证一定会被执行。

  • 相关阅读:
    HDU 1010 Tempter of the Bone
    HDU 4421 Bit Magic(奇葩式解法)
    HDU 2614 Beat 深搜DFS
    HDU 1495 非常可乐 BFS 搜索
    Road to Cinema
    Sea Battle
    Interview with Oleg
    Spotlights
    Substring
    Dominating Patterns
  • 原文地址:https://www.cnblogs.com/yeyang/p/10460333.html
Copyright © 2011-2022 走看看