Effective Java(二)

来源:互联网 发布:那些年网络歌曲动漫 编辑:程序博客网 时间:2024/06/14 09:50
一.覆盖equals方法时请遵守通用约定
很多时候我们需要覆写object的equals方法(Object默认的equals方法实现为:return( this = obj);),通常来说,如果类具有自己特有的“逻辑相等”概念时,我们就需要覆写equals方法来实现逻辑相等。类的每一个实例本质上都是唯一的。在覆写equals方法时必须遵守一些约定,下面是约定的内容:
1.自反性。对于任何非null的引用值x,x.equals(x)必须返回true.
2.对称性。对于任何非null的引用值x,y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true.
3.传递性。对于任何非null的引用值x,y,z,当x.equals(y)返回true,且y.equals(z)返回true,那么x.equals(z)一定返回true.
4.一致性。对于任何非null的引用值x,y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)一定返回true,或一定返回false.
5.对于任何非null的引用值x,x.equals(null)一定返回false.
这些规则理解起来比较简单,但是有时候我们会无意中破坏这些规则。假如我有A,B两个类,如果我覆写了A类的equals方法而忘了覆写B,此时A.equals(B)为true,B.equals(A)为false,就违反了对称性约定。
实现高质量equals方法的步骤:
1.使用 == 操作符检查参数是否为这个对象的引用,如果是则返回true。
2.使用instanceof操作符检查参数是否为正确的类型,如果不是返回false。
3.把参数转为正确的类型,因为转换之前使用instanceof进行过测试,所以不会报错。
4.检查类中所有的关键域,检查参数中的域是否与该对象中的域相匹配。
二.覆写equals时总要覆写hashCode方法
在覆写equals方法时,总要覆写hashCode方法,如果不这样做,就会违反object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,如hashMap,Hashtable,hashSet等。
public class PhoneNumber {     private final short areaCode ;     private final short prefix ;     private final short lineNumber ;     public PhoneNumber( int areaCode , int prefix, int lineNumber) {           super();           this.areaCode = ( short)areaCode ;           this.prefix = (short)prefix;           this.lineNumber = (short)lineNumber;     }          public boolean equals(Object obj ){           if(obj == this){               return true ;          }           if(!(obj instanceof PhoneNumber)){               return false ;          }          PhoneNumber pn = (PhoneNumber) obj;           return pn .areaCode == areaCode &&                    pn. prefix == prefix&&                    pn. lineNumber == lineNumber ;     }     public static void main(String[] args) {          HashMap<PhoneNumber,String> map = new HashMap<>();          PhoneNumber pn = new PhoneNumber(123,234,234);           map.put( new PhoneNumber(123,234,234), "liuyang");               }}
上面这段代码返回null,这里涉及到两个对象,一个是put到map中的对象,第二个对象与第一个相等,被用于获取。由于PhoneNumber没有覆写hashCode方法导致两个实例有不相等的散列码,违反了hashCode约定。因此hashMap把第一个对象放到第一个散列桶,get方法却在另一个散列桶里查找,即使两个对象凑巧放到一个散列桶,同样返回null,因为hashMap中两个散列码不匹配。
三.始终要覆盖toString方法
java中Object自带的toString方法返回的是类的名称以及@,散列码的十六进制表示法,如:effective.PhoneNumber@65712a80,为了让对象的信息更通俗易懂,我们都应该覆写toString方法,通常我们是输出所有域的值。
四.使类和成员的可访问性最小化
设计良好的模块会隐藏所有的实现细节,把它的API和实现清晰的隔离开来。然后模块之间通过这个API进行通信,一个模块不需要知道其他模块的内部工作情况,这就是封装。
封装之所以这样重要的原因:它可以有效解除组成系统各模块之间的耦合关系,使得这些模块可以独立开发,测试,优化,使用,理解和修改。这样可以加快系统开发速度,也减轻了维护负担,并且在调试的时候可以不影响其他模块。虽然封装本身不会带来性能的提高,但是它可以有效的调节性能:一旦完成系统,可以通过剖析确定了哪些模块影响了性能,哪些模块单独优化,不影响系统正确性。封装提高了系统的可重用性,因为模块之间不紧密相连,除了开发这些模块使用的环境之外,他们在其他的环境中往往也很有用。最后,封装降低了大型系统的构建风险,即使整个系统不可用,但是独立的模块可能可用。
1.尽可能使每个类或者成员不被外界访问     
换句话说,应该使用与你正在编写的软件的对应功能相一致的尽可能最小的访问级别。不做成public的好处是,在以后的版本中,我们可以对他进行修改,替换甚至删除,不用担心会影响到现有的客户端程序,如果做成公有的,就有责任永远支持他,保证兼容性。
2.实例域决不能是公有的
如果域不是final的,那么一旦这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力,也就是说当这个域被修改时,我们失去了对它采取任何行动的能力。
3.在公有类中使用访问方法而非公有域
这就是我们平时在类中属性私有化,提供setter,getter方法来操作属性,这样做的好处是,我们可以在setter,getter方法操作实例域的时候做一些逻辑处理,同时方便维护,使用方法来操作数据,当逻辑有修改时,我们只需修改方法
五.使可变性最小化
不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。存在不可变类的理由:不可变类比可变类更加容易设计,实现和使用,它们不容易出错,且更加安全。
为了使类成为不可变,要遵循下面五条原则:
1.不要提供任何会修改对象状态的方法。
2.保证类不会被扩展,一般做法是将这个类声明为final的(或者将构造器私有,提供公有的静态工厂代替公有构造器)。
3.使所有的域都是final的。
4.使所有的域都是私有的。
5.确保对于任何可变组件的互斥访问,如果类具有指向可变对象的域,则必须保证客户端无法获得指向这些对象的引用。
不可变对象的优点:
1.不可变对象比较简单,只有一种被创建时的状态,在整个生命周期内不会变化,无需在做额外的工作维护。
2.不可变对象本质上是线程安全的,它们不要求同步。
不可变对象的真正缺点是:对于每个不同的值都需要一个单独的对象。
不可变类虽然很多优点,不过我平时工作的时候见到的很少,且大多数类都是公有的,不符合这两条建议。
六.组合优先于继承
继承是代码复用的有力手段,但它并非完成这项工作的最佳工具。
继承的主要问题是破坏了封装性,子类依赖超类中特定功能的实现细节,超类的实现有可能随着版本不同而变化,如果发生了变化,子类可能遭到破坏,即使它的代码没有改变。
组合是一种新的方式,在新的类中增加一个私有域,引用现有类的一个实例,因为现有类变成了新类的一个组件。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这被称为转发。这样得到的类更加稳固,它不依赖现有类的实现细节,即使现有类添加了新的方法,也不会影响到新的类。   
只有在子类真的是一个父类的时候,才适合用继承。
七.接口优于抽象类
Java提供了两种机制,接口和抽象类。这两种机制最明显的区别是:抽象类允许包含某些方法的实现,但是接口不允许。一个更重要的区别是:为了实现由抽象类定义的类型,类必须称为抽象类的一个子类。任何一个类,直要它定义了所有必要的方法,并且遵守通用约定,就被允许实现一个接口。Java只允许单继承,所以抽象类受到极大限制。(接口相当于一种规范,抽象类则相当于模板)


0 0
原创粉丝点击