Effective Java2读书笔记-创建和销毁对象(三)

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

本条主要讲的是一些反面教材,希望大家引以为鉴。

①无意中使用自动装箱导致多创建对象。

public class Sum {
    public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}

sum被声明为Long而不是long,意味着每次i都要被自动装箱(创建Long实例),因此速度会慢很多。

②可以重用不会被修改的可变对象。

也就是说,有些看起来是变量,但是实际上它的值一旦计算出来之后就不再变化,这种也应该视作常量,而不是每次都计算。

下面先看一段代码。

public class Person {
    //生日
    private final Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = new Date(birthDate.getTime());
    }

    public boolean isBabyBoomer() {
        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;
    }
}

这段代码中的isBabyBoomer方法,根据生日判断是否属于婴儿潮时间。看起来并没有什么问题。

但实际上,每次调用这个方法时,都会新建一个Calendar,一个TimeZone和两个Date实例,这是没有必要的(所以,在平时写方法时也要注意,有很多对象是可以提取出来的)。下面看如何改进。

public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = new Date(birthDate.getTime());
    }
    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;
    }
}

改进之后Person类仅在初始化时创建了实例,后面调用isBabyBoomer方法时,就没有生成对象的消耗了。

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

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

问题就在于,当栈先增长,然后再收缩。从栈中弹出来的对象并不会被当做垃圾回收,这是因为栈内还维护着这些对象的引用。弹出之后,使用栈的程序不再需要它,只能人为清理掉。一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。

文中还还提到在使用回调时,确保回调立即被当作垃圾回收的最佳方法时只保存它们的弱引用,例如,只将它们保存成WeakHashMap中的键(这个平时很少用到,只作为了解)。

弱引用与强引用的区别在于,即使内存不足,强引用也不会被回收掉。而弱引用不管内存足不足,一旦被垃圾回收线程发现,就会被回收掉。

时间: 2024-09-29 23:47:48

Effective Java2读书笔记-创建和销毁对象(三)的相关文章

Effective Java2读书笔记-创建和销毁对象(一)

第1条:考虑用静态工厂方法代替构造器 通常情况下,我们创建一个对象采取new的形式,但是还有一种方法也是经常使用到的,它的名称叫做静态工厂方法. 例如,java中基本类型boolean的包装类Boolean就采用了这种方式,源代码如下: public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } 当然,除了valueOf这种比较low的名字之外,我们常用的还有getInstance(最常见),newInstanc

Effective Java2读书笔记-创建和销毁对象(二)

第3条:用私有构造器或者枚举类型强化Singleton属性 这一条,总体来说,就是讲了一个小技巧,将构造器声明为private,可以实现单例.具体有以下几种实现的方式. ①最传统的单例实现模式,可能有很多变种,核心思想是私有化构造器. public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton(){}; public static Singleton g

Effective Java2读书笔记-创建和销毁对象(四)

第7条:避免使用终结方法 这一条讲的简直是不知所云.先简单记下来其中说出的几条: ①显式终止方法的典型例子有InputStream.OutputStream和java.sql.Connection上的close方法,以及java.util.Timer上的cancel方法.这些方法一般与try-catch连用,在finally中调用显式的终止方法.终结方法的意义在于这些close方法忘记调用时,充当安全网的作用(感觉扯淡,close都会忘,终结方法能记得?). ②finalize方法,源自Obje

Effective Java阅读笔记——创建和销毁对象(一)

类通常提供一个公有的构造器方法,以此来让客户端可以获取自己(类)的一个实例.但是在创建对象时,应该首先考虑利用静态工厂方法代替构造器来返回一个实例.利用静态工厂方法而不是公有的构造器有几个优势: 静态工厂方法有(不同的)名字 构造器方法都有相同的名字,就是类的名字.区分不同构造器的方法是通过观察方法的签名确定的.方法的签名包括方法名,方法参数的类型,数目以及顺序,方法的返回类型不是方法签名的一部分.无法通过构造器方法名区分不同的构造器,只能通过参数的不同区分,这种情况下(特殊情况下有可能参数类型

Effective Java读书笔记(3对于所有对象都通用的方法)

3.1 覆盖equals时请遵守通用约定 什么时候应该覆盖Object.equals()方法呢? 如果类具有自己特有的"逻辑相等"概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. Object.equals()方法具有自反性.对称性.传递性.一致性和与null比较返回false的特点. 实现高质量equals方法的诀窍: (1)使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true,这

[Effective Java]第二章 创建和销毁对象

第一章      前言 略... 第二章      创建和销毁对象 1.            考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Boolean的简单示例: public static Boolean valueOf(boolean b) { return (b ? Boolean.TRUE : Boolean.FALSE); } l  静态工厂方法与构造器不同的第一大优势在于,它们有名称. 作用不同的公有构造器只能通过参数来区别(因

Effective java 第二章创建和销毁对象 读书笔记

建和销毁对象 一般有两种方式,一种是获得本身的实例,最常用的方法就是提供一个共有的构造器.第二个方法是,类可以提供一个共有的静态工厂方法. 静态工厂方法与构造器不同的三大优势: 有名字: 调用时不用创建新的对象: 返回原来类型的任何子类型对象. 第四大优势: 在创建参数化类型实例的时候,它们使代码变得更加简洁. 服务提供者框架 三个组件: 服务接口 提供者主持API 服务访问API 可选: 服务提供者接口 静态工厂方法的缺点: 类如果不含共有的或者受保护的构造器,就不能被子类化: 它们与其他的静

Effective java经验之谈,创建和销毁对象

关于Effective java 这本书,自己的一些总结性的思考.篇幅可能不按照目录来,因为自己喜欢先看哪一章就直接阅读了.不过能确定的是,每一章都会有总结.欢迎大家拍砖与补充. 1.      考虑用静态工厂的方法代替构造器.优点:有名字,不必每次创建对象,返回任何子类型对象,简洁的代码.缺点:该类将不能被子类化(复合大于继承,也是优点),不方便doc工具输出文档,一般约定的命名规则: valueOf  转换类型 getInstance 获得对象实例 newInstance 创建新的对象实例

Effective Java2读书笔记-对于所有对象都通用的方法(二)

第10条:始终要覆盖toString 这一条没什么好讲的,就是说默认的toString方法打印出来的是类名[email protected]+十六进制哈希码的值.我们应该覆盖它,使它能够展示出一些更为详细清晰的信息,这个看实际情况吧. 第11条:谨慎地覆盖clone 有时候会出现这样的场景,你需要备份一些数据,对其一进行修改时,另外一个不受影响.这样,直接Foo a = new Foo(); Foo b = a; 是不可行的,b引用和a指向的是同一个对象. 这一条讲了很多,最后的总结是,Clon