您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 经营企划 > Effective-Java
第四章:类和接口类和接口是java程序设计语言的核心,它们也是java语言的基本气象单元。Java语言提供了许多强大的基本元素,供程序员用来设计类和接口。本章包含的一些指导原则,可以帮助你更好地利用这些语言元素,以便你设计出来的类和接口更加有用、健壮和灵活。Item12:使类和成员的可访问能力最小化要想区别一个设计良好的模块与一个设计不好的模块,最重要的因素是,这个模块对于外部的其他模块而言,是否隐藏了内部的数据和其他的实现细节。一个设计良好的模块会隐藏所有的实现细节,把它的API与实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个模仿被称为信息隐藏(informationhiding),或封装(encapsulation),是软件设计的基本原则之一[Parnas72]。信息隐藏之所以非常重要,是有许多的理由,其中大多数理由都源于这样一个事实:它可以有效地解除一个系统中各模块之间的耦合关系,使得这些模块可以被独立地开发、测试、优化、使用、理解和修改。这样可以加速系统开发的速度,因为这些模块可以被并行地开发,它也减轻了维护的负担,因为程序员很快就可以理解这些模块,并且在调试他们的时候可以不伤其他的模块。虽然信息隐藏本身无论是对内还是对外,都不会带来更好的性能,但是它使得有效的性能调节成为可能。一旦一个系统已经完成,通过分析就可以知道哪些模块影响了系统的性能(见item37),那么这些模块可以被进一步优化,而不会影响到其他模块的正确性。模块所使用的环境之外,它们在其他的环境中也往往是有用的。最后,信息隐藏隐藏也降低了构建大型系统的风险;即便整个系统并不成功,这些独立的模块也有可能是成功的,Java程序设计语言提供了许多设施(facility)来帮助做到信息隐藏。其中一个设施是访问控制(accsscontrol)机制[JLS,6.6],它决定了类、接口和成员的可访问性(accessibility)。一个实体的可访问性是由该实体声明所在的位置,以及该实体声明中所出现的文章修饰符(private、protected和public)共同决定的。正确地使用这些修饰符对于实现信息隐藏是非常关键的。经验表明,你应该尽可能地使每一个类或成员不被外界访问。换句话说,你应该使用最低可能的、并且与该软件的正确功能想一致的访问级别。对于顶层的(非嵌套的)类和接口,它们只有两种可能的访问级别:狗尾续貂私有的(package-private)和公有的(public)。如果你声明了一个具有public修饰符的顶层类或者接口,那么它是公有的;否则,它将是包级私有的,如果一个类或接口能够被做成包级私有的么它就应该被做成狗尾续貂私有的,通过把一个类或者接口做成包级私有的,它实际上成了这个包的实现的一部分,而不是该包导出的API的一部分;并且,在以发行版本中,你可以对它进行修改、替换。或者去除。而无需担心会伤害到现有的客户。如果你把它做成公有的,你就有义务永远支持它,以保持兼容性。如果一个包级私有的顶层类或者接口只是在某一个类的内部被用到,那么你应该考虑使它成为后者的一个私有嵌套类(或者接口)(见item18)。这样可以进一步降低它的可访问性,然而,这样做不像“使一个不必要的公有类成为包级私有的类”那样重要,因为一个包级私有的类已经是这个包的实现的一部分,而不是其API的一部分。对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别,下面按照可访问性递增的顺序列出来:•私有的(private)-----只有在声明该成员的顶层类内部才可以访问这个成员。•包级私有的(package-private)-----声明该成员的包内部的任何类都可以访问这个成员。在技术上,它被称为“默认(default)访问级别”,如果没有为成员指定访问修饰符的话,那么它就具有这样的访问级别。•受保护的(protected)-----该成员声明的反在类的子类可以访问这个成员(有一些限制[JLS,6.6.2])并且,该成员声明所在的包内部的任何类也可以访问这个成员。•公有的(public)-----任何地方都可以访问该成员。当你仔细地设计了一个类的公有API之后,接下去应该把所有其他的成员都变成私有的。只有当同一个包内的另一个类真正需要访问一个成员的时候,你才应该去掉private修饰符,使该成员变成包级私有的。如果你发现自己经常要这样的事情,那么你应该重新松果你的系统设计,看看是否另一种分解方案所得到的类具有更好的分离特性,彼此之间耦合度更小。可以这样说,私有成员和包级私有成员都是一个类的实现中的一部分,并不会影响到其导出的API。然而,如果这些域所在的类实现了Serializable接口(见item54和item55),那么这些域可能会被“泄漏(leak)”到导出的API中。对于公有类的成员,当访问级别从包级私有变成保护级别时,会出现可访问性的巨大增加。受保护的成员是一个类的导出的API的一部分,必须永远被支持。更进一步,一个导出的类的每一个受保护的成员代表了该类对于一个实现细节的公开承诺(见item15)。受保护的成员应该尽量少用。有一条规则使得你无法降低一个方法的可访问性。如果一个方法改写了超类中的一个方法,那么子类中该方法的访问级别低于超类中的访问级别是不允许的[JLS,8.4.6.3]。这样可以确保子类的实例可以被用在任何可使用超类的实例的场合。如果你违反了这条规则,那么当你试图编译该子类的时候,编译器会产生一条错误信息。这条规则的一种特殊情形是,如果一个类实现了一个接口,那么接口中所有的方法在这个类中都必须被声明为公有的。这是因为接口中所有的方法都隐含着公有访问级别。公有类应该尽可能少地包含公有的域(相对于公有的方法)。如果一个域是非final的,或者是一个指向可变对象的final引用,那么一旦你使它成为公有的,就放弃了对存储在这个域中的值进行限制的能力;当这个域被修改的时候,你也失去了采取任何行动的能力。一个简单的后果是,包含公有可变域的类不是线程安全的。即使一个域是final的,并且没有指向任何一个可变对象,那么,一旦你把这个域变成公有的,也就放弃了“切换到一个新的内部数据表示(其中这个域不存在)”的灵活性。对于“公有类不应该包含公有域”这条规则也有一个例外,通过公有的静态final域来暴露类的常量是允许的。按照惯例,这样的域的名字由大写字母组成,单词之间用开线隔开(见item38)。很重要的一点是,这些域要么包含原语类型的值,要么包含指向非可变对象的引用(见item13)。如果一个final域包含一个指向可变对象的引用,那么它具有非final域的所有缺点。虽然引用本身不能被修改,但是它引用的对象可以被修改-----这会导致灾难性的后果。注意,非零长度的数组总是可变的,所以,具有公有的静态final数组域几乎总是错误的。如果一个类包含这样的一个域,客户将能够修改数组中的内容。这是安全漏洞的一个觉根源://Potentialsecurityhole!publicstaticfinalType[]VALUES={...};公有数组应该被替换为一个私有数组,以及一个公有的非可变列表:privatestaticfinalType[]PRIVATE_VALUES={...};publicstaticfinalListVALUES=Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));另一种办法是,如果你要求编译时compile-time的类型安全性,并且愿意损失一点性能的话,你可以把公有的数组域替换为一个公有的方法,它返回私有数组的一份拷贝:privatestaticfinalType[]PRIVATE_VALUES={...};publicstaticfinalType[]values(){return(Type[])PRIVATE_VALUES.clone();}总而言之,你应该总是尽可能地降低可访问性。你在仔细地设计了一个最小的公有API之后,应该防止把任何杂散的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类不应该包含公有域,并且确保公有静态final域所引用的对象是不可变的。Item13:支持非可变性一个非可变是一个简单的类,它的实例不能被修改。每个实例中包含的所有信息都必须在该实例被创建的时候就提供出来,并且在对象的整个生存期内固定不变。java平台库包含许多非可变类,其中有String、原语类型的包装类、BigInterger和BigDecimal。非可变类的存在有许多理由:非可变类比可变类更加易于设计、实现和使用。它们不容易出错,更加安全。为了使一个类成为非可变类,要遵循下面五条规则:1.不要提供任何会修改对象的方法(也称为mutators)。2.保证没胡可被子类改写的方法。这可以防止粗心的或者恶意的子类破坏该类的不可变行为。为了禁止改写方法,一般做法是使这个类成为final的,但是后面我们还会讨论到其他的做法。3.使所有的域都是final的。通过系统的强制方式,这可以清楚地表明你的意图。而且,如果一个指向新创建的实例的引用在缺乏同步机制的情况下,被从一个线程传递到别一个线程,那么有可能还必须要保证正确的行为,这取决于正在重新设计中的内存模型的情况[Pugh01a]。4.使所有的域都成为私有的。这样可以防止客户直接修改域中的信息。虽然非可变类可以只有公有的final域,只要这些域包含原语类型的值或者指向非可变对象的引用,从技术上讲这是允许的,但是,这样做不值得提倡,因炎这使得在以后的版本国不可能再改变内部表示。5.保证对于任何可变组件的互斥访问。如果你的类具有指向可变对象的域,刚必须确保该类的客户无法获得指向这些对象的引用。并且,永远不要用客户提供的对象引用来初始化这样的域,了不要在任何一个访问方法中返回该对象引用。在构造函数、访问方法和readObject方法(见item56)中请使用保护性拷贝技术(见item24)。前面条目中的许多例子都是非可变的,其中一个鸽子是item8中的PhoneNumber,针对每一个属性它都有一个访问方法,但是没有对应的改变函数。下面是一个稍微复杂一点的例子:publicfinalclassComplex{privatefinalfloatre;privatefinalfloatim;publicComplex(floatre,floatim){this.re=re;this.im=im;}//AccessorswithnocorrespondingmutatorspublicfloatrealPart(){returnre;}publicfloatimaginaryPart(){returnim;}publicComplexadd(Complexc){returnnewComplex(re+c.re,im+c.im);}publicComplexsubtract(Complexc){returnnewComplex(re-c.re,im-c.im);}publicComplexmultiply(Complexc){returnnewComplex(re*c.re-im*c.im,re*c.im+im*c.re);}publicComplexdivide(Complexc){floattmp=c.re*c.re+c.im*c.im;returnnewComplex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp);}publicbooleanequals(Objecto){if(o==this)returntrue;if(!(oinstanceofComplex))returnfalse;Complexc=(Complex)o;return(Float.floatToIntBits(re)==//Seepage33toFloat.
本文标题:Effective-Java
链接地址:https://www.777doc.com/doc-5197281 .html