读Effective Java 备忘

 
实现singleton的两种方法:
1.Public class Elvis{
       Public static final Elvis INSTANCE=new Elvis();
       Private Elvis(){
              …
       }
       …
}
2.public class Elvis{
       Private static final Elvis INSTANCE=new Elvis();
       Private Elvis(){
              …
       }
       Public static Elvis getInstance(){
              Return INSTANCE;
       }
       …
}
第二种方法是可以被修改的,比如说,为每个调用该方法的线程返回一个惟一的实例
 
如果一个类只是个工具类,不想被实例化,最好的方法是建立一个私有的构造方法,而不是想其声明为抽象类
 
如果一个类既有静态的工厂方法又有构造方法,则使用前者要明显好于后者。如Boolean.valueOf(String) 要好于Boolean(String) ,后者在每次被调用的时候都会创建一个新的对象,而静态工厂方法则会重用已有的对象;类似的情形更常发生在String身上
 
要想在扩展一个可实例化的类的同时,既要增加新的特性,同时还要保留equals约定……复合优先于继承……在Java平台库中,有一些类是可实例化类的子类,并且加入了新的特征。例如,java.sql.Timestamp对java.util.Date进行子类化,并且增加了nanoseconds域。Timestamp的equals实现确实违反了对称性,如果Timestamp和Date对象被用于同一个集合中,或者以其他方式被混合在一起,则会出现不正确的行为。Timestamp类有一个否认声明,告诫程序员不要混合使用Date和Timestamp对象……Timestamp类是一个反常的类,不值得仿效。
……你可以在一个抽象类的子类中增加新的特性而不会违反equals约定……只要不可能创建超类的实例……
 
“==” 比较的是两端是否是同一个对象(在堆内存中指的是同一块同存区域)
 
如果elements域是final的,则这种方案并不能正常工作,因为clone方法是被禁止给elements域赋一个新值的……clone结构与指向可变对象的final域的正常用法是不兼容的……为了使一个类成为可克隆的,可能有必要从某些域中去掉final修饰符。
 
Java平台库中的所有值类都实现了Comparable。如果你正在编写一个值类,它具有非常明显的内在排序关系,那么你几乎总是应该考虑实现这个接口。
 
考虑BigDecimal类,它的compareTo方法与equals不一致。如果你创建了一个HashSet并且加入一个new BigDecimal(“1.0”) 和一个new BigDecimal(“1.00”) ,则这个集合将包含两个元素,因为两个新加入到集合中的BigDecimal实例,通过equals方法来比较是不相的。然而如果你使用TreeSet而不是HashSet来执行同样的过程,则集合中只包含一个元素,因为这两个BigDecimal实例通过compareTo方法进行比较是相等的。
 
Item12:
Classes are permitted to expose constants via public static final fields…….It is critical that these fields contain either primitive values or references to immutable objects .A final field containing a rdference to a mutable object has all the disadvantages of a nonfinal field
       Note that a nonzero-length array is always mutable,so it is nearly always wrong to have public static final array field……This is a frequent source of security holes
 
Item13:Favor immutability
一个immutable类一旦实例化就不可以更改。其对象的所有状态都在创建时即已确立,且在生命期内不可变更。Java类库提供了很多immutable类,包括String,基本类型的包装类,BigInteger和BigDecimal。这样做基于以下理由:immutable类较之mutable类易于设计、扩展(后文讲不提倡扩展,存疑)。它们有着更小出错的可能以及更高的安全性。
       设计一个immutable类,可遵循以下五个规则:
1.       不要提供任何对类对象进行修改的方法
2.       确保所有的方法都不能被重载。这会防止粗心或恶意地继承使不变性受到损害。防止方法被重载通常可以将类声明为final,后面我们还将讨论三种其它的方法
3.       所有的置性定义成final (省去一段不会翻,跟线程安全似乎有关)
4.       所有方法定义为private。防止客户直接修改域。immute类拥有包含基本类型或对immtable对象的引用的public final域,在技术上是许可的,但是不建议这样用,因为它杜绝了(不会翻)
5.       确保不会向任何mutable 组件提供额外的接口。如果你的类包含一些指向mutable对象引用的域,确保客户不会得到这些对象的引用。(一小段不会翻)
 
许多前面举的类的例子都是immutable的。比如说Item8里的PhoneNumber,提供了每个域的访问方法(accessor),但是没有mutator。下面是一个稍微复杂点的例子:
(略)
这是一个代表复数的类(略一部分)注意其数学方法是怎么创建并返回一个新的Complex对象而不是对原对象进行修改。(略一部分)
 
immutable 对象天生就是线程安全的,它们不需要synchronization。它们不会被并行访问的多个线程所破坏。(略)事实上,一个线程不可能看到另一个线程对于immutable对象所做的任何操作。 因此 immutable 对象可以自由地被共享。immutable类的这一优点可以鼓励客户允许的情况下尽量去重用已存在的实例。实现这一目的的一个简单的途径是为经常要用的值提供public static final量。比如对Complex类来说可以提供如下量:
 
public static final Complex ZERO=new Complex(0,0);
public static final Complex ONE=new Complex(1,0);
public static final Complex I=new Complex(0,1);
       (略若干) 一个immutable类可以提供静态工厂方法,把经常要用到的对象cache起来,在接到请求时不用创建新的对象。BigInteger和Boolean类都提供了静态工厂方法。
       immutable对象可以随心所欲地共享,一个好处就是不用编写defensive copy(Item24)。事实上所有的copy和原始对象都是一样的。因此你不必也不应该为immutable类提供clone方法或copy constructor(Item10)。这在Java platform推出的早期是很难被人们理解的,所以String含有copy constructor,但是应该尽量少使用它。
 
       不仅可以共享immutable 对象,还可以共享其成员。比如BigInteger类采用sign-magnitude(符号-量,译者) 表示数。sign用int表示,magnitude由int数组表示。negate方法返回一个新的BigInteger对象,magnitude不变,sign相反。这个过程不需要拷贝原来的数组,只要让新的对象指向原来的数组。
       immutable 对象为其它objects 提供了great building blocks 不论是mutable还是immutable。持有一个不变的complex对象将变得大为简单,(略一句)。这个原则的一个例子是immutable对象非常胜任作为map的key或set的原素;只要它们在map或set里面了,就永远不用担心它们的值会发生变化,这种变化将破坏map或set中的不变量。
       immutable 类唯一的缺点是其对于每个不同的值都要创建一个完全不同的新对象。这会带来一定的开销,尤其是当它们很大的时候。(略若干)
      
       我们来讨论一下immutable的一些其它设计。回想一下,为了确保不变性,类不能允许其方法被重载。除了令class为final,还有其它两种方式。一是不让类本身还是令其所有方法为final。这个措施的唯一好处是允许程序员继承,并在原来的基础上增加新的方法。这样做的效果同在一个独立的,不可实例化的工具类中提供一些新的static方法是一样的,因此这种做法并不提倡。
       另一种代替令immutable为final的做法是让其所有的constructor为private(私有,译者)或package-private(包访问权,译者),并添加公有的静态工厂来取代公共的构造方法。下面是例子(略一大段)
 
       在编写BigInteger和BigDeimal的时候,immutable类必须final这个观念还没有被大多数人理解,因此这两个类的所有方法都可以被重载(!,译者)。不幸的是在下一版本推出之前都不能够改变。如果你写了一个类,其安全性取决于从不受信任的客户端传来的BigInteger或BigDecimal参数,你必须检查此参数是真正的BigInteger或BigDecimal还是被重载过的子类。如果是后者,你必须假设它有可是mutable的,并defensively copy它(Item24):
       Public void foo(BigIteger b){
              If(b.getClass()!=BigInteger.class)
              B=new BigInteger(b.toByteArray());
              ……
       }
       我们在开头列举的设计immutable类的规则认为任何方法不得修改对象以及所有的域必须为final。事实上这个要求在实际应用中可以适度放宽以提升性能。任何方法不能对对象的状态进行外部可见的修改。很多immutable类都有一个或多个nonfinal的redundant域用来在第一次被请求时缓存一些昂贵计算的结果。如果同样的计算在今后被请求,就可以直接返回缓存的结果。
       例如PhoneNumber的hashCode方法(Item8),在第一次调用时计算出hash code,将其缓存起来准备再次需要时使用。这种技术是lazy initialization(Item48)的一个经典例子,它也在String中被采用。由于hash值不管计算多少次都不会改变,因此根本不需要synchronization。下面是返回缓存结果,lazily initialized的一个immutable类的典型用法:
       Private volatile Foo cachedFooVal=UNLIKELY_FOO_VALUE;
       Public Foo foo(){
              Int result=cachedFooVal;
              If(result==UNLIKEYLY_FOO_VALUE)
                     Result=cachedFooVal=fooValue();
              Return result;
}
//private helper function to calculate our foo value
Private Foo fooVal(){……}
 
       涉及到serializability时,给予一个忠告。如果你的immutable类实现Serializable接口,并含有一个多个引用mutable对象的域,你必须提供详尽的readObject或readResolve方法,即使默认的serialized形式也可以被接受。默认的readObject方法允许攻击者利用你的immutable类创建出mutable的实例(不理解,译者)。这个问题在Item56中讨论。
 
(略若干)除非有很好的理由,否则类应该设计成immutable。immutable类有很多好处,它唯一的缺点就是在某些特定环境下存在潜在的性能问题。那些小的值对象,比如PhoneNumber和Complex应该被实现为immutable。(Java platform library里有一些类,比如java.util.Date和java.awt.Point,本应该实现为immutable,实际上没有)稍大点的对象如String和BigInteger,经过认真设计,也为immutable也是很好的。必要的时候,你要为你的immutable类提供一个公共的mutable companion class 。
 
对有些类来说,immutability并不实用,包括“过程类”如Thread和TimerTask。 即使一个类不能被设计成 immutable ,你也要尽量减少其可变的可能性。(略若干)因此构造方法要为对象的所有不变量提供完全的初始化,而不应该把部分初始化任务交给其它方法。除非有特别好的理由,否则不可以提供一个public的和构造方法完全独立的初始化方法。类似地,不应该提供一个reinitialize方法(略若干)。
 
TimerTask类可以解释这些原则。它是mutable的,但其状态空间非常之小。可以创建一个实例,schedule it for execution,并且选择性地将其取消。一旦temer task对象运行结束或被cancel,不可以reschedule it。
(略一段,完)
      
      
      
 
 
Item14:用组合而不是继承
It is safe to use inheritance within a package,where the subclass and the superclass implementation are under the control of the same programmers.It is also safe to use inheritance when extending classes specifically designed and documented for extension(Item15).Inheriting from ordinary concrete classes across package boundaries,however,is dangerous. (……)
 
       与方法invocation 不同,inheritance 破坏了encapsulation。换句话说,子类的行为正确与否取决于父类的implementation detail。父类的implementation可能会在后续的版本中发生变化,如果这样的话,即使子类的代码本身没有问题,也很可能不能正常工作。(……)
(下面给出了一个例子class InstrumentedHashSet extends HashSet,挺有意思,可以看一下)
 
这个类看起来没什么问题,但是它不能正确工作。如果我们创建一个实例,利用addAll方法向其中添加3个元素:
(代码)
我们期望getAddCount方法返回3,但是实际上返回了6. 哪里出问题了呢?原因是内在的,HashSet的addAll方法建立在add方法之上,尽管HashSet是合理的,但是没有把这个implementation detail写在文档上。InstrumentedHashSet的addAll方法先使addCount加3,然后通过super.addAll调用HashSet的addAll。这就为每一个元素依次调用了InstrumentedHashSet里被重载的add方法。每次调用都使addCount增加1(……)
我们可以修改子类,去掉重载的addAll方法。尽管结果可以工作,但是其正确的行为建立在HashSet的addAll方法与add方法的依赖关系之上。这种“self-use” 作为一个implementation detail,不能确保在java平台到处都存在,并且不被后来的版本所改变。因此,InstrumentedHashSet类是脆弱的。
 
       重载addAll方法较之于迭代指定的collection,对每个元素调用add方法似乎稍微好些。这可以确保类的正确行为,不管HashSet的addAll方法是否建立在add方法之上。因为HashSet本身的addAll方法不会再被调用。然而这样做并不能解决所有的问题。(……)
 
       子类是脆弱的另一个原因在于其父类在后续版本中可能会要求增加新的方法。设想一个程序,它的安全性取决于将所有的元素插入满足特定标准的collection中。这个可以由继承collection,重载其中每个能够增添元素的方法来保证所添加的元素都符合特定的条件。这个在父类的后续版本增加了新的添加元素方法之前是可以正常工作的。但是之后,在子类中仅仅通过调用这个新方法,插入非法的元素就变成可能。(……)
 
       上面的问题都由方法重载而来。你也许会认为继承一个类而仅仅增添新的方法而不重载已有的方法就是安全的。这种方法也有风险。如果一个父类在升级版本中增添了一个新方法,不幸的是你之前已经在子类中定义了同这个方法具有同样signature和不同返回值的方法,你的子类将不能通过编译。如果你的子类方法同父类新添的方法具有完全相同的signature,那就意味着你实际上重载了它。(……)
      
       幸运的是,有一种方法可以防止上面提到的所有问题。(……)
 
       封装类几乎没有明显的缺点。有一点要警醒的是封装类不适合用于callback framework(……)。因为被封装的类不知道它的封装者类,它会把自身的引用传给自己(this)(此处不是很明白,译者)。(后文略)
 
 
Item15:
Constructors must not invoke overridable methods ,directly or indirectly。(……) 父类的构造方法要在子类的构造方法运行之前运行,因此在子类中重载的方法会在子类的构造方法运行之前被调用(在父类的构造方运行时已得到调用,译者)。如果这个重载方法需要一些子类构造方法提供的条件,那么这个方法就不会得到期望的结果。
 
设计一个可被扩展的类,如果实现Cloneable和Serializable接口,就会给继承带来额外的麻烦,详情见P68
 
最好的解决方案就是禁止继承没有被特别设计并在文档中说明的普通类,详情P69
 
如果不能做到上一条,则要消除self-use,详情P69
 
把每个可继承方法的主体移到一个私有的helper方法中,然后让每个可继承方法调用其私有的helper方法。然后(……)
以下是自己写的小例子:
class SuperClass {
       public SuperClass(){
              System.out.println("SuperClass:Constructor");
       }
       public void Say(){
              helperSay();
       }
       private void helperSay(){
              System.out.print("SuperClass.Say():");helperWords();
       }
      
       public void words(){
              helperWords();
       }
       private void helperWords(){
              System.out.println("SuperClass.words()");
       }
}
 
public class SubClass extends SuperClass{
       public SubClass()
       {
              System.out.println("SubClass:Constructor");
       }
       public void Say(){
              super.Say();
              System.out.println("SubClass.Say()");
       }
       public void words(){
              super.words();
              System.out.println("SubClass.words()");
       }
       /*public void SayIt(){
              super.Say();
       }*/
       public static void main(String[]args){
              new SubClass().Say();
       }
}
 
添加了两个私有的helper方法,把基类中两个直接关联的公有方法予以解耦;原本是调用关系,现在全都转移到私有第三者中
 
Item16:Prefer interfaces to abstract classes
个人印象:
使用接口在诸多方面好于抽象类
一个接口一旦要增添新的方法,则所有实现这个接口的类将无法通过编译
在要求快速构建子类的场合,可以考虑用抽象类
强烈建议发布接口时提供一个skeletal implementation(骨架实现,要编写一个子类,最好同时扩展骨架实现、实现接口;前者用于继承,后者用于标定类型。译者)
 
 
Item18:Favor static member classes over nonstatic
(……)嵌套类的存在,唯一目的是为外部类提供服务。如果一个嵌套类在其它环境下也有用途,那它应该作为一个顶级类。共有四种嵌套类:static member class,nonstatic member class,anonymous class和local class。除第一种之外又称为内部类。下面要讲的是在什么时候使用什么样的嵌套类比较合适。
 
       一个static member class是最简单的嵌套类。它定义在一个类的内部,但是和一个普通的类差不多,但是可以访问外部类的所有成员,包括private成员。一个static member class是外部类的一个静态成员,同外部类的其它静态成员遵守同样的访问约束条件。如果它被声明为private,则它只在外部类内部可以被访问。
 
       一个static member class的一般用法是作为类的public 辅助类,只用于同外部类相关的功能。例如,考虑一个类型安全的enum,它用来描述一个calculator(Item21)支持的各种operation。Operation类应该是Calculator类的一个static member class。Calculator类的客户可以使用类似Calculator.Operation.PLUS和Calculator.Operation.MINUS这样的名称来引用各种运算(operation, 译者)。这个例子将在后面详解。
 
       字面上来看,static和nonstatic的member class只不过差了一个static修饰符而已。实际上这两种嵌套类差别是很大的。Nonstatic member class的每一个实例都暗含了一个外部类的实例。Within instance methods of a nonstatic member class, it is possible to invoke methods on the enclosing instance. 得到了一个nonstatic member class的引用,也就可以由此获得其外部类的一个引用。如果一个嵌套类的实例可以在脱离外部类的环境下存在,那它不可能是nonstatic member class:不可能在没有外部类实例的情况下创建一个nonstatic member class的对象。
 
       一个nonstatic member class实例和外部类实例的关联在nonstatic member class被创建时即被确立;并且不能再被更改。通常,这种关联在外部类的实例化方法调用nonstatic member class的构造方法时自动确立。尽管可以通过使用enclosingInstance.new MemberClass(args) 手工建立关联,但这种方式很少见。可以想象,这种关联要在nonstatic member class实例内部增加额外的空间,并且带来构造时间上的开销。
 
       Nonstatic member class的一个常见用法是定义Adapter,允许将外部类的实例当成某个不相关的类。例如,(……不会翻,译者)。类似地,collection接口的实现类,如Set和List的代表性的作法是使用nonstatic member class来实现其iterator接口。
 
Public class MySet extends AbstractSet{
       ……
       Public Iterator iterator(){
              Return new MyIterator();
       }
       Private class MyIterator implements Iterator{
              ……
       }
}
 
如果要定义一个嵌套类,它并不需要访问其外部类实例,那么最好是在定义时加上static修饰符,这样要好于nonstatic member class。如果忽略了static修饰符,则每个实例都会包含一个外部类的实例引用。持有这个引用会带来时间和空间上的消耗却得不到任何收益。(……)
 
       一个private static member class的典型用法是(……不会翻,译者)。例如,考虑一个Map实例,它把key和value关联起来。Map实例的典型情况是为每一个key-value对都含有一个Entry对象。尽管每个个entry都与map相关联,但entry的方法(getKey,getValue和setValue) 却并不需要访问map。因此用nonstatic member class实现entry就是一种浪费了;用private static member class是最好的选择。如果不小心忘记了static修饰符,那么map也能工作,但是每一个entry都持有一个不必要的map的引用,造成时间和空间的花销。
 
       如果当前要设计的类一个exported class(即非包访问权限,译者)的public或protected成员类,那么在static和nonstatic之间作出正确的选择就尤为重要。在这种情况下,成员类是个exported API元素,在不破坏和原有版本兼容性的情况下,后续版本不可能从nonstatic改为static。
 
       匿名类在Java中是比较特殊的东西。它没有名字。它也不是外部类的一个成员。与其它嵌套类的单独定义方式不同,它在使用的时候同时完成定义和实例化。只要表达式合法,匿名类可以在代码的任何地方使用。匿名类的行为象static还是nonstatic嵌套类取决于它们出现的地点:如果它们出现在nonstatic context,它们就含有对外部类的引用。
 
       匿名类的使用有一些限制条件。(……)。匿名类的典型用法是仅仅去实现接口或父类的方法。它们不会去定义新的方法(……)。由于匿名类一般出现在表达式中间,它们应该比较短。(……)
 
       匿名类的常见用法是创建function object,象一个Comparator实例等。例如,下面的方法对一个字符串数组调用sort方法,以字符串长度为依据:
       Arrays.sort(args,new Comparator(){
              Public int compare(       Object o1,Object o2){
                     Return((String)o1).length()-((String)o2).length();
              }
       });
 
       匿名类另一个常见用法是创建process object,象Thread,Runnable或TimerTask实例。第三种常见用法是用在static factory method里面(Item16)。第四种用法是用于类型安全的enum(Item21) 。如前所述,如果Operation是Calculator的static member class,那么每个Operation量都是双重嵌套的内部类(不是很懂,译者):
(例子略)
(local member class不懂,略)
 
Chapter5.Substitutes for C Constructs
Item19:Replace structures with classes
Item20:Replace unions with class hierarchies
Item21:Replace enum constructs with classes
 
[插入一个tip:]
对于Object.equals的认识到此完全澄清,Object.equals的源码为:
Public Boolean equals(Object obj){
       Return(this= =obj);
}
好家伙,原来是调用==的,而= =是比较内存地址的!
所以这个方法如果不重载基本没什么用!
只是在String和基本类型的wrapper类中重载得比较实用,把equals定义为值的比较
 
Item21:Replace enum constructs with classes
(需要重读)
Item22:Replace function pointers with classes and interfaces
C里面的函数指针在Java里实际上变成了策略模式,由一个策略接口和实现此接口的function object组成。
Function object的定义(第一次出现):典型的策略类,没有filed,只有策略method。
其中Comparator接口是典型例子。
策略模式的两种实现方式:
如果不想重用,就用匿名内部类
如果想重用,可仿照下例:
Class Host{
       ……
       Private static class StrLenCmp implements Comparator,Serializable{
              Public int compare(Object o1,Object o2){
                     String s1=(String)o1;
                     String s2=(String)o2;
                     Return s1.length()-s2.length();
              }
}
Public static final Comparator STRING_LENGTH_COMPARATOR=new StrLenCmp();
}
 
String类就是使用上面的模型实现了一个大小写无关的comparator,String.CASE_INSENSITIVE_ORDER。
 
Chapter6:Methods
Item23:Check parameters for validity
在方法开始时对参数作有效性检查。
 
对public方法,利用javadoc的@throws标签把参数不符合条件时会抛出的异常记载下来。一般都是IllegalArgumentException,IndexOutOfBoundsException或NullPointerException等。
 
对于非public方法,只有你(包作者)对这种方法进行调用,因此你能够且应该确保只有合法的参数才能予以处理。因此非public的方法通常要用assertion而不是常规方式进行参数有效性检查。如果你的平台支持assertion,就应该用assert结构;不然的话就应该运用makeshift assertion机制。
对于那些当前方法并不使用,而是存起来供以后使用的参数进行有效性检查尤为重要。
构造方法是这种情形一个例子。
 
Item24:Make defensive copies when needed
当你写了一个方法或构造方法,要接受一个客户提供的对象送给内部数据结构来处理的时候,考虑一下这个客户提供的对象是mutable还是immutable。如果是mutable,你的类在将其送交内部数据结构进行处理之后是否还允许这个对象发生改变。如果不允许,那么你必须保护性拷贝这个对象,将拷贝送交数据结构以代替原始对象。比方说,如果你打算把一个客户提供的对象作为自己的类内部一个Set实例的元素或一个Map实例的key,你应该意识到当你把这个客户对象插入以后,Set和Map的不变性将不允许再对这个对象作任何的修改,否则Set和Map将遭破坏(这种情况下,一定要插入原始对象的defensive copy而不是原始对象,译者)。
 
在将类的一个mutable成员返还客户的时候,也要考虑这个问题。例如,一个长度大于零的数组是mutable的。因此在将一个内部数组返回给客户之前应该使用保护性拷贝。当然,你也可以返回数组的一个immutable view。这两种方法在Item12都有描述。
在Period例子中,有经验的程序员会由Date.getTime()得到long类型的数据作为内部表示时间的数据而不是直接用Date对象引用。
 
Item24:Design method signatures carefully
参数列表不要多于3个参数
相同类型的参数序列特别有害,即使参数传递顺序错误,程序仍然能通过编译正常运行。
 
Item26:Use overloading judiciously
类CollectionClassifier的例子表明:对于方法的overload,执行哪个方法的选择是在编译期(compile time)就确定的。在每一轮循环中编译期类型(compile-time type)是相同的:Collection。运行时类型(run-time type)在每轮循环中都不一样,但是这并不会影响overloading的选择。
 
Selection among overloaded methods is static ,while selection among overridden methods is dynamic .( 对overloaded 方法的选择是静态的,而对overridden 方法的选择是动态的)   overridden方法的正确选择是在运行期,调用哪个方法取决于对象的运行期类型。
对象的compile-time type并不会影响overridden方法的调用;“最合适”的方法总是能被正确调用。而在overloading的情况下,对象的run-time type并不影响哪个overloading方法被调用;选择在compile time(编译期间,译者)就完成了,执行时完全是遵照参数的compile-time type。
 
       一个安全的传统办法是不要暴露两个同样参数数目相同的overloading。更好的办法是总是给不同的方法取不同的名字。
      
       一个例子是ObjectOutStream。它对每种基本类型和若干种对象引用类型都有相应的write方法。它没有采用orverloading write方法,而是用象writeBoolean(Boolean) ,writeInt(int) ,以及writeLong(long) 这样不同的方法名。
 
       对于构造方法,你不能够选择使用不同的名称。多个构造方法的情况都是overloading,因为构造方法不能被overriden。对于两个含有相同数目的参数的overloading,至少要有一对相同位置的参数是绝对不同的。在这种情况下,不会出现问题。
 
       例如,ArrayList有一个构造方法接受一个int参数,另一个构造方法接受一个Collection参数。在任何情况下,也不会为调用哪一个构造方法而产生混淆,因为基本类型和对象引用是完全不同的。
 
       在改进一些已有类,令基实现某个新的接口的时候有可能会破坏上面的原则。比如很多Java类库中的值类型在引进Comparable接口之前,都定义了自己的compareTo方法。下面是String类原始的compareTo方法定义:
Public int comareTo(String s) ;
引入Comparable接口之后,所有这些类都改为实现这个接口,那么就要增添一个更一般化的compareTo方法如下:
       Public int compareTo(Object o);
这样导致的overloading很明显破坏了上述的原则,好在这两种方法在接受相同参数的时候执行的是完全一样的功能。程序员并不知道实际上调用了哪个overloading,但是这两个方法返回完全一样的结果,不会带来危害。要得到这种效果,一个标准的方法是让更通用的overloading forward to 那个more specific:
       Public int compareTo(Object o){
              Return compareTo((String)o);
       }
 
对equals方法的一个相似的惯用法是:
Public Boolean equals(Object o){
       Return o instanceof String &&equals((String)o);
}
 
尽管Java类库大多数是遵守上述建议的,但是也有一些地方没有做到。比如,String类有两个overloaded 静态工厂方法,valueOf(char[])和valueOf(Object) ,当传入同一个对象引用时,两个方法会做出截然不同的事情。这是一个不正常的现象,有潜在的导致混乱的可能。
 
[在这里提一个tip:在本Item之前所有“重载”的字样,指的都是overriden,不是overloading]
 
Item27:Return zero-length arrays ,not nulls
0长度数组是immutable。
 
Item28:Write doc comments for all exposed API elements
 
Chapter7.General Programming
Item29:Minimize the scope of local variables
使用for循环好于while循环,因为前者可以将循环变量的作用域缩小。例如:
For(Iterator i=c.iterator();i.hasNext();){
       doSomething(i.next());
}
如果用while就是
Iterator i=c.iterator();
While(i.hasNext()){
       doSomething(i.next());
}
循环结束之后,变量i已没有用但仍将存在,这在编程上是个大忌,容易在以后出现问题。
for循环缩小局部变量作用域的另一个例子:
for(int i=0,n=list.size();i<n;i++){
       doSomething(list.get(i));
}
它有两个循环变量i和n,其中size()方法只调用一次而不用每次循环都调用。且i和n的作用域都减到最小。
 
Item30:Know and use the libraries
利用java.util包里的Random.nextInt(int) 来产生某个域内的随机大小整数,而不是自己去写方法实现。
 
       任何一个程序员都应该熟读java.lang,java.util,其次是java.io!
      
接下来作者举了Collections为例,提到了一个有趣的方法。打印数组时一般可能想到用循环,但实际上可以这样:System.out.println(Arrays.asList(a));
 
Item31:Avoid float and double if exact answers are required
float和double是为了科学计算而设计的,它们只提供近似值,并不适合日常生活中的通用计算。比如下面的例子:
System.out.println(1.00-9*.10);
结果是0.9999999999999995!
再如:
考虑下面这个问题,你身上有1美元。你发现了一个架子上有一排精美的蜡烛,其价格为.10,.20,.30,以此类推直到1美元。你从最便宜的蜡烛买起,一样买一支,你一共能买多少支。下面是一个天真的解决方案:
Public static void main(String[]args){
       Double funds=1.00;
       Int itemsBought=0;// 构买数量
       For(double price=.10;funds>=price;price+=.10){
              Funds-=price;
              itemsBought++;
       }
       System.out.println(itemsBought+”items bought.”);
       System.out.println(“Change:$”+funds);
}
运行这个程序的结果是,你只能买三支蜡烛,并且剩下$0.3999999999999999。显然不是你想要的结果。正确的解决方案是使用BigDecimal,int,或long来进行金融的计算。下面是简单的用BigDecimal类型对上述程序加以改进:
Public static void main(String[]args){
       Final BigDecimal TEN_CENTS=new BigDecimal(“.10”);
       Int itemsBought=0;
       BigDecimal funds=new BigDecimal(“1.00”);
       For(BigDecimal price=TEN_CENTS;funds.compareTo(price)>=0;price=price.add(TEN_CENTS)){
              itemsBought++;
              funds=funds.subtract(price);
       }
       System.out.println(itemsBought+”items bought”);
       System.out.println(“Money left over:$”+funds);
}
运行结果是买了4支蜡烛,剩下$0.00。如果你觉得用BigDecimal有点麻烦你可以使用int或long来代替,但是小数点就要自己来跟踪了。在这个问题中,计算单位就不能是dollar而应该是penny。
 
       用BigDecimal还有一个好处就是它有8种四舍五入的模式。如果要求实际计算要采用哪舍入模式,用BigDecimal就很方便了。
 
       此外,如果计算的数值在9位以内可以用int;18位以内可以用long;超过18位只能用BigDecimal。
 
Item32:Avoid strings where other types are more appropriate
对作者所说的情况都不太了解。
 
Item33:Beware the performance of string concatenation
举了String和StringBuffer的例子。后者可以在初始化的时候在构造方法里初始化长度!
 
Item34:Refer to objects by their interfaces
Item35:Prefer interfaces to reflection
看得不完全懂。
 
reflection是很强大的功能。但是有以下三个缺点:
失去了编译时期类型检查能带来的全部好处。
代码复杂,可读性不好
性能不高(在1.4 平台上通过reflection 访问和常规方法访问速度差一倍)
 
       reflection是为了组件化的应用程序生成工具而设计的。作为常规来讲, 在运行期,一般的对象不应该通过反射机制来访问(应该通过常规的方法调用,译者)
      
       原书120页的例子(通过命令行参数传入类名,由类名得到Class对象—Class.forName(args[0]),由Class.newInstance()得到所需要的对象实例)。通常而言,一般情况下对反射运用到这个程度已经足够了(当然还有一些要深入运用的特殊情况)。
 
那个例子还展示了用反射的意义:在编译时期不想确定具体类型。比如要用相同的数据比较TreeSet和HashSet中数据的排列顺序,在编译期并不想确定具体类型,只是指定为Set接口,在运行时通过命令行参数控制生成TreeSet实例还是HashSet实例,生成之后即完全通过Set接口访问两者的方法,不再用reflection。
 
Item36:Use native methods judiciously
本地方法调用是历史原因形成的。最好不用,非用不可时尽量少用。
 
 
Item37:Optimize judiciously
没看完,大致就是解说标题要表达的意思,鲜有举例
 
Item38:Adhere to generally accepted naming conventions
域最好不用缩写,局部变量则没有关系
只有常量(static final) 可以使用下划线。
 
Chapter8:Exceptions
Exception是把双刃剑
Item39:Use exceptions only for exceptional conditions
 
避免一些聪明过头的作法。
 
最后一段讲对于state-dependent方法来说,如何在提供一个state-testing方法(例如Iterator.next()是state-dependent方法,其state-testing方法为Iterator.hasNext(),要首先满足Iterator.hasNext()的条件才能调用Iterator.next()方法,译者)或是令其返回一个distinguished值之间做出选择。 采用后者的情况没有看懂。
 
Item40:Use checked exceptions for recoverable conditions and run-time exceptions for programming errors
 
Item41:Avoid unnecessary use of checked exception
最后一段与 Item39 的结尾类似,都没看懂
 
Item42:Favor the use of standard exceptions
 
Item43:Throw exceptions appropriate to the abstraction
似懂非懂,只有一个地方有点印象,就是告诉人不要写象throws Exception或者throws Throwable这种敷衍的父类,要写具体的异常,以免把一些别处意想不到的异常给屏蔽了。
       (事实上,笼统地使用try{doSomething()}catch(Exception e){} 是极其错误的,应为它可能会把doSomething()中可能抛出的RuntimeException给屏蔽了!!译者)
 
Item45:Include failure-capture information in detail messages
Unchecked exception 最好提供一种 accessor ,没看懂
 
Item46:Strive for </vetbfailure atomicity
 
[other tips]
注意:覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常。
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值