• 《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()增加了终结方法被执行的机会,但不保证一定会被执行。

  • 相关阅读:
    grpc学习
    01
    样本1
    杀死长时间占用CPU的进程
    SWFTools pdf2swf 参数详解
    C#自动下载并保存文件示例
    Flex初始化时加载外部XML
    通过XPDF抽取PDF中的中文文本
    Flex操作Json数据示例
    C#下载文件和将文件转换为数据流下载的示例
  • 原文地址:https://www.cnblogs.com/yeyang/p/10460333.html
Copyright © 2020-2023  润新知