Effictive Java学习笔记(1)--创建和销毁对象

创建和销毁对象

1.使用静态工厂代替构造器

1)静态工厂的好处

1.静态工厂方法有名称,而构造器只能与类名相同。
2.静态工厂不用每次调用时都创建一个新对象。
可以在静态工厂方法中每次返回一个新的对象,也可以使用单例模式,每次返回同一个对象。
3.静态工厂可以返回原类型的任何子类型的对象。
经常适用于在接口中定义静态工厂方法,返回一个已知实现该接口的子类对象。
4.在创建参数实例化时,代码变得更简洁。

Map<String,List<String>> m = new HashMap<String,List<String>>();

如果使用下面静态工厂法

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

则可以这样调用

Map<String,List<String>> m = HashMap.newInstance();

2)静态工厂的缺点

1.类如果不含公有或者受保护的构造器,那么该类不能被继承。
2.静态工厂方法与其他的静态方法没有区别。

3)静态工厂的惯用名称

  • valueOf----------该方法返回的实例与它的参数具有相同的值,该静态方法相当于类型转换方法。
  • of----------valueOf的更为简洁的替代。
  • getInstance-------返回唯一的实例(每次都返回相同的实例)。
  • newInstance--------每次返回不同的实例。
  • getType---------与getInstance相同,只是在其他类中返回Type类的实例。
  • newType---------与newInstance相同,只是在其他类中返回Type类的实例。

2.多个构造器参数时使有构建器

在构造器的参数比较多时,使用构建器可以很好的扩展大量的可选参数。
public class Person {
    private String name;
    private String sex;
    private Integer weight;
    private Integer height;
    // 建造器
    public static class PersonBuilder{
        private String name;
        private String sex="男";
        private Integer weight=50;
        private Integer height;

        // 返回值类型不再是void 而是建造器类型本身
        public PersonBuilder name(String name) {
            this.name = name;
            return this;
        }
        public PersonBuilder sex(String sex) {
            this.sex = sex;
            return this;
        }
        public PersonBuilder weight(Integer weight) {
            this.weight = weight;
            return this;
        }
        public PersonBuilder height(Integer height) {
            this.height = height;
            return this;
        }
        public Person build() {
            return new Person(this.name,this.sex,this.weight,this.height);
        }
    }

    private Person(String name, String sex, Integer weight, Integer height) {
        this.name = name;
        this.sex = sex;
        this.weight = weight;
        this.height = height;
    }
}

调用时

Person person = new Person.PersonBuilder()
                .sex("男")
                .name("张三")
                .height(170)
                .build();

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

实现Singleton的两种方法
1.私有构造器,并将唯一实例声明为static final。
2.在静态工厂返回相同实例的基础上,再私有构造器就形成了单例模式。
单例模式可参考几种常见的单例模式
但是有两种方法可以破坏单例模式。
1.通过反射机制的setAccessible方法。
2.通过反序列化生产新的对象。
针对第一种方法我们可以在构造方法中加上判断,若被调用两次,则抛出异常。
针对第二种方法可以提供一个readResolve方法。JVM在反序列化时会调用该方法。

privaet Object readResolve(){
	return INSTANCE//返回该类中的唯一实例。
}

还有一种方式是使用枚举类。只需包含单个元素的枚举类型。这种方法无偿的提供类序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。

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

有一些工具类只包含静态方法和静态域,不希望被实例化(比如java.lang.Math、java.util.Arrays)。如果不写构造方法,则会默认一个共有的无参构造方法,不能达到我们的目的。
这时候可以写一个无参构造方法,并且声明为私有。这样就不会被外界调用。为了防止反射机制,可以在构造方法中抛出异常,一旦被调用,则出先异常。
这样做会有一个副作用:不能被其他类所继承。

5.避免创建不必要对象

1)重用不可变对象

String s = new String("stringette");

该语句每次执行都会创建一个新的String实例,但是创建这些对象都不是必要的。传递给String构造器的参数(“stringette”)本身就是一个String实例,功能方面等同于构造器所创建的所有对象。如果这样用法在一个循环中,机会创建出成千上万个不必要的String实例。

所以应该使用下边的语句来代替上边

String s = "stringette";

2)重用不会被修改的对象

除了重用不可变对象外,也可重用那些已知不会被修改的可变对象。这也被称为享元模式
比如Integer的valueOf方法

public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

这段代码是指如果数字在一个范围内则会返回一个存储在cache数组中的对象。即实现了对象的重复利用。

3)优先使用基本类型

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

上边代码用于计算int数之和,虽然答案是正确的,但是在这个过程中会创32768个Long的实例。因为上边sum声明为Long类型,而不是long。
所以我们在编写代码时,在非必要情况下,应该尽可能的使用基本数据类型来代替包装类型。

注意:
并不是说创建对象的代价非常昂贵,所以我们应该尽可能地避免穿创建对象。相反,由于小对象的构造器只做很少量的显示工作,所以,小对象的创建和回收动作是非常廉价的,特别是在现代JVM实现上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事情。

6.消除过期对象的引用

public Class Stack{
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_INITAL_CAPACITY = 16;
	public Stact(){
		elements = new Object[DEFAULT_INITAL_CAPACITY];
	}
	public void push(Object e){
		ensureCapacity();
		elements[size++]==e;
	}
	public Object pop(){
		if(size == 0)
			throw new EmptyStackExecption();
		return elements[--size];
	}
	private void ensureCapacity(){
		if(elemenst.length==size)
			elemenst=Arrays.copyOf(elements,2*size+1);
	}
}

这段程序看起来没有什么明显错误,但是它隐藏着一个内存泄漏问题。
如果一个栈先是增长,然后再收缩,那么从栈中弹出来的对象不会被当做垃圾回收。这是因为栈内部维护着对这些对象的过期引用(永远不会被解除的引用)。
在支持垃圾回收的语言中,内存泄漏时很隐蔽的。这类问题的修复方法很简单:一旦对象引用过期,只需要清空这些引用即可。

public Object pop(){
		if(size == 0)
			throw new EmptyStackExecption();
		Object result = elements[--size];
		elements[size] = null;//清空过期引用
		return result;
	}

那么,何时应该清空引用呢?问题在于Stack类自己管理内存。
只要是类自己管理内存,程序员就应该警惕内存泄漏问题。

7.避免使用终结方法

终结方法是值Object类中的finalizer()方法,如果一个对象需要被清除,那么JVM会调用该方法。
终结方法的缺点在于不能保证会被及时的执行。而且在执行终结方法时也会出现异常,这种异常会被忽略,并且该对象的终结过程也会终止。终结方法有一个非常严重的性能损失。
Java语言规范不仅不保证终结方法会被及时执行,而且根本就不保证他们会被执行。
如果类的对象中封装的资源确实需要终止,只需要提供一个显示的终止方法。在每一个实例不在有用时调用这个方法。
显示的终止方法通常与try-finally结构结合起来使用。典型的例子就是InputStream、OutputStream的close方法。
如果要使用终结方法应该注意终结方法链
如果子类覆盖了终结方法,那么JVM会调用子类的终结方法,父类的终结方法得不到调用,会导致父类中的资源无法释放。因此,如果重写终结方法应该再调用父类的终结方法。

@Override
protected void finalize()throws Throwable{
	try{
	//...
	//释放资源
	}finally{
		super.finalize();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值