Effective java笔记(一),创建与销毁对象

1、考虑用静态工厂方法代替构造器

类的一个实例,通常使用类的公有的构造方法获取。也可以为类提供一个公有的静态工厂方法(不是设计模式中的工厂模式)来返回类的一个实例。例如:

    
    //将boolean类型转换为Boolean类型
    public static valueOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE;
    }

使用静态工厂方法代替构造器的优势:

  • 静态工厂方法有名称,更易读。静态工厂方法能够使用方法名称进行自注释,来描述被返回的对象。例如:BigInteger.probablePrime的静态方法表示返回的BigInteger为素数。当一个类需要多个带有相同签名(参数列表仅在参数类型的顺序上有所不同)的构造器时,应使用静态工厂方法并且慎重的选择名称以便突出它们的区别。

  • 静态工厂方法能够为重复的调用返回相同的对象。将创建好的对象缓存起来,重复利用。

  • 静态工厂方法可以返回原返回类型的任何子类型的对象。例如:在基于接口的框架(通过接口来引用对象)中,为了隐藏类的实现,通常会使用API返回对象的实例。由于接口中不能有静态方法,通常把静态工厂方法放在实现了接口的不可实例化类(private构造函数)中。

  • 创建参数化类型实例时,静态工厂方法使代码变的更简洁。(java 8加入泛型的推导后,优势不再)

    // java 1.7之前必须这样写,前后两次都要写泛型列表
    Map<String, List<String>> map = new HashMap<String, List<String>>(); 
    Map<String, List<String>> map = new HashMap<>(); // java 1.8可以这样写

    //静态工厂方法
    //HashMap类中加入
    public static <K, V> HashMap<K, V> newInstance() {
        return new HashMap<K, V>();
    }
    //使用
    Map<String, List<String>> map = HashMap.newInstance();

缺点:

  • 类如果不含有public或protected型的构造器,则不能被继承。

2、遇到多个构造器参数时考虑用构建器

当有多个构造器参数(其中一些为可选参数)时,代码的编写通常有几种方式:

  • 使用重叠的构造器,根据可选参数提供不同的构造器。

  • 使用JavaBean模式:先调用一个无参的构造器来创建对象,然后使用setter方法来设置必要的参数。

当参数很多时,使用重叠的构造器代码的编写将很繁琐,难以阅读,同时调用时容易出错。而是用JavaBean模式,因为对象的构造过程被分到了几个调用中,在构造过程中JavaBean处于不一致状态。试图使用(特别是在多线程中)不一致状态的对象,将导致错误并且很难调试。并且JaveBean模式阻止了将类做成不可变的可能。

这时可以考虑使用构建器(Builder模式),不直接生成想要的对象,而是通过Builder对象来构造对象。

代码:


    public class AlertDialog {

        private final String title;

        private  final String message;

        private final boolean cancelable;

        private AlertDialog(Builder builder) {
            this.title = builder.title;
            this.message = builder.message;
            this.cancelable = builder.cancelable;
        }

        public static class Builder {

            private final String title;

            private String message;

            private boolean cancelable;

            public Builder(String title) {
                this.title = title;
            }

            public Builder setMessage(String message) {
                this.message = message;
                return this;
            }

            public Builder setCancelable(boolean cancelable) {
                this.cancelable = cancelable;
                return this;
            }

            public AlertDialog create() {
                return new AlertDialog(this);
            }
        }

        public String toString() {
            return "title: " + title + ", message: " + message + ", cancelable: " + cancelable;
        }

        public static void main(String[] args) {
            AlertDialog dialog = new AlertDialog.Builder("地点")
                .setMessage("知识的荒漠")
                .setCancelable(true).create();
            System.out.println(dialog);
        }
    }

Builder模式的优点:

  • 比JavaBean更安全,比重叠的构造器更易于阅读和编写

  • 适用于参数较多,且大多数参数可选的情况

3、用私有构造器或枚举类型强化Singleton属性

实现Singleton的几种方式:

方式一:饿汉方式,线程安全

    
    public class Singleton {
        private static final Singleton singleton = new Singleton();

        private Singleton() {}

        public static Singleton getInstance() {
            return singleton;
        }
    }

这种方式基于classloader机制避免了线程同步的问题。但singleton在类被装载时立即实例化,没有实现类的延时加载(lazy loading)。另外这种方式可以通过反射机制(借助Class对象的setAccessible方法)来生成对象。

方式二:懒汉方式,线程不安全

    
    public class Singleton {
        private static final Singleton singleton = null;

        private Singleton() {}

        public static Singleton getInstance() {
            if(singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }

这种方式实现了类的延时加载,但致命的是多线程下不安全。

方式三:双重校验锁,线程安全


    public class Singleton {
        private volatile static Singleton singleton = null;
        private Singleton() {}
        public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

方式四:枚举,线程安全,可避免反序列化时重新创建对象,推荐使用


    public enum Singleton {
        INSTANCE;

        private Singleton() {}
        .....
    }

    //使用
    Singleton single = Singleton.INSTANCE;

这种方式可自由序列化,可有效避免反序列化时创建多个对象。保证只有一个实例(即使反射机制也无法多次实例化一个枚举量),线程安全。缺点是使用的人较少(java1.5后才有enum类型),失去了类的一些特性。单元素的枚举型是实现Singleton的最佳方式

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

编写一些只包含静态方法或静态域的类(工具类)时,可以使用private构造器来使类不能被实例化。这些类不能被继承。(因为子类的构造器都要显示或隐式调用父类的构造器)。自己编写的工具类要使用这种方法

    
    public class UtilityClass {
        private UtilityClass() {
            throw new AssertionError(); //断言,确保不会在类内部被调用
        }
        .....
    }

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

重用对象而不是每次需要时创建一个相同的新对象,这可以显著程序提高性能。对于不可变的对象应始终重用,对于可变的对象应尽量重用。

例如:

    
    String str1 = "abc";
    String str2 = new String("abc");
    String str3 = new String("abc");

内存模型:


String存储模型

new String("abc")每次执行都会创建一个新的String实例,在堆中分配一个新的空间。而使用String str1 = "abc"每次调用都将指向常量池中同一个常量,不会产生新的实例。

对于可变对象,那些一旦计算出来就不再变化的子对象或常量可单独提取到静态域中。


    class Person {
        private final Date birthDate;
        private static final Date BOOM_STATE; //第一次初始化后将不改变,所有对象共用
        private static final Date BOOM_END;

        static {
            Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_STATE = gmtCal.getTime();
            gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
            BOOM_END = gmtCal.getTime();
        }

        public boolean isBabyBoomer() {
            return birthDate.compareTo(BOOM_STATE) >= 0 && birthDate.compareTo(BOOM_END) < 0;
        }
    }

当心无意识的自动装箱,要优先使用基本类型而不是包装类。

例如:计算所有int正值的和


    Long sum = 0L; //应使用基本类型long
    for(long i=0; i<Integer.MAX_VALUE; i++ ) {
        sum += i; //自动装箱,将创造2^32个多余的Long实例,降低性能
    }
    System.out.println(sum);

6、消除过期的对象引用

过期引用是指永远也不会再被解除的引用。在java中内存泄漏是隐藏的(无意识的对象保持)。如果一个对象的引用被无意识的保留下来,那么垃圾回收机制不会回收这个对象及这个对象所持有的所有对象。

消除过期引用最好的方法是让包含该引用的变量结束其生命周期。只要类自己管理内存,就应该小心内存泄漏问题。

    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 o) {
            ensureCapacity();
            elements[size++] = o;
        }

        public Object pop() {
            if(size == 0) 
                throw new EmptyStackException();
            Object result = elements[--size];
            elements[size] = null; //【避免内存泄漏】
            return result;
        }

        private void ensureCapacity() {
            if(elements.length == size) {
                elements = Arrays.copyOf(elements, 2*size + 1);
            }
        }
    }

对于垃圾回收器而言,elements数组中的所有对象的引用都同等有效,它无法区分哪些可以回收哪些不能回收。

内存泄漏的常见来源:

  • 数组

  • 缓存。使用WeakHashMap代表缓存,对于复杂的缓存必须使用java.lang.ref

  • 监听器及其回调。要及时取消注册,可以使用弱引用

7、避免使用终结方法

终结方法finalizer()为类Object中的方法,当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。

  • 终结方法是不可预测的,通常不要直接调用。

  • Java语言规范不保证终结方法会被立即执行,所以不应该依赖终结方法来更新重要的持久状态。

  • 终结方法可用作安全网或终止非关键的本地资源(记住调用super.finalize())

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容