美文网首页
Effective Java:创建和销毁对象

Effective Java:创建和销毁对象

作者: Gino_Gu | 来源:发表于2017-02-14 20:37 被阅读0次

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

    (Consider static factory methods instead of constructors)

    静态工厂方法的优势:

    1. 静态工厂方法与构造器不同的第一大优势在于,它们有名称。如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用。
    2. 静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。 笔者注:缓存,重复利用
    3. 静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。 笔者注: 更大的灵活性,构成service provider framework的基础
    4. 静态工厂方法的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。 笔者注:得益于类型推导

    静态工厂方法的缺点:

    1. 静态工厂类如果不含public或者protected构造器,就不能被子类化。
      它们与其他的静态方法实际上没有任何区别。 笔者注: api文档没像constructor那样明确标注
    2. 静态工厂方法的惯用方法名:
      valueOf
      of
      getInstance
      newInstance
      getType
      newType

    第2条:遇到多个构造器参数时要考虑用builder模式

    (Consider a builder when faced with many constructor parameters)

    一个类的大部分字段为可选字段的场合,构建类有三种模式:
    分别是重叠构造器,Javabean模式和Builder模式。
    重叠构造器的示例代码:

    // Telescoping constructor pattern - does not scale well! - Pages 11-12
    
    public class NutritionFacts {
        private final int servingSize;   // (mL)            required
        private final int servings;      // (per container) required
        private final int calories;      //                 optional
        private final int fat;           // (g)             optional
        private final int sodium;        // (mg)            optional
        private final int carbohydrate;  // (g)             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;
        }
    
        public static void main(String[] args) {
            NutritionFacts cocaCola =
                new NutritionFacts(240, 8, 100, 0, 35, 27);
        }
    }
    

    一长串类型相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是程序在运行时会出现错误的行为。

    Javabean模式:调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。
    缺点:构造过程中无法保证一致性;阻止了把类做成不可变的可能。
    Javabean模式示例代码:

    // JavaBeans Pattern - allows inconsistency, mandates mutability - Pages 12-13
    
    public class NutritionFacts {
        // Parameters initialized to default values (if any)
        private int servingSize  = -1;  // Required; no default value
        private int servings     = -1;  //     "     "     "      "
        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; }
    
    
        public static void main(String[] args) {
            NutritionFacts cocaCola = new NutritionFacts();
            cocaCola.setServingSize(240);
            cocaCola.setServings(8);
            cocaCola.setCalories(100);
            cocaCola.setSodium(35);
            cocaCola.setCarbohydrate(27);
        }
    }
    

    如果类的构造器或者静态工厂中具有多个参数,设计这种类时, Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans更加安全。
    Builder模式示例代码:

    // Builder Pattern - Pages 14-15
    
    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 carbohydrate  = 0;
            private int sodium        = 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 carbohydrate(int val)
                { carbohydrate = val;  return this; }
            public Builder sodium(int val)
                { sodium = 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;
        }
    
        public static void main(String[] args) {
            NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
                calories(100).sodium(35).carbohydrate(27).build();
        }
    }
    

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

    (Enforce the singleton property with a private constructor or an enum type)

    在Java 1.5发行版本之前,实现Singleton有两种方法。
    第一种方法中,公有静态成员是个final字段:

    // Singleton with public final field - Page 17
    public class Elvis {
        public static final Elvis INSTANCE = new Elvis();
        private Elvis() { }
    
        public void leaveTheBuilding() {
            System.out.println("Whoa baby, I'm outta here!");
        }
    
        // This code would normally appear outside the class!
        public static void main(String[] args) {
            Elvis elvis = Elvis.INSTANCE;
            elvis.leaveTheBuilding();
        }
    }
    

    第二种方法中,公有的成员是个静态工厂方法:

    // Singleton with static factory - Page 17
    
    public class Elvis {
        private static final Elvis INSTANCE = new Elvis();
        private Elvis() { }
        public static Elvis getInstance() { return INSTANCE; }
    
        public void leaveTheBuilding() {
            System.out.println("Whoa baby, I'm outta here!");
        }
    
        // This code would normally appear outside the class!
        public static void main(String[] args) {
            Elvis elvis = Elvis.getInstance();
            elvis.leaveTheBuilding();
        }
    }
    

    从Java 1.5发行版本起,实现Singleton还有第三种方法。只需编写一个包含单个元素的枚举类型,该方法已经成为实现Singleton的最佳方法。

    // Enum singleton - the preferred approach - page 18
    public enum Elvis {
        INSTANCE;
    
        public void leaveTheBuilding() {
            System.out.println("Whoa baby, I'm outta here!");
        }
    
        // This code would normally appear outside the class!
        public static void main(String[] args) {
            Elvis elvis = Elvis.INSTANCE;
            elvis.leaveTheBuilding();
        }
    }
    

    注意:客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。

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

    (Enforce noninstantiability with a private constructor)

    企图通过将类做成抽象类来强制该类不可被实例化,这是行不通的!
    我们只要让这个类包含私有构造器它就不能被实例化了:

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

    副作用,它使得一个类不能被子类化。

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

    (Avoid creating unnecessary objects)

    Person 类的isBabyBoomer方法每次被调用时都会产生一个Calendar对象,一个Timezone对象和两个Date对象,性能开销很大。

    // Creates lots of unnecessary duplicate objects - page 20-21
    
    import java.util.*;
    
    public class Person {
        private final Date birthDate;
    
        public Person(Date birthDate) {
            // Defensive copy - see Item 39
            this.birthDate = new Date(birthDate.getTime());
        }
    
        // Other fields, methods omitted
    
        // DON'T DO THIS!
        public boolean isBabyBoomer() {
            // Unnecessary allocation of expensive object
            Calendar gmtCal =
                Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
            Date boomStart = gmtCal.getTime();
            gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
            Date boomEnd = gmtCal.getTime();
            return birthDate.compareTo(boomStart) >= 0 &&
                   birthDate.compareTo(boomEnd)   <  0;
        }
    }
    
    

    正确的做法应该是:

    // Doesn't creates unnecessary duplicate objects - page 21
    
    import java.util.*;
    
    class Person {
        private final Date birthDate;
    
        public Person(Date birthDate) {
            // Defensive copy - see Item 39
            this.birthDate = new Date(birthDate.getTime());
        }
    
        // Other fields, methods
    
        /**
         * The starting and ending dates of the baby boom.
         */
        private static final Date BOOM_START;
        private static final Date BOOM_END;
    
        static {
            Calendar gmtCal =
                Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_START = gmtCal.getTime();
            gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_END = gmtCal.getTime();
        }
    
        public boolean isBabyBoomer() {
            return birthDate.compareTo(BOOM_START) >= 0 &&
                   birthDate.compareTo(BOOM_END)   <  0;
        }
    }
    
    

    要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
    以下代码中的Long sum改为long sum将显著提升性能。

    public class Sum {
        // Hideously slow program! Can you spot the object creation?
        public static void main(String[] args) {
            Long sum = 0L;
            for (long i = 0; i < Integer.MAX_VALUE; i++) {
                sum += i;
            }
            System.out.println(sum);
        }
    }
    
    

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

    (Item 6: Eliminate obsolete object references)

    一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。
    内存泄漏的另一个常见来源是缓存。
    内存泄漏的第三个常见来源是监听器和其他回调。
    以下示例代码的pop()方法中,应考虑增加elements[size] = null;

    // Can you spot the "memory leak"?
    
    import java.util.*;
    
    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];
        }
    
        /**
         * Ensure space for at least one more element, roughly
         * doubling the capacity each time the array needs to grow.
         */
        private void ensureCapacity() {
            if (elements.length == size)
                elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
    
    

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

    (Item 7: Avoid finalizers)

    终结方法(finalizer) 通常是不可预测的,也是很危险的.一般情况下是不必要的。
    使用终结方法有一个非常严重的(Severe)性能损失。
    如果类的对象中封装的资源(例如文件或者线程)确实需要终止, 只需提供一个显式的终止方法,显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止。

    相关文章

      网友评论

          本文标题:Effective Java:创建和销毁对象

          本文链接:https://www.haomeiwen.com/subject/xvlfwttx.html