文档章节

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

小楼听雨中
 小楼听雨中
发布于 2014/07/03 08:04
字数 1860
阅读 103
收藏 7

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可能会造成额外的性能损失


© 著作权归作者所有

小楼听雨中
粉丝 61
博文 201
码字总数 129524
作品 0
海淀
程序员
私信 提问
第三章 spring-bean之DefaultSingletonBeanRegistry(3)

前言 SingletonBeanRegistry是一个非常重要的接口,用于注册,获得,管理singleton对象。 SingletonBeanRegistry目前唯一的实现是DefaultSingletonBeanRegistry,DefaultSingletonBeanRegis...

鸟菜啊
2018/07/18
228
0
那些年,关于 Java 的那些事儿

版权声明:Follow your heart and intuition. https://blog.csdn.net/qq_35246620/article/details/78695893 温馨提示:本系列博文(含示例代码)已经同步到 GitHub,地址为「java-skills」,...

维C果糖
2017/12/02
0
0
Java虚拟机运行时数据区结构

本文部分参考自《Java虚拟机规范(Java SE 7版)》的中译本和周志明的《深入理解Java虚拟机》,另加个人理解。原书对Java虚拟机运行时数据区描述只有6页,同时参考其他网络网资料,个人能力所...

foodon
2014/12/09
367
4
JVM 运行时数据区简介及堆与栈的区别

理解JVM运行时的数据区是Java编程中的进阶部分。我们在开发中都遇到过一个很头疼的问题就是OutOfMemoryError(内存溢出错误),但是如果我们了解JVM的内部实现和其运行时的数据区的工作机制,...

大数据之路
2015/08/02
4K
1
JDBC代码阅读中出现的问题

上述的static是修饰的叫"代码块"是吧?为啥要用static修饰呢?是为了不重复创建只用static中已经存在的吗? 上边这段代码是封装的关闭资源,一直不太理解关闭资源的意思,是把资源对象销毁还是怎么...

HaleyZhang
2018/06/10
170
3

没有更多内容

加载失败,请刷新页面

加载更多

VMware vSphere ESXi主机的访问控制

在vShpere中,访问ESXi主机的途径很多,如下: ESXi DCUI ESXi Shell ESXi SSH ESXi Host Client vCenter --> vSphere web client / vSphere Client VMware vSphere ESXi主机的访问控制,除了......

大别阿郎
27分钟前
3
0
大神讲解CGI、FastCGI和PHP-FPM关系图解

参考资料 概念了解:CGI,FastCGI,PHP-CGI与PHP-FPM:http://www.nowamagic.net/librarys/veda/detail/1319 php中fastcgi和php-fpm是什么东西:https://www.zybuluo.com/phper/note/50231 ......

网络小虾米
37分钟前
3
0
《DNS攻击防范科普系列3》 -如何保障 DNS 操作安全

引言 前两讲我们介绍了 DNS 相关的攻击类型,以及针对 DDoS 攻击的防范措施。这些都是更底层的知识,有同学就来问能否讲讲和我们的日常操作相关的知识点,今天我们就来说说和我们日常 DNS 操...

Mr_zebra
37分钟前
3
0
zk中ServerCnxn

实现接口Stats, Watcher 内部类 DisconnectReason CloseRequestException EndOfStreamException(流关闭) 属性 方法 getSessionTimeout 获取session失效时间 sendResponse 发送回复数据 se......

writeademo
42分钟前
3
0
如何将 Redis 用于微服务通信的事件存储

来源:Redislabs 作者:Martin Forstner 翻译:Kevin (公众号:中间件小哥) 以我的经验,将某些应用拆分成更小的、松耦合的、可协同工作的独立逻辑业务服务会更易于构建和维护。这些服务(也...

中间件小哥
45分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部