Effective Java 读书笔记

Effective Java-对于所有对象都通用的方法


#表示需深入理解
一、覆盖equals时请遵守通用约定
  覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重。最容易避免这类问题的方法就是不覆盖equals方法,在这种情况下,。类的每个实例只与自身相等。如果满足了以下任何一个条件,这就是所期望的结果:
  (1)类的每个实例本质上都是唯一的。
  (2)不关心类是否提供了“逻辑相等”的测试功能。
  (3)超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
  (4)类是私有的或包级私有的,可以确定它的equals方法永远不会被调用。
  那么,什么时候应该覆盖Object.equals呢?如果类有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这是我们需要覆盖equals。通常情况下,这属于“值类”的情形。有一种“值类”不需要覆盖equals方法,即用实例受控确保“每个值至多只存在一个对象”的类。例如枚举。对于这样的类而言,逻辑相同和对象等同是一回事。
  在覆盖equals方法时,你必须要遵守它的通用约定:自反性,对称性,传递性,一致性,非空性。
  违反传递性,主要是因为面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同事,既增加新的组件,同时有保留equals约定。
  结合以上所有的要求,得出了一下实现高质量equals方法的诀窍:
  1、使用==操作符检查“参数是否为这个对象的引用”。
  2、使用instance of 操作符检查“参数是否为正确的类型”。所谓“正确的类型”一般指equals方法所在的那个类。有些情况下,是指该类所实现的接口。
  3、把参数转换成正确的类型。
  4、对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。对于既不是float也不是douvle类型的基本类型域,可以使用==操作符进行比较;对于对象引用域,可以递归地调用equals方法;对于float或double域,可以使用Fload.compare或Double.compare方法。域的比较顺序可能会影响到equal方法的性能。应该先比较最有可能不一致的域,或者是开销最低的域。
  5、当你编写完成了equals方法之后,应该检查它是否是对称的、传递的、一致的?
  最后是一些告诫:覆盖equals时总要覆盖hashCode;不要企图让equals方法过于智能;不要讲equals声明中的Object对象替换为其它类型。

二、覆盖equals时总要覆盖hashCode
  

三、始终要覆盖toString
  

四*、谨慎地覆盖clone
  

五、考虑实现Comparable接口
  

Effective Java-类和接口


一、使类和成员的可访问性最小化
  区别设计良好的模块与设计不好的模块的一个最重要因素在于,这个模块对于外部而言,是否隐藏了其内部数据和其他实现细节。
  信息隐藏可以有效的解除组成系统的各模块之间的耦合关系。使得这些模块可以独立地开发、测试、理解和优化。
  信息隐藏的第一规则是,尽可能地使每个类或者成员不被外界访问。就是使用尽可能最小的访问级别。
  实例域决不能是公有的。如果域是非fianl的,或者一个指向可变对象的final引用,那么一旦这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力。同样的建议也使用于静态域。
  另外,长度非零的数组总是可变的。类具有公有的静态final数组域,或者返回数组域的访问方法,这几乎总是错误的。

二、使公有类中使用访问方法而非公有域
  坚持面对对象程序设计思想的看法:如果类可以在它所在包的外部进行访问,就提供访问方法。然而,如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。

三、使可变性最小化
  不可变类只是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建的时候就提供,并在对象的整个生命周期内固定不变。不可变类比可变类更加易于设计、实现和使用,它们不容易出错,且更加安全。
  为了使类成为不可变,要遵循下面五条规则:1、不要提供任何会修改对象状态的方法。2、保证类不会被扩展。3、使所有的域都是final的。4、使所有的域都成为私有的。5、确保对于任何可变组件的互斥访问。(如果类有指向可变对象的域,则必须保证客户端无法获得指向这些对象的引用;并且,永远不要用客户端提供的对象引用来初始化这样的域)。
  不可变对象本质上是线性安全的,它们不要求同步。可以被自由地共享。不可变对象应该尽可能地重用现有的实例。对于频繁使用的值,为它们提供静态final常量。
  不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象

四、复合优先于继承
  继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具。使用不当会使软件变得很催乳奥。在包的内部使用继承是非常安全的,在那里,子类和超类的实现都处在同一个程序员的控制之下。对于专门为继承而设计,并且具有很好的文档说明的类来说,使用继承也是非常安全的。然而,对普通的具体类进行跨越包边界的继承,则是非常危险的。
  与方法调用不同,继承打破了封装。子类依赖于其超类中特定功能的实现细节。超类的实现一旦变化,子类有可能会被破坏。在扩展一个类的时候,可能由于覆盖旧方法或未覆盖新方法而导致子类的脆弱。
  有一种方法可以避免前面的问题,便是使用“复合”。在新类中增加一个私有域,它引用现有类的一个实例。新类中的每个实例方法都可以调用被包含的现有类实例中的方法,这被称为转发。复合和转发的结合,从技术的角度而言并不是委托,除非包装对象把自身传递给被包装的对象。
  只有当子类和超类之间确实存在子类型关系时,使用继承才是恰当的。

五、要么为继承而设计,并提供文档说明,要么就禁止继承。
  对于为了继承而设计的类,唯一的测试方法就是编写子类。允许继承的类中,构造器决不能调用可被覆盖的方法。

六#、接口优于抽象类
  

七、接口只用于定义类型
  

八、类层次优于标签类
  

九、用函数对象表示策略
  函数指针的主要用途就是实现策略模式。为了在Java中实现这种模式,要申明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略值被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来反复使用的时候,它的类通常就要被实现 私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。

十、优先考虑静态成员类
  如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中。

Effective Java-泛型


一、请不要在新代码中使用原生态类型
  

二、消除非受检警告
  要尽可能地消除每一个非受检警告。如果无法消除警告,同时可以证明引起警告的代码是安全的,可以用@SuppressWarnings(”unchecked”)注解来禁止这条警告。每当使用SuppressWarnings(“unchecked”)注解时,都要调价一条注释,说明为什么这么做是安全的。

三、列表优先于数组
  数组与泛型相比,有两个重要的不同点。首先,数组是协变的,泛型则是不可变的。其次,数组是具体的,只有运行时才知道并检查它们的元素类型约束,而泛型是通过擦除实现的,只是在编译是强化它们的类型信息。从技术的角度来说,像E,List和List这样的类型被称为不可具体化的类型。不可具体化类型(non-reifiable)是指其运行时包含的信息比它编译是包含的信息更少的类型。

四、优先考虑泛型
  

五、优先考虑泛型方法
  

六、利用有限制的通配符类型来提升API的灵活性
  

七、优先考虑类型安全的异构容器
  

Effective Java-枚举和注解


一、用enum代替int常量
  为了将数据与枚举常量关联起来,得声明实例域,并编写一个带有数据并将数据保存在域中的构造器。
  java枚举类型背后的基本想法非常简单:它们就是通过公有的静态final域为每个枚举常量导出实例的类。因为没有可访问的构造器,枚举类型是真正的final。枚举将数据与枚举常量关联起来,它允许添加任意的方法和域,并实现任意的接口。如果要将不同的行为和每一个枚举常量相关联起来,可以定义一个抽象方法。通过策略枚举,可以为每一个枚举常量定义不同的策略,强制为每一个枚举常量选择一个策略枚举,将行为委托给策略枚举。

二、用实例域代替序数
  永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中。

三、用EnumSet代替位域
  

四、用EnumMap代替序数索引
  

五、用接口模拟可伸缩的枚举
  虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。

六、坚持使用Override注解
  

七、用标记接口定义类型
  标记接口有两点胜过标记注解。首先,也是最重要的一点是,标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。另一个优点是:它们可以被更加精确地进行锁定。
  标记注解胜过标记接口的最大优点在于,它可以通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多的信息。
  那么什么时候该使用标记注解,什么时候该使用标记接口呢?很显然,如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解。如果标记只用于类和接口,要先确定:我要编写一个还是多个只接受这种标记的方法呢?如果是这种情况,优先使用标记接口。这样可以提供编译时进行类型检查的好处。

Effective Java-方法


一、检查参数的有效性
  绝大多数方法和构造器对于传递给它们的参数值都会有某些限制。你应该在文档中清楚地指明所有这些限制,并且在方法体的开头处检查参数。这一规则也有例外,一个很重要的例外是,在有些情况下,有效性检查工作非常昂贵,或者根本是不切实际的,而且有效性检查已隐含在计算过程中完成。然而,不加选择地使用这种方法会导致失去失败原子性。
  不要从本条目的内容中得出这样的结论:对参数的任何限制都是好事。相反,在设计方法时,应该使它们尽可能地通用,并符合实际的需要。

二、必要时进行保护性拷贝
  假设类的客户端会尽其可能来破坏这个类的约束条件,因此你必须保护性地设计程序。保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始对象。对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。

三、谨慎设计方法签名
  谨慎地选择方法的名称;不要过于追求提供便利的方法;避免过长的参数列表(目标是四个参数,或者更少。对于参数类型,要优先使用接口而不是类。对于boolean参数,要优先使用两个元素的枚举类型)

四、慎用重载
  要调用哪个重载方法是在编译是做出决定的。比如对于编译时类型相同的:Collection

Effective Java-通用程序设计


一、将局部变量的作用域最小化
  要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。几乎每个局部变量的声明都应该包含一个初始化表达式。

二、for-each循环优先于传统的for循环
  

三、了解和使用类库
  

四、如果需要精确的答案,请避免使用float和double
  

五、基本类型优先于装箱基本类型
  

六、如果其他类型更适合,则尽量避免使用字符串
  字符串不适合代替其他的值类型;字符串不适合代替枚举类型;字符串不适合代替聚集类型;字符串也不适合代替能力表。

七、当心字符串连接的性能
  

八、通过接口引用对象
  

九、接口优先于反射机制
  

十、谨慎地使用本地方法
  

十一、谨慎地进行优化
  

十二、遵守普遍接受的命名习惯
  

Effective Java-异常


一、只针对异常的情况才使用异常
  

二、对可恢复的情况使用受检异常,对编程错误使用运行时异常
  如果期望调用者能够适当地恢复,对于这种情况就应该使用受检的异常。用运行时异常来表示编程错误。大多数的运行时异常都表示前提违例。

三、避免不必要地使用受检的异常
  

四、优先使用标准异常
  

五、抛出与抽象相对应的异常
  如果不能阻止或者处理来自更低层的异常,一般的做法是使用异常转译,除非低层方法碰巧可以保证它抛出的异常对高层也适合才能将异常从低层传播到高层。异常链对高层和低层异常都提供了最佳的功能:它允许抛出适当的高层异常,同时又能捕获底层的原因进行分析。

六、每个方法抛出的异常都要有文档
  

七、在细节消息中包含能捕获失败的异常
  

八、努力是失败保持原子性
  对于在可变对象上执行操作的方法,获得原子性最常见的办法是,在执行操作之前检查参数的有效性。

九、不要忽视异常
  

你可能感兴趣的:(读书笔记,effective)