Effective Java 笔记

2014.06.17 | Comments

创建和销毁对象

NO.1 考虑用静态工厂方法代替构造函数

静态工厂方法好处:

  • 1、构造函数有命名的限制,而静态方法有自己的名字,更加易于理解。
  • 2、静态工厂方法在每次调用的时候不要求创建一个新的对象。这种做法对于一个要频繁创建相同对象的程序来说,可以极大的提高性能。它使得一个类可以保证是一个singleton;他使非可变类可以保证“不会有两个相等的实例存在”。
  • 3、静态工厂方法在选择返回类型时有更大的灵活性。使用静态工厂方法,可以通过调用方法时使用不同的参数创建不同类的实例,还可以创建非公有类的对象,这就封装了类的实现细节。
  • 4、在创建参数化类型实例的时候,他们使代码变的更加简洁。

例如:

public static Boolean valueOf(boolean f){
	return b ? Boolean.TRUE : Booleab.FALSE;
}

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

静态工厂方法坏处:

  • 1、如果一个类是通过静态工厂方法来取得实例的,并且该类的构造函数都不是公有的或者保护的,那该类就不可能有子类(被继承),子类的构造函数需要首先调用父类的构造函数,因为父类的构造函数是private的,所以即使我们假设继承成功的话,那么子类也根本没有权限去调用父类的私有构造函数,所以是无法被继承的。
  • 2、毕竟通过构造函数创建实例还是SUN公司所提倡的,静态工厂方法跟其他的静态方法区别不大,这样创建的实例谁又知道这个静态方法是创建实例呢?弥补的办法就是:静态工厂方法名字使用valueOf、of、getInstance、newInstance、getType、newType。

NO.2 遇到多个构造器参数时要考虑用构建器

当有多个构造方法,一般式参数大于4个的时候,建议使用Builder模式。

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

用单元素的枚举来实现单例模式。

NO.4 通过私有的构造函数强化不可实例化的能力

在面向对象程序设计中,假如存在太多只有静态属性和静态方法的类;那么,面向对象的思想可能在这会损失殆尽。

但是,并不能说面向对象的程序中就不应该出现只有静态属性和静态方法的类,相反,有时候我们还必须写这样的类作为工具类。这样的类怎么实现呢?有人可能会把该类定义成抽象类(Abstract class),的确,抽象类是不可以实例化的,但是别忘了还有继承,继承了抽象类的子类在实例化时候,默认是会先调用父类无参数的构造函数的(super();),这时候,父类不是也被实例化了嘛?

其实我们可以这样做,把该类的构造函数定义为私有的(private),而类的内部又不调用该构造函数的话,就成功了。这样带来的后果就是该类成了 final的,不可能再被任何类继承了,要被继承,得提供一个公有(public)的或者保护(protect)的构造函数,这样才能被子类调用。

NO.5 避免创建重复的对象

如果一个对象是不可变的,那么他总是可以被重用的,如:

//不推荐,"test"本来就是一个String实例,如果此方法在一个循环中或者被频繁的调用,将会严重影响性能
String s = new String("test");
//推荐方式
String s = "test";

对于提供静态方法和构造函数的非可变类,推荐使用静态方法,这样可以避免重复创建对象,如:Boolean.vauleOf(String)方法优于构造函数Boolean(String)

类初始化的顺序:

先初始化父类的静态代码 —> 初始化子类的静态代码 —> 初始化父类的非静态代码 —> 初始化父类构造函数 —> 初始化子类非静态代码 —>初始化子类构造函数。

NO.6 消除过期的对象引用

内存泄漏问题:如果一个对象的引用被无意识的保留起来,那么垃圾回收机制是不会去处理这个对象,而且也不会去处理被这个对象引用的其它对象。 比如堆栈的弹出栈元素方法。

public Object pop(){

   if(size == 0){
       throw new EmptyStackException();
   }

	Object result = elements[--size];
	//自减后把原来的引用置为null
	elements[size] = null;
	return result;
}

内存泄露常出现在:

  • 过期对象
  • 缓存,由于缓存没有及时清除无用的条目而出现,可以使用weakHashMap来避免这种情况
  • 监听器和其他回调

清理过期对象引用的好处是:如果在以后又被使用到该引用,最会抛下NullPointException而不是让程序继续错误的运行下去,尽可能早的监测出程序中的错误总是有好处的。

方法:重新使用这个已经指向一个对象的引用,或结束其生命周期。

对所有对象都通用的方法

equals方法

(1) equals方法一般用于“值类”的情形,比如Integer,Date目的是为了比较两个指向值对象的引用的时候,希望 它们的逻辑是否相等而不是它们是否指向同一个对象。

约定:

  • a 自反性 对任意的对象必须和它自身相等。对值引用x x.equals(x) 一定返回true
  • b 对称性 对任意的值引用x,y,如果x.equals(y) 一定有y.equals(x)
  • c 传递性 对任意的值引用x,y,z,如果x.equals(y),y.equals(z) 一定有x.equals(z)
  • d 一致性 对于任何非null的引用x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true

结论:

要想在扩展一个可实例化的类的同时,即要保证增加新的特性,又要保证equals约定,建议复合优于继承原则。若类和类是 a kind of 关系则用继承,若类和类是 a part of 关系则用组合(复合)

hashCode

相等的对象必须要有相等的散列码,如果违反这个约定可能导致这个类无法与某些散列值得集合结合在一起使用,所以在改写了equals方法的同时一定要重写hashCode方法以保证一致性。

toString

toString返回值中包含所有信息

clone

Comparable

类和接口

  • 使类和成员的可访问能力尽量的小
  • 支持非可变性
  • 复合优于继承
  • 接口优于抽象
  • 优先考虑静态成员类

方法

  • (1) 检查参数的有效性
  • (2) 需要使用保护性拷贝
  • (3) 方法设计的一些原则 - a、避免长的参数列表,尤其是参数相同的参数列表。 - b、对参数类型使用接口,而不是接口的实现类。 - c、谨慎使用重载。 - d、返回0程度的数组而不是null。
  • (4) 为所有导出的api方法编写注释

重载(overloaded method) 选择的是静态的。选择的依据是参数类型

重写(oveeridden method) 选择的依据是被调用方法所在对象的运行时的类型。

通用设计方法

  • (1) 将局部变量的作用域最小化
  • (2) foreach优于传统的for循环。有三种情况无法使用foreach循环:过滤、转换、平行迭代
  • (3) 了解和使用类库
  • (4) 如果要得到精确结果,最好是用BigDecimal 而不使用fload或double
  • (5) 对数量大的字符串连接使用StringBuffer而不是String前者速度快。
  • (6) 基本类型优先于装箱类型
  • (7) 当心字符串连接性能
  • (8) 通过接口引用对象
  • (9) 接口优先于反射机制
  • (10) 谨慎使用本地方法
  • (11) 谨慎进行优化

原创文章,转载请注明: 转载自JavaChen Blog,作者:JavaChen
本文链接地址:http://blog.javachen.com/2014/06/17/note-about-effective-java.html
本文基于署名2.5中国大陆许可协议发布,欢迎转载、演绎或用于商业目的,但是必须保留本文署名和文章链接。 如您有任何疑问或者授权方面的协商,请邮件联系我。