【01】Effective Java - 对象创建销毁

1、术语

(1)Java语言支持四种类型:接口、类、数组、基本类型(primitive),前三种为引用类型,而基本类型的值不是对象。

(2)方法的签名包括它的名称和所有参数的类型,签名不包括它的返回类型。


2、静态工厂替代构造器

(1)静态工厂有名称

(2)不必再每次调用都创建一个新的对象

public static Boolean valueOf(boolean b){
   return b? Boolean.TRUE : Boolean.FALSE;
}

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

    适用于返回类型为接口/抽象类

public interface Service {
    // Service-specific methods go here
}

public interface Provider {
    Service newService();
}

public class Services {
    private Services() { }  // Prevents instantiation (Item 4)

    // Maps service names to services
    private static final Map<String, Provider> providers =
        new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    // Provider registration API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
    public static void registerProvider(String name, Provider p){
        providers.put(name, p);
    }

    // Service access API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null)
            throw new IllegalArgumentException(
                "No provider registered with name: " + name);
        return p.newService();
    }
}

    客户端

public class Test {
    public static void main(String[] args) {
        // Providers would execute these lines
        Services.registerDefaultProvider(DEFAULT_PROVIDER);
        Services.registerProvider("comp",  COMP_PROVIDER);
        Services.registerProvider("armed", ARMED_PROVIDER);

        // Clients would execute these lines
        Service s1 = Services.newInstance();
        Service s2 = Services.newInstance("comp");
        Service s3 = Services.newInstance("armed");
        System.out.printf("%s, %s, %s%n", s1, s2, s3);
    }

    private static Provider DEFAULT_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Default service";
                }
            };
        }
    };

    private static Provider COMP_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Complementary service";
                }
            };
        }
    };

    private static Provider ARMED_PROVIDER = new Provider() {
        public Service newService() {
            return new Service() {
                @Override public String toString() {
                    return "Armed service";
                }
            };
        }
    };
}

(4)创建参数化实例更简洁

public static <K,V> HashMap<K,V> newInstance(){
   return new HashMap<K,V)();
}

(5)静态工厂惯用名称

valueOf,of,getInstance,newInstance,getType,newType


3、用Builder模式替代多参数的构造器

    构造器的参数太多的话,调用方容易混淆,特别是针对相邻的同类型的参数,如果不小心将顺序颠倒,则编译时难以发现,运行时出问题。

(1) 重叠构造器版本,好处是安全性,坏处是难以阅读

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);
    }
}

(2)JavaBean版本,好处是可读性,坏处是自己要保证线程安全

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);
    }
}

(3)Builder版本,兼容二者好处

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();
    }
}

  newInstance方法可充当build方法的一部分,但是它总是企图去调用类的无参构造器,如果类没有无参构造器,编译时是不会暴露出来的,只能到客户端调用运行时才暴露。

(4)实践版

   一般比如sql构造之类的,参数太多,可以用builder模式,普通bean的赋值,采用builder反而搞得太过负责,工业上的最佳实践,一般是采用JavaBean + Design by Contract 的模式,要求开发者根据约定传参数,service层再进行一层校验,来确保必填参数是不为null的。


4、私有构造器或枚举强化Singleton属性

    (1)私有构造器能够防止通过反射调用去构造实例

    (2)为确保反序列化之后还是单例,需要重写readResolve方法,return INSTANCE,或者直接使用枚举单例,它内置了反序列化单例的功能。


5、避免创建不必要的对象

   (1)达到尽量重用对象的目的,比如字符串字面常量,不可变的对象。

反例

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;
    }
}

正解:使用静态初始化器,初始化起止日期

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;
    }
}

(2)优先使用基本类型,而不是封装类型,当心无意识的自动装箱额外产生的对象,下面的sum声明为long,即可减少2的31次方个多余的Long实例。

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);
    }
}

(3)对象创建的误解

   误解:对象创建的代价非常昂贵,应该避免创建对象。

   正解:小对象的创建,构造器只做很少量的显式工作,其创建和回收都是非常廉价的,因而对小对象没必要搞什么对象池之类的;只有重量级的对象,比如数据库连接池等,连接数据库代价是昂贵的,采用对象池正好。


6、消除过期引用
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);
    }
}

  出栈的时候没有清空引用

public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];  
        elements[size] = null;   
        return result;
    }

(1)清空对象引用应该是一种例外,而不是一种行为规范,不必每次使用变量的时候,都紧张兮兮的,考虑要不要在使用后赋值为null

(2)内存泄露的常见来源

A、自己管理内存的容器

B、缓存

C、监听器和回调方法(注册监听,不需要的时候没有取消监听)


7、避免使用finalize方法

(1)JVM不保证finalize方法会被及时执行,而且根本不保证它们会被执行

(2)不要依赖finalize方法去关闭重要资源,比如关闭文件、关闭数据库连接,一般采用finally语句即可

(3)使用finalize可能会造成额外的性能损失


转载于:https://my.oschina.net/scipio/blog/286529

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值