effective java 78条

本文提供了78条Java编程的最佳实践,涵盖了从静态工厂方法、构建器的使用到Singleton模式的实现,再到避免创建不必要的对象、消除过期引用、合理使用终结方法等。同时还深入探讨了equals、hashcode和toString方法的覆盖,以及clone方法的谨慎使用和Comparable接口的实现,旨在帮助开发者写出更高效、更安全的代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一条:考虑用静态工厂方法代替构造器

静态工厂方法与构造器不同的第一大优势在于,他们有名称。
静态工厂方法与构造器不同的第二大优势在于,不必在每次调用的时候都创建一个新的对象。
静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。
静态工厂方法与构造器不同的第三大优势在于,在创建参数化类型实例的时候,它们使代码变得更简洁。

静态工厂方法的主要缺点在于,类如果不含有公有的或者受保护的构造器,就不能被子类化。
静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别。

第二条:遇到多个构造器参数时要考虑用构建器

public class NuTritionFacts {

	private final int servingSize;
	private final int servings;
	private final int calories;
	private final int fat;
	private final int sodium;
	private final int carbohydrate;
	
	
	public static class Builder{
	//必须的属性
		private final int servingSize;
		private final int servings;
		//可选的属性
		private  int calories;
		private  int fat;
		private  int sodium;
		private  int carbohydrate;
		
		public Builder( int servingSize,int servings){
			this.servingSize=servingSize;
			this.servings=servings;
		}
		
		public Builder calories(int val) {
			calories=val;
			return this;
		}
		
		public Builder fat(int val) {
			fat=val;
			return this;
		}
		
		
		public Builder carbohydrate(int val) {
			carbohydrate=val;
			return this;
		}
		
		public Builder sodium(int val) {
			sodium=val;
			return this;
		}
		
		public NuTritionFacts build() {
			return new NuTritionFacts(this);
		}
		
	}
	

	private NuTritionFacts(Builder builder) {
		servings=builder.servings;
		servingSize=builder.servingSize;
		calories=builder.calories;
		fat=builder.fat;
		carbohydrate=builder.carbohydrate;
		sodium=builder.sodium;
	}
}

public class Test {

	public static void main(String[] args) {
		
		NuTritionFacts nut=new NuTritionFacts.Builder(20, 30).fat(200).build();
	}
}

第三条:用私有构造器或者枚举类型强化Singleton属性

实现Singleton的三种方法
1、利用共有的final静态成员实现


public class Elvis1 {
	// 利用final的共有静态成员实现
	public static final Elvis1 INSTANCE = new Elvis1();

	private Elvis1() {

	}

	public void leaveBuild() {

	}
}

2、静态工厂方法实现

public class Elvis2 {

	private static final Elvis2 INSTANCE = new Elvis2();

	private Elvis2() {
	}

	public static Elvis2 getInstance() {
		return INSTANCE;
	}

	public void leaveBuild() {

	}
}

3、使用枚举方法实现

public enum Elvis3 {

	INSTANCE;

	public void leaveBuild() {

	}
}

4、客户端

public class Test {

	public static void main(String[] args) {
		Elvis1 e1= Elvis1.INSTANCE;
		e1.leaveBuild();
		
		Elvis2 e2=Elvis2.getInstance();
		e2.leaveBuild();
		
		Elvis3 e3=Elvis3.INSTANCE;
		e3.leaveBuild();
	}
}

第四条:通过私有构造器强化不可实例化的能力

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

最好能重用对象,而不是需要的时候就创建一个。
不要为了避免创建对象而使用对象池,除非池中对象时重量级的。

第六条:消除过期的对象引用

避免造成内存泄漏,其一是数组中不再被用到的元素。
内存泄漏的另一常见来源是缓存,容易被遗忘。
内存泄漏的第三个常见来源是监听器和其他回调。

第七条:避免使用终结方法

因为终结方法的优先级很低。

第八条 : 覆盖equals时请遵守通用约定

1、自反性(reflexive)。对于任何非null的引用值,x.equals(x)必须返回true。
2、对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
3、传递性(transitive)。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
4、一致性(consistent)。对于任何非null的引用值x和y,只要equals的比较操作符在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或一直的返回false。
5、对于任何非null的引用值x,x.equals(null)必须返回false。
提高equals方法质量的诀窍
1、使用==操作符检查“参数是否为这个对象的引用”。
2、使用instanceof操作符检查“参数是否为正确类型”。
3、把参数转为正确类型。
4、对于类中的每个关键(significant)域,检查参数中的域是否与该对象中对应的域相匹配。
5、遵守通用约定。

  • 覆盖equals时总要覆盖hashcode。
  • 不要企图让equals方法过于智能。
  • 不要将equals声明中的object对象替换为其他类型。

第九条 :覆盖equals时总要覆盖hashcode。

散列函数的简单实现:

  1. 把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。

  2. 对于对象中的每个关键域(指equals方法涉及的每个域),完成以下步骤。
    a.为该域计算int类型的散列码c:
    i.如果是boolean类型,则计算(f?1:0);
    ii.如果该域是byte、char、short或者int类型,则计算(int)f。
    iii.如果该域是long类型,则计算(int)(f^(f>>>32))。
    iv.如果该域是float类型,则计算Flaot.floatToIntBits(f)。
    v.如果该域是double类型,则计算Double.doubleToLongBits(f)。
    vi.如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方式来比较这个域,则同样的为这个域递归的调用hashcode。如果需要更复杂的比较,则为这个域计算一个范式(canonical representation),然后针对这个范式调用hashcode。如果这个值域是null,则返回0;
    vii.如果该域是一个数组,则需要把每一个元素当做单独的域来处理。也就是说递归的应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b的做饭把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用arrays.hashcode方法。
    b.按照下面公式,把步骤2.a中计算得到的散列码c合并到result中:
    result=31*result+c.

  3. 返回result

  4. 写完之后验证,相等的实例是否有相同的散列码。

第十条:始终要覆盖toString方法。

  1. toString方法可以让对象更具有可读性,提高开发的效率,比如BigInteger和BigDecimal。
  2. toString 的坏处就是,如果在其他地方过多的使用toString方法来获取对象的指定格式的字符串,那么在后期对于对象的修改将会变得难以维护。

第十一条 :谨慎的的覆盖clone

  1. Cloneable接口的目的是作为一个对象的mixin interface,表明对象允许克隆。但是Object的clone方法是受保护的,所以clone接口的实现需要具有规范的实现,以保证clone的正常执行。
  2. clone的通用约定:
 x.clone()!=x;
 x.clone().getclass()==x.getclass();
 x.clone().equals(x);

clone的约定是非常弱的。
3. 建议使用拷贝工厂或拷贝构造器来实现clone的功能,不需要遵守clone的规范,同时过程是可控的

public Yum(Yum yum);
public static Yum newInstance(Yum yum);

第十二条:考虑实现Comparable接口

  1. 实现Compareable接口,它就可以和许多泛型算法以及依赖于该接口的集合实现进行协作。
  2. CompareTo约定的条款(自反性、对称性、自反性):
        第一条指出,如果颠倒了两个对象之间的的比较方向,就会发生下面的情况:如果第一个对象小于第二个对象,则第二个对象一定大于第一个对象;如果第一个对象等于第二个对象,则第二个对象一定等于第一个对象;如果第一个对象大于第二个对象,则第二个对象一定小于第一个对象。
       第二条指出,如果第一个对象大于第二个对象,并且第二个对象又大于第三个对象,那么第一个对象一定大于第三个对象。
       最后一条指出,在比较被认为相等的所有对象,他跟别的对象做比较时一定会产生相同的结果。
  3. 注意:整数基本数类型时可以使用<>操作符,但是在浮点数时,应使用Double.compare()或者Float.compare()。

第十三条:使类和成员的可访问性最小化

1、非final类型或者final指向了可变对象的引用时,便失去了强制这个域不可变的能力。
2、注意,长度非零的数组总是可变的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值