《Effective Java》读书笔记二

十五 使可变性最小化

1、一个类的只有一个默认的私有构造函数,这就说明了两点

①其不能被初始化

其不能被继承(因为继承之后,子类要初始化,首先就要调用父类的构造函数,但是父类的构造函数是私有的,无法访问!同时这个功能起到了final的作用

2、final类不能被继承,但可以实例化!

3、String类就是一个不可变类。不可变对象本质上是线程安全的,它们不要求同步;不仅可以共享不变对象,甚至可以共享它们的内部信息;不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。

4、子类所覆盖的方法的可见性至少要等于父类,即子类可见性≥父类。特殊的例子就是所有实现接口的方法的可见性都是public的,因为接口所有的方法都是public的。

十六 复合优先于继承

1、继承的缺点:

①子类父类的耦合性太高,父类的改变对于子类来说,有可能是毁灭性的。子类依赖于父类的函数,有可能造成子类复写的失败。依赖于父类的实现细节。

②新发行的版本当中父类当中有可能有和子类当中相同的函数名。

2、只有当子类真正是父类的子类型时才适合继承操作。

3、继承机制会把父类API中的所有缺陷传播到子类中,而符合则允许设计新的API来隐藏这些缺陷。

4、继承关系最大的弱点是打破了封装,子类能够访问父类的实现细节,子类与父类之间紧密耦合,子类缺乏独立性,从而影响了子类的可维护性。为了尽可能地克服继承的这一缺陷,应该遵循以下原则:

l  精心设计专门用于被继承的类,继承树的抽象层应该比较稳定。

l  对于父类中不允许覆盖的方法,采用final修饰符来禁止其被子类覆盖。

l  对于不是专门用于被继承的类,禁止其被继承。

l  优先考虑用组合关系来提高代码的可重用性。

组 合 关 系

继 承 关 系

优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立

缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性

优点:具有较好的可扩展性

缺点:支持扩展,但是往往以增加系统结构的复杂度为代价

优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象

缺点:不支持动态继承。在运行时,子类无法选择不同的父类

优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口

缺点:子类不能改变父类的接口

缺点:整体类不能自动获得和局部类同样的接口

优点:子类能自动继承父类的接口

缺点:创建整体类的对象时,需要创建所有局部类的对象

优点:创建子类的对象时,无须创建父类的对象

 

十七 要么为继承而实际,并提供文档说明,要么就禁止继承

1、要么为继承而设计文档,并提供文档说明,要么就禁止继承。

2、好的API文档应该描述一个给定的方法做了什么工作,而不是描述它是如何做到的,而继承文档违背了这一点。

3、对于为了继承而设计的类,唯一的测试方法就是编写子类。

4、为了继承而设计的类,决不能在构造函数当中调用可以被复写的方法,因为在子类当中可能复写了改方法,而没有初始化,造成空指针异常。

5、在设计父类时,如果父类实现了Cloneable或者Serializable接口,那么clone和readObject方法都不可以直接或间接的调用可被子类覆盖的方法。因为这种方式同初始化一样,有可能造成在子类序列化之前就调用子类的方法,而失败。

6、解决问题的方法①将类设置为final②将构造函数设置为private③确保父类当中不会调用可覆盖方法,这样子类就可以安心的覆盖了。

7、消除可覆盖方法在父类当中的自用性(就是这个可覆盖方法可能在父类当中已经调用)。造成了继承的不确定性。

十八 接口优于抽象类

1、接口使得安全地增强类的功能成为可能。

2、通过对导出的每个重要接口都提供一个抽象的骨架实现类(抽象类),把接口和抽象类的有点结合起来。接口的作用仍然是定义类型,但是骨架实现类接管了所有与接口实现相关的工作。例如JDK当中的Collection Framework为每个重要的接口都提供了一个骨架实现,包括AbstractCollection、AbstractSet等。

3、模拟多重继承可以使用内部类实现。骨架类是为了继承而设计的,因此需要为继承设计文档。

4、接口一旦被公开发行,并且已被广泛实现,再想改变这个接口几乎是不可能的。因为你一旦在接口当中增加新方法,实现这些接口的子类就无法通过编译而报错,可以实现骨架类实现这些新方法以减小损失。但是,你不能确保所有的子类继承是骨架方法而不是接口,因此直接实现接口的那部分子类肯定会报错的。

5、推荐实现方式:接口ß骨架类(抽象类)ß实现类。

 

6、接口和抽象类这二者比较起来,从特征来说,最明显的区别在于抽象类允许包含某些方法的实现,但是接口是不允许的。但是在使用方面还有一个更为突出的区别,就是抽象类是通过被子类继承来使用的;而接口是通过被实现来使用的。而Java是一种单继承机制的语言,所以就限制了抽象类的使用。(一个类只有一个超类或叫父类)当一个类已经继承了一个超类时,现在你再想因为一些原因让它再继承一个抽象类是不可能的(当然也有变通办法,就是先让这个抽象类继承那个超类,再把子类的超类改为这个抽象类。即原超类和子类的关系从父子关系变为了爷孙关系,中间插了这个抽象类)。这时候就能体现出接口的优势了——一个类可以实现多个接口(其实这也是Java之所以大胆地放弃多继承的原因)。

7、再来罗列一下使用接口的其他好处吧:

1)已有的类可以很容易被更新,以实现新的接口。其实这就是上面说的,如果是使用抽象类就麻烦了。

2)接口是定义混合类型的理想选择。

3)接口使得我们可以构造出非层次结构的类型框架。层次结构用来描述组织机构这样的事物是非常合适的。但并不是所有事物都适用。

 

接口与抽象类各自的优缺:

  接口缺点:如果向一个java接口加入一个新的方法时,所有实现这个接口的类都得编写具体的实现。

  接口优点:一个类可以实现多个接口,接口可以让这个类不仅具有主类型的行为,而且具有其他的次要行为,比如 HashMap主要类型是Map,而Cloneable接口使它具有一个次要类型,这个类型说明它可以安全的克隆.

  抽象类的缺点:一个类只能由一个超类继承,所以抽象类作为类型定义工具的效能大打折扣。

  抽象类的优点:具体类可从抽象类自动得到这些方法的缺省实现。

  抽象类与接口的区别:

  1、抽象类可以包含部分方法的实现,这是抽象类优于接口的一个主要地方。

  2、由于Java的单继承,每个类只能从一个抽象类继承,但是每个类可以实现多个接口,使用接口还可以实现Mixin混合类型的类。接口可以继承多个接口,即接口间可以多重继承。

  3、将类抽取出通用部分作为接口容易,要作为抽象类则不太方便,因为这个类有可能已经继承自另一个类。

  4、可以将接口和抽象类一起使用。在集合框架体系中,顶层接口Collection定义了一些方法,同时又提供了几个抽象类 AbstractCollection、AbstractList、AbstractMap实现了一些方法,这样具体的集合实现类可以选择从抽象类中继承 或直接实现接口。

  抽象类和接口在语法和设计原则上的区别:

  1、类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类

  接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中

  2、接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;

  3、一个类一次可以实现若干个接口,但是只能扩展一个父类

  4、接口可以用于支持回调,而继承并不具备这个特点.

  5、抽象类不能被密封。

  6、抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的。(C#)

  7、(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。

  8、抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。

  9、好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。

  10、尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类, 就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如 asp.net 中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。

  11、如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法

  如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。

十九 接口只用于定义类型

1、当类实现接口时,接口就充当可以引用这个类的实例的类型。因此,类实现了接口,就表明客户端可以对这个类的实现实施某些动作。

2、常量接口:不包含任何方法,只包含static final域,每个域都导出一个常量。常量接口模式是对接口的不良使用。接口应该只被用来定义类型,它们不应该被用来导出常量。JDK当中的java.io.ObjectStreamConstants就是常量接口,这是个反面教材。实现常量接口,会导致把实现细节泄露到改类的导出API当中。

3、如果要导出常量,可以在相应的类当中实现public static final String …,同时也可以使用特定的枚举类型类实现。

二十 类层次优于标签类

1、标签类:一个类在内部表现为多种不同的事物。通过在构造函数输入参数的不同构建不同的实例。其内部使用枚举类型和switch case或者是if else 这样的语法对输入的参数进行判断而表现出不同的实现。标签类就是对类层次的一种简单效仿。

2、标签类过于冗长,容易出错,并且效率低下,而且违背了类的单一职责原则。可以使用接口或者抽象类的方式来替换标签类。在实际的使用当中根据不同的情况对抽象类赋予不同的实现,而不需要在类内部进行判断。

二十一 用函数对象标识策略

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

二十二 优先考虑静态成员类

1、嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为了它的外围类提供服务。

2、嵌套类有四种:静态成员类、非静态成员类、匿名类和局部类。除了第一种之外,其他三种都背成为内部类。

3、非静态成员类的每个实例都隐含着与外围类的一个外围实例相关联。而静态成员类则可以独立于外围类的实例而存在。非静态成员类的实例必须依赖于外围类的实例。

4、如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的生命中。因为如果是非静态成员类,每个实例都会包含一个额外的指向外围对象的引用。保存这份引用要消耗时间和空间,并且会导致外围实例在符合垃圾回收时却仍然得以保留。

5、匿名类在使用时被声明和实例化。匿名类和局部类的都不要太长,同时这两者都不允许有静态成员。

6、静态成员类遵循其他静态成员可访问性规则。

二十三 请不要在新代码中使用原生态类型

1、程序当中的错误,越早发现越好。而泛型的使用使得错误能够在编译时发现。

2、如果使用原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。

3、泛型有子类型化的规则,List<String>是原生态类型List的一个子类型,而不是参数化类型List<Object>的子类型。

4、无限制的通配符类型,如果要使用泛型,但不确定或者不关心实际的参数类型,就可以使用一个问号代替。例如,泛型Set<E>的无限制通配符类型为Set<?>。

通配符类型(List<?>)是安全的,原生态类型(List)是不安全的。

5、在类文字(List.class)中必须使用原生态类型。这个例外与instanceof操作符有关。

6、原生态类型只是为了与引入泛型之前的遗留代码进行兼容性和互用而运行继续使用的。

二十四 消除非受检查警告

1、要尽可能地消除每一个非受检警告。

2、如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下)可以用一个@SuppressWarning(“unchecked”)注解来禁止这条警告。

3、SuppressWarning注解可以用在任何粒度的级别中。但应该始终在尽可能小的范围中使用SuppressWarning注解。永远不要在整个类上使用SuppressWarning,这么做可能掩盖了重要的警告。

4、每当使用SuppressWarning注解时,都要添加一条注释,说明为什么这么做是安全的。

二十五 列表优先于数组

1、数组是协变的,如果Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型。相反泛型是不可变的,对于任意两个不同类型的Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>的超类型。

       List<String> str1 = new ArrayList<String>();
//     List<Object> str2 = new ArrayList<String>();//不可变的
       Object[] str3 = new String[]{};//协变的

2、利用数组我们在运行时发现错误,但是利用列表,我们可以在编译时发现错误。

3、泛型只在编译时强化他们的类型信息,而在运行时会丢弃(擦除)它们的元素类型信息。擦除使得使用泛型可以与没有使用泛型的代码随意进行互用。而数组是具体化的,数组在运行时知道并检查元素类型信息。因此泛型数组是非法的。

4、泛型数组是非法的,因为它不是类型安全的。

5、数组是协变的具体化的;泛型是不可变的且可以被擦除的。因此,数组提供了运行时的类型检查,但是没有在编译时进行类型安全检查。相反,泛型是在编译时进行类型安全检查,而在运行时对类型信息进行了擦除操作。一般来说,数组和泛型不能很好的混合使用。如果混用了,并且报错,第一反应就是用列表代替数组。

二十六 优先考虑泛型(类)

1、ArrayList<String>,HashMap<String,Object>等容器都是在数组之上实现的。

2、原始代码;修正后代码;再次修正代码

二十七 优先考虑泛型方法

1、声明类型参数的类型参数列表,出在方法的修饰符及其返回类型之间。如

Public static <E> Set<E>union(Set<E> s1, Set<E> s2);

2、泛型的静态工厂方法。

3、泛型方法的一个显著特性是,无需明确指定类型参数的值,不想调用泛型构造器的时候必须指定的。编译器通过检查方法参数的类型来计算类型参数的值。对于第Public static<E> Set<E> union(Set<E>s1, Set<E> s2);而言,编译器发现union的两个参数都是Set<String>类型,因此知道类型参数E必须为String。这个过程称作类型推导

4、递归类型限制:通过某个包含该类型参数本身的表达式来显示类型参数。例如:public static <Textends Comparable<T>> T max(List<T> list){}

二十八 利用有限制通配符来提升API的灵活性

1、有限制的通配符类型:如Iterable<? Extends E>:E的某个子类型的Iterable接口。同时Collection<?Super E>:E的某种超类的集合

2、PECS原则:producer-extends,consumer-super。如果参数化类型标识一个T生产者,就使用<? Extends T>;如果它标识一个T消费者就使用<? Super T>。

当其既可以消费有可以生产时使用E即可。

  • 如果你想从一个数据类型里获取数据,使用 ? extends 通配符
  • 如果你想把对象写入一个数据结构里,使用 ? super 通配符
  • 如果你既想存,又想取,那就别用通配符。

具体参考http://developer.51cto.com/art/201106/266852_1.htm

http://blog.csdn.net/jiafu1115/article/details/6624254

xtends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类

super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object

①   向上造型一个泛型对象的引用

例如,假设我们有很多箱子,每个箱子里都装有不同的水果,我们需要找到一种方法能够通用的处理任何一箱水果。更通俗的说法,A是B的子类型,我们需要找到一种方法能够将C<A>类型的实例赋给一个C<B>类型的声明。

②向下造型一个泛型对象的引用

现在我来介绍另外一种通配符:? super。如果类型B是类型A的超类型(父类型),那么C<B> 是 C<? super A> 的子类型:

二十九 优先考虑类型安全的异构容器

1、单元素容器如ThreadLocal和AtomicReference。

2、?表示任意类型(无限制的通配符类型),E表示一个确定类型。这也是为什么会出现Class<?>的原因了,因为泛型是类型安全的,而Class<?>使用了泛型,虽然其表示任意类型。

3、具体代码

三十 用enum代替int常量 关于枚举可以访问http://blog.csdn.net/chinakite

1、JDK1.5当中引入了枚举类型(enmu type)和注解类型(annotation type)

2、枚举类型是指由一组固定的常量组成合法值的类型,java的枚举类型是功能十分齐全的类,java的枚举本质上是int值。

3、枚举类型通过共有的静态final域为卖给枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正的final。客户端既不能创建枚举类型,也不能对其进行扩展,枚举类型是可控的,类型安全的。单例模式实质上是单元素的枚举。

4、一般来说,枚举会优先使用comparable而非int常量。与int常量相比,枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间成本。除了受资源约束的设备,如手机等,在实践当中不必在意这个问题。每当需要一组固定常量的时候,就适合使用枚举类型。

5、enmu.values()获得这个枚举类型的所有实例化对象(即其有几个对象)。

6、枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为。

7、枚举类型可以看做是一个类,而其中的实例就是那些可见的枚举常量,每个常量标识一个实例。当然你可以再这个枚举当中添加公有,私有属性方法等,但是在方法为abstract时,其不能为private类型。这种要求同类当中的是一致的。因为如果定义为private,那么子类是无法覆盖的,但是其又是abstract类型,这就造成了前后的矛盾,因此不管是class还是enum都不允许private类型的abstract函数存在。

Enum的作用

1.增加程序的可读性和可维护性,比如一些参数只能取那么几个值,而从参数类型上又看不出来有哪些值可以取。

2.可以保证单例,且比较时候可以用”==”来替换equals(同时可以利用枚举来实现单例)。

3.可以用switch,这个特点是用enum的一大原因

4.复杂的应用里,有很多字典表,这些字典表必须体现出其词性才有意义

5.类型安全,  不会出现非法参数

6.Enum 本身就是个普通的class,可以有很多自定义方法用来实现不同的功能

Enum的特性

1.它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。    

2.所有枚举值都是public  static final的,且成员变量的定义要放在其后。    

3.Enum默认实现了java.lang.Comparable接口。    

4.Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.    

5.Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。    

6.Enum提供了values方法,这个方法使你能够方便的遍历所有的枚举值。    

7.Enum的oridinal的方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定。

Enum需要注意的地方

1. 枚举类型不能使用extends关键字,但是可以使用implements关键字。这样我们可以把不同枚举类型共有的行为提取到接口中,来规范枚举类型的行为。

2. 枚举类型的自定义构造函数并不能覆盖默认执行的构造函数,它会跟在默认构造函数之后执行。

3. 枚举类型的自定义构造函数必须是私有的。你不需要在构造函数上添加private关键字,编译器会为我们代劳的。

4. 枚举类型中枚举常量的定义必须放在最上面,其后才能是变量和方法的定义。

具体可以参考:http://www.cnblogs.com/strayromeo/archive/2010/07/01/1517789.html

三十一 用实例域代替序数

1、Enum的oridinal的方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定。

2、永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域当中。最好完全避免使用oridinal方法。

public enum Ensemble {
    SOLO(1), DUET(2),TRIO(3), QUARTET(4),QUINTET(5), SEXTET(6),SEPTET(7), OCTET(
           8), DOUBLE_QUARTET(8),NONET(9), DECTET(10),TRIPLE_QUARTET(12);
    private final int numberOfMusicians;
    Ensemble(int size) {
       this.numberOfMusicians = size;
    }
    public int numberOfMusicians() {
       returnnumberOfMusicians;
    }
}

三十二 用EnumSet代替位域

1、EnumSe是一个与枚举类型一起使用的专用 Set 实现。枚举set中所有元素都必须来自单个枚举类型(即必须是同类型,且该类型是Enum的子类)。

     枚举类型在创建 set 时显式或隐式地指定。枚举 set 在内部表示为位向量。 此表示形式非常紧凑且高效。此类的空间和时间性能应该很好,足以用作传统上基于 int 的“位标志”的替换形式,具有高品质、类型安全的优势。

注意1:不允许使用 null 元素。试图插入 null 元素将抛出 NullPointerException。但是,试图测试是否出现 null 元素或移除 null 元素将不会抛出异常。

注意2:EnumSet是不同步的。不是线程安全的。

注意3:EnumSet的本质就为枚举类型定制的一个Set,且枚举set中所有元素都必须来自单个枚举类型。

注意4:关于EnumSet的存储,文档中提到是这样的。 “枚举 set 在内部表示为位向量。”

2、此博客说的很清楚了。http://blog.csdn.net/hudashi/article/details/6943843

三十三 EnumMap代替序数索引

1、推荐博客http://blog.csdn.net/chinakite/article/details/3244934

2、EnumMap内部使用了序数索引的数组,因此非常之快,其集Map的丰富功能和类型安全与数组的快速于一身。

3、EnumMap<……,EnumMap<……>>其内部使用了数组的数组,因此效率还是很高的。

4、最好不要使用序数(Enum.ordinal)来索引数组,而要使用EnumMap。

三十四 用接口模拟可伸缩的枚举http://helloyesyes.iteye.com/blog/1186575

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

2、所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。

三十五 注解优于命名模式

1、JDK 1.5发行版本之前,一般使用命名模式表明有些程序元素需要通过某种工具或者框架进行特殊处理。

2、@Retention(RetentionPolicy.RUNTIME) 元注解,表明Test注解在运行时保留。如果没有保留测试工具就无法知道Test注解。

       @Target(ElementType.METHOD)  元注解,表明Test注解只在方法声明中才合法。它不能运用到类声明、域声明或者其他程序元素上。

       Public@interface Test{}

Use only on parameterless static methods:只能作用在无参的静态方法。但是在实际运用当中方法必须是无参的,同时不是静态的。JDK版本问题???

2、注解不会改变注解代码的语义。

三十六 坚持使用Override注解(可以防止大量的错误)

1、Override注解只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明。加上Override注解之后超类的方法和子类的方法头必须一致。

2、应该在想要覆盖超类声明的每个方法声明中使用Override注解。以方式是覆盖的形式而不是重载。

3、现代的IDE具有自动检查功能,称作代码检查。如果启用了改功能,同时一个方法没有Override注解,却覆盖了超类的方法时,IDE会产生一条警告。

三十七 用标记接口定义类型(没看懂)

1、标记接口(marker interface)是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。例如:Serializable接口。

2、标记接口定义的类型是由标记类的实例实现的;标记注解则没有定义这样的类型。

3、标记接口和标记注解都各有用处。如果想定义一个任何新方法都不会与之关联的类型,标记接口就是最好的选择。如果想要标记程序元素而非类和接口,考虑未来可能要给标记添加更多信息,或者标记要适合于已经广泛使用了注解类型的框架,那么标记注解是正确选择。

三十八 检查参数的有效性

1、应该在文档中清楚的知名所有对参数的限制,并且在方法的开头处检查参数,以强制施加这些限制。这是“应该在发生错误之后尽快检测出错误”这一普遍原则的一个具体情形。

2、对于公有的方法,要用javadoc的@throws标签在文档中说明违反参数值限制时会抛出的异常。这样的异常通常为IllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException

3、对于未被导出的方法,即非公有的方法,应该控制这个方法将在什么情况下被调用,非公有的方法通常使用断言(assertion)来检查参数。深入解析Java的新特性assertionJava陷阱之assert关键字

4、异常转译技术,将计算过程中抛出的异常转换为正确的异常。

三十九 必要时进行保护性拷贝(没怎么看懂)

1、java是一门安全的语言,这意味着,它对于缓冲区溢出,数组越界,非法指针以及其他的内存破坏都是自动免疫的。这对于那些“把内存当做一个巨大的数组来看待”的语言来说,这是不可能的。

2、对于构造器的每个可变参数进行保护性拷贝是必要的。

3、保护性拷贝是在检查参数有效性之前进行的,并且有效性检查时针对拷贝之后的对象,而不是针对原始对象。

4、对于参数类型可以被不可信任方子类化参数,请不要使用clone方法进行保护性拷贝。

5、如果类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性的拷贝这些组件。

四十 谨慎设计方法签名

1、谨慎的选择方法名称。①易于理解,同时命名风格一致②大众认可的名称

2、不要过于最求提供便利的方法。只有当一项操作被经常使用到的时候,才考虑为它提供快捷方法。如果不能确定,还是不提供快捷方法为好。

3、避免过长的参数列表。参数是四个或者更少为最佳选择。同类型的长参数序列格外有害。

4、有三种方法可以缩短过长的参数列表。①把方法分解成多个方法,提高API的“功能-重量”比。②创建辅助类用来保存参数的分组。如果一个频繁出现的参数序列被看做是代表了摸个独特的实体,则建议使用这种方法。③从对象的构建到方法调用都采用Builder(创建者模式)。

5、对于参数类型,要优先使用接口而不是类。例如使用Map而不是HashMap作为接口的参数。

6、对于boolean参数,要优先使用两个元素的枚举类型。它使得代码更容易阅读和编写。

四十一 慎用重载

1.1方法重载:如果有两个方法的方法名相同,但参数不一致,哪么可以说一个方法是另一个方法的重载。

  • 方法名相同
  • 方法的参数类型,个数顺序至少有一项不同
  • 方法的返回类型可以不相同
  • 方法的修饰符可以不相同
  • main方法也可以被重载

1.2方法覆盖:如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。

  • 子类的方法名称返回类型及参数签名 必须与父类的一致
  • 子类方法不能缩小父类方法的访问权限
  •  子类方法不能抛出比父类方法更多的异常
  • 方法覆盖只存在于子类和父类之间,同一个类中只能重载
  • 父类的静态方法不能被子类覆盖为非静态方法
  • 子类可以定义于父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法(满足覆盖约束),而且Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。
  • 父类的非静态方法不能被子类覆盖为静态方法
  • 父类的私有方法不能被子类覆盖
  • 父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖)(P169)
  • 父类的非抽象方法可以被覆盖为抽象方法

1.3、Super关键字:super和this关键字都可以用来覆盖Java语言的默认作用域,使被屏蔽的方法或变量变为可见(三种情况下的不可见P171)。

  • 父类的成员变量和方法为private使用super访问编译出错
  • 在类的构造方法种,通过super语句调用这个类的父类的构造方法
  • 在子类种访问父类的被屏蔽的方法和属性
  • 只能在构造方法或实例方法内使用super关键字,而在静态方法和静态代码块内不能使用super

1.4、多态:

  • 对于一个引用类型的变量,Java编译器按照它的声明的类型来处理
  • 对于一个引用类型的变量,运行时Java虚拟机按照它的实际引用的对象来处理
  • 运行时环境中,通过引用类型变量来访问所引用对象的方法和属性时,Java虚拟机采用以下绑定规则

           1)实例方法与引用变量实际引用的对象的方法绑定,属于动态绑定

           2)静态方法与引用变量所声明的类型的方法绑定,属于静态绑定

           3)成员变量(包括静态和实例变量)与引用变量所声明的类型的成员变量绑定,属于静态绑定 

1.5、继承的利弊和使用原则:

  • 继承数的层次不可太多
  • 继承数的上层为抽象层

            (1)定义了下层子类都用友的相同属性和方法,并且尽可能默认实现,从而提高重用性

            (2)代表系统的接口,描述系统所能提供的服务

  • 继承关系最大的弱点:打破封装
  • 精心设计专门用于被继承的类

            (1)对这些类必须提供良好的文档说明

            (2)尽可能的封装父类的实现细节,把代表时间细节的属性和方法定义为private类型

            (3)如果某些实现细节必须被子类访问,定义为protected类型

            (4)把不允许子类覆盖的方法定义为final类型

            (5)父类的构造方法不允许调用可被子类覆盖的方法

            (6)如果某些类不是专门为了继承而设计,那么随意继承它是不安全的

2、对于重载方法的选择是在编译时进行的,例如这段代码

public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return"Set";
    }
    public static String classify(List<?> lst) {
        return"List";
    }
    public static String classify(Collection<?> c) {
        return"Unknown Collection";
    }
    public static void main(String[] args) {
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<String,String>().values()
        };
 
        for (Collection<?> c : collections)
            System.out.println(classify(c));
    }
}

For循环当中的三次迭代参数在编译时类型都是相同的:Collection<?>,而在每次运行时类型都是相同的,但这并不影响对重载方法的选择。

3、对于重载方法的选择是静态的(编译时),而对于被覆盖的方法的选择则是动态的(运行时)。覆盖机制是规范,而重载机制是例外。应避免混乱的使用重载机制。

4、选择被覆盖的方法的正确版本是在运行时进行的,选择的依据是被调用的方法所在的对象运行时的类型。在这里重新说明一下,当一个子类包含的方法声明与其祖先包含的方法声明具有同样的签名时,方法就被覆盖了。最为具体的那个覆盖版本总会得到执行。

5、对于重载来说,安全而保守的策略是,永远不要到处两个具有相同参数数目的重载方法。

6、对于构造函数而言,没有必要担心覆盖,因为其根部不可能被覆盖。

7、一般情况下,对于多个具有相同参数数目的方法来说,应该尽量避免重载方法。在某些情况下,特别是设计构造器的时候,要遵循这条建议也学是不可能的。在这种情况下,至少应该避免这样的情形:同一组参数只需要经过类型转换就可以被传递给不同的重载方法。如果不能避免这种情形,就应该保证:当传递同样的参数时,所重载方法的行为必须一致。

四十二 尽量避免可变参数

1、Java1.5增加了新特性:可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。注意:可变参数必须位于最后一项。当可变参数个数多余一个时,必将有一个不是最后一项,所以只支持有一个可变参数。因为参数个数不定,所以当其后边还有相同类型参数时,java无法区分传入的参数属于前一个可变参数还是后边的参数,所以只能让可变参数位于最后一项

可变参数的特点:

(1)只能出现在参数列表的最后;

(2)...位于变量类型和变量名之间,前后有无空格都可以;

(3)调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中一数组的形式访问可变参数。

2、可变参数方法接受0个或者多个指定类型的参数。可变参数机制通过先创建一个数组,数组的大小为在调整位置所传递的参数数量,然后将参数数值传到数组中,最后将那个数组传递给方法。

3、可变参数是为了printf而设计的。printf和反射机制都从可变参数中极大的受益。

4、不必改造具有final数组参数的每个方法;只当确实是在数量不定的值上执行时菜使用可变参数。

5、在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,它是它们不应该被过度滥用。如果使用不当,会产生混乱的结果。

四十三 返回零长度的数组或者集合,而不是null

1、返回null会使得每次调用该方法时都需要判断null这种曲折的处理方式。客户端程序员,肯能因为没有专门处理这种返回值而发生错误。

2、有人认为null返回值比零长度数组更好,因为其避免了分配数组所需要的开销。但是这种观点是站不住脚的,原因有一下两点。第一,在这个级别上担心性能问题是不明智的,除非分析表明这个方法正是造成性能问题的真正源头。第二,对于不返回任何元素的调用,每次都返回同一个零长度的数组都是有可能的,因为零长度数组是不可变的,而不可变对象有可能被自由的共享。(待商榷)

3、简而言之,返回对于类型为数组或者集合的方法可以返回一个零长度的数组或者集合没有理由返回null。即优先返回零长度数组或集合而不是null。

四十四 为所有导出的API元素编写文档注释

1、如果想要使一个API真正可用,就必须为其编写文档。Java语言环境提供了一种被称为Javadoc的实用工具,从而使这项任务变得很容易,Javadoc利用特殊格式的文档注释,根据源码自动产生API文档。

2、Howto Write Doc Comments for the Javadoc Tool这是Oracle公司关于文档注释的官方指导。

3、为了正确地编写API文档,必须在每个被导出的类、接口、构造器、和域声明之前增加一个文档注释。为了编写出可维护的代码,还应该为那些没有被导出的类、接口、构造器、方法和域编写文档注释。

4、方法的文档注释应该简洁地描述出它和客户端之间的约定。文档注释应该列举出这个方法所有前提条件和后置条件。前提条件是指为了使客户能够调用这个方法,而必须要满足的条件;所谓后置条件是指在调用成功完成之后,哪些条件必须满足。一般情况下,前提条件是由@throw标签对未受检查的异常和参数@param标记所隐含描述的。除了前提条件和后置条件之外,还应有副作用,所谓副作用是指系统状态中可以观察到的变化,它不是为了获得后置条件而明确要求的变化。最后,文档注释也应该描述类或者方法的线程安全性。

5、方法的文档注释每个参数都有一个@param标签,以及一个@return标签,以及@throws标签。

6、javadoc工具会把文档注释翻译成HTML,文档注释中包含的任意HTML元素都会出现在结果HTML文档中。

7、使用@doc标签,它有两个作用:①使得该代码以代码字体呈现②限制HTML标记和嵌套的javadoc标签在代码片段中进行处理(避免了转译诸如<这样的HTML标签)。对于多行代码的情况下可以使用<pre>{@doc^^^^^^}</pre>

8、@literal标签,与@doc标签的作用②类似即对出现在其内部的HTML标签不进行转译操作。

9、文档注释在源代码和产生的文档中都应该是容易阅读的。如果无法让两者都易读,产生的文档的可读性要优先于源代码的可读性。

10、文档的第一句话应为注释所描述元素的概要描述。为了避免混淆,同一个类或者接口中的两个成员或者构造器不应该具有同样的概要描述。特别是在重载的情况下,更不要有同样的描述。

11、党委泛型或者方法编写文档时,确保要在文档中说明所有的类型参数。

12、为枚举类型编写文档时,要确保在文档中说明常量。

四十五 将局部变量的作用域最小化

1、将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低错误的可能性。

2、要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明。如果变量使用之前,进行声明对于读者来说,只会分散他们的注意力,而且等到用到该变量的时候,读者可能已经记不起该变量的类型或初始值了。

3、局部变量的作用域是从它被声明点开始扩展,一直到外围块(函数)得结束出。

4、几乎每个局部变量的声明都应该包含一个初始化表达式。如果变量的值必须在try块内部外部都使用,则必须在try块外部声明。

5、循环提供了特殊的机会来将变量的作用域最小化。因此,如果在循环终止之后不再需要循环变量的内容,for循环就优于while循环。优势①可以大大降低“剪切-粘贴”错误②更简短,从而增强了可读性。

6、如果再for循环当中涉及方法调用,就应该使用

for(int i=0,len=list.size();i<len;i++){}方式操作,这样减少每次对list.size()的计算,同时减少一段计算len的代码(如果把计算len放在外部的话)。

7、最后一种将局部变量最小化的方法就是使得方法小而集中。(对于大的方法改分隔开的地方就要分隔开)

四十六 for-each循环优先于传统的for循环

1、for-each循环不会有性能损失,而且在某些情况下,还比for循环更有性能优势,因为它对数组索引的边界值只计算一次。

2、for-each循环不仅可以遍历集合和数组,而且可以遍历任何实现了Iterable接口的对象。

3、for-each循环在简洁性和预防Bug方面有着传统for循环无法比拟的优势,而且没有性能损失。

4、以下三种情况无法使用for-each循环实现:①过滤——如果需要遍历集合,并删除选定的元素,就需要使用显示的迭代器,以便调用remove方法。

②转换——如果需要遍历列表或者数组,并取代它部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值

③平行迭代——如果需要并行的遍历多个集合,就需要显式地控制迭代器或索引变量,以便所有迭代器或者索引变量都可以得到同步前移。

四十七 了解和使用类库

1、伪随机数生成器、数论、2的求补算法。Random.nextInt(int)产生随机数。

2、通过使用标准类库,可以充分利用这些编写便准类库的专拣的知识,以及在你之前的其他人的使用经验。

3、java-feat,了解java.lang,java.util,java.io,java.util.concurrent多线程

4、总而言之,不要重新发明轮子!!!一般而言,类库的代码要比你自己编写的代更好一些,并且会随着时间的推移而不断改进。

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

1、float和double类型主要是为了科学计算和工程计算而设计的。它们并没有提供完全精确的结果。floatdouble类型尤其不适合货币计算。

2、使用BigDecimal类型代替double。但是BigDecimal有两个缺点:①与基本运算类型相比,操作不方便。②效率低,速度很慢。

四十九 基本类型优先于装箱基本类型

1、每个基本类型都有一个对应的引用类型,称作装箱基本类型。例如int、double和boolean的装箱类型是Integer、Double和Boolean(要理解为类,而非基本类型)。

2、Java1.5当中增加了自动装箱和自动拆箱。

3、基本类型和装箱基本类型之间有三个主要区别:①基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。换句话说,两个装箱基本类型可以具有相同的值和不同的同一性。②基本类型只有功能完备的值,而每个装箱基本类型除了它对应的基本类所有功能值之外,还有非功能值:null。③基本类型通常比装箱基本类型更节省时间和空间

4、对装箱基本类型进行<、>比较操作,首先是进行了拆箱操作,而后笔记基本类型,而对装箱基本类型进行==操作,是比较的装箱基本类型的堆地址,而非堆中的内容(基本类型)。

5、当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,因此如果对于一个没有初始化的装箱基本类型进行拆箱操作,毫无疑问会得到一个NullPointerException异常。而且还会因为频繁的自动装箱和拆箱而严重影响效率。

6、装箱基本数据类型的用处①作为集合中的元素、键和值。Java中不允许在集合当中使用基本数据类型。②在参数化类型中,必须使用装箱基本数据类型作为参数。如Map<String,Integer>。

五十 如果其他类型更合适,则尽量避免使用字符串

1、一些不应该使用字符串的情形①字符串不适合代替其他的值类型。当从网络、文件或者键盘当中获取的字符串应该根据其本质上的意义赋予其真正的类型,如int,boolean或者其他自定义的类型(类)。做到如ORMPPING效果。②字符串不适合代替枚举类型。③字符串不适合代替聚集类型。如果一个实体有多个组件,用一个字符串来表示这个实体通常是很不恰当的。④字符串也不适合代替能力表。如很早之前作为ThreadLocal的键值。

2、如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,就应该避免用字符串来表示对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值