创建和销毁对象 笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011216273/article/details/75006830

1.静态工厂方法和构造器优缺点
使用静态工厂方法的优点:
1.静态工厂方法有方法名称,构造器没有只有参数类型,有方法名称的话可以详细表明对象是做什么的。
2.静态工厂方法不必在每次调用他们都创建新对象,可以用单例模式,避免重复创建对象造成浪费。
3.静态工厂方法可以返回原返回类型的任何子类型对象。基于接口。(了解但不知道怎么使用)
4.静态工厂方法在创建参数化类型实例的时候,会使代码更加简洁。(没用过)
使用静态工厂方法的缺点:
1.当类如果不含有共有公有的和受保护的构造器,就不能被实例化。
2.静态工厂方法和其他静态方法一样。

使用中,切记直接考虑使用构造器,先考虑是否使用静态构造方法。

2.使用构建器(当构造器参数过多和可变参数的时候,使用构建器builder)
为什么不使用构造器:
1.当参数过多的时候,使用构造器实例化的需要多个构造器,比如,2个参数构造的实例、5个参数构造的实例。使用者很难编写,难以阅读,当相邻参数类型一致的话稍微不注意就错误
为什么不是使用JavaBeans模式(使用一个无参的构造器,然后使用setXXX方法):
1.很不安全,使用过程中可能JavaBean可能处于不一致的状态。
使用构建器的缺点:
1.为了创建对象必须先创建它的构建器,性能问题。代码厄长。

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

private static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public static Elvis getInstance() {
        return INSTANCE;
    }

使用final来修饰对象,不论是谁调用静态工厂方法都会返回唯一的对象实例。

4.通过私有构造器强化不可实例化的能力(Math等)
1。有些工具类不希望自己被实例化,实例化对它没有任何意义,也不希望自己是基类。如果当前类不包含显式构造器(在类中声明的构造器)的话,那么编译器会默认的给它生成共有无参的构造器。可以通过显式的构建私有构造器,来强化类不可实例化。那么对于被继承的话,可以采用final来修饰当前类。不可被继承。为了防止反射调用私有构造器在类中被调用可以采用如下代码:

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

5.避免创造不必要的对象
举个例子代码如下:

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

此代码块,中每次调用isBabyBoomer方法都会创建一个Calendar、一个TimeZone、两个Date的实例,这是不必要的。
下面是修改后的代码:

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

改进后的代码只有在类初始化的时候创建Calendar、TimeZone、Date实例一次。并且BOOM_START和BOOM_END都是从局域变量改为final作用域,这两个对象显然被当成常量使用(赋值一次就不会改变)。
当然有个小问题是如果不掉用isBabyBoomer方法的话,也会造成创建不必要的对象。

小心自动装箱:

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

        System.out.println(sum+"time:"+(endTime-startTime)/1000);
    }
}

当sum类型是Long的时候。测试时间是10S,原因是大约构造了2^31次方的Long实例,自动装箱了。
当采用long基本类型的时候:

扫描二维码关注公众号,回复: 3026257 查看本文章
public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        long sum = 0L;
        Long startTime = System.currentTimeMillis();
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        long endTime = System.currentTimeMillis();

        System.out.println(sum+"time:"+(endTime-startTime)/1000);
    }
}

运行时间不到1S中。所以创建对象中要注意自动装箱问题。

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

此段程序会发生内存泄漏,当栈先增长,再收缩,那么从栈中弹出的对象将不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为,栈内部维护这这些对象的过期引用。所谓的过期引用,是指永远不会被解除的引用。凡是在elements数组的活跃部分(active portion)之外的引用都是过期的。活动部分是指elements中下标中小于size的元素(发生在栈先增长,再弹出元素的时候)。
在支持垃圾回收的语言中,内存泄漏是很隐蔽的(称这类内存泄露为”无意识的对象保留“)如果一个对象引用被无意识的保留起来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的其他所有对象。即使只有少量的几个对象引用被无意识的保留下来,也会有许多对象被排除在垃圾回收机制外,从而对性能造成重大影响。

这类问题的修复方法很简单:一旦对象引用已经过期,只需清空这些对象引用即可。对上例子的栈来说的话, 只要一个单元被弹出栈,指向它的引用就过期了,修改代码如下:

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

消除过期引用的最好的方法是让包含该引用的变量结束其生命周期。
缓存、监听器、回调都容易发生内存泄漏。

7.避免使用终结方法(finalizer)
1.不能保证被及时执行。
2.可能会延迟的其实例回收过程。

猜你喜欢

转载自blog.csdn.net/u011216273/article/details/75006830