创建和销毁对象
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();
}
}