竹笋炒肉转载 -- 读《Effective java 中文版》(2)

FROM 竹笋炒肉 http://hedong.3322.org/

读《Effective java 中文版》(11)
第10条:谨慎地改写clone

  Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对象允许克隆。Cloneable没有包含任何方法,只是决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,则Object的clone方法返回该对象的逐域拷贝,否则抛出一个CloneNotSupportedException异常。
  至于clone本身,是一种很有风险的、语言之外的对象创建机制:无须调用构造函数就可以创建一个函数。
  clone方法的约定是:创建和返回对象一个拷贝,且

x.clone!=x为true,
x.clone().getClass()==x.getClass()为true,
x.clone.equals(x)为true,
当然,这三个也不是绝对的要求。
  如果你改写了一个非final类的clone方法,则应该返回一个通过调用super.clone而得到的对象。如果一个类的所有超类都遵守这条规则,那么一直调用super.clone最终会调用到object的clone方法,从而创建出正确的类的实例。这种机制大致上类似于自动的构造函数链,只不过它不是强制要求的。
  实际上,clone方法是另一个构造函数,你必须确保它不会伤害到原始的对象,并且正确地建立起被克隆对象中的约束关系。
  Clone结构与指向可变对象的final域的正常用法是不兼容的。非除非在原始对象和克隆对象之间可以安全地共享此可变对象。为了使一个类成为可克隆的,可能有必要从某些域中去掉final修饰符。
  实现对象拷贝的好办法,是提供一个拷贝构造函数(其唯一的参数的类型是包含该构造函数的类)。
  Cloneable有如此多问题,其它的接口不应该扩展该接口,为了继承而设计的类(参见第15条)也就该实现这个接口。


读《Effective java 中文版》(12)
第11条:考虑实现Comparable接口
  compareTo方法在Object中并没有被声明,它是java.lang.Compareable接口中唯一的方法。一个类实现了Compareable接口,就表明它的实例具有内在的排序关系(natural ordering)。如果一个数组中的对象实现了Compareable接口,则对这个数组进行排序非常简单:Arrays.sort();对于存储在集合中的Comareable对象,搜索、计算极值以及自动维护工作都非常简单。一旦你的类实现了Compareable接口,它就可以跟许多泛型算法(generic algorithm)以及依赖于该接口的集合的操作实现进行协作,以小的努力得到强大的功能。
  Java平台库中的所有值类(value classes)都实现了Compareable接口。


  compareTo的约定是:
  将当前这个对象与指定的对象进行顺序比较,当该对象小于、等于或大于指定对象时,分别返回一个负整数、0或正整数,如果无法进行比较,则抛出ClassCastException异常。

实现者必须保证对于所有的x和y,满足sgn(x.compareTo(y))==-sgn(y.compareTo(x)),且如果前者抛异常后者也抛。

实现者必须保证该比较关系可传递:x.compareTo(y)>0且y.comareTo(z)>0暗示x.compareTo(z)>0

实现者必须保证经x.compareTo(y)==0暗示着:对所有的z,sgn(x.compareTo(z))==sgn(y.compareTo(z))

强烈建议(x.compareTo(y)==0)==(x.equals(y)),对于实现了Compareable接口的类,如果违反这个条件,应该予以明确说明。

  与equals不同的是,在跨越不同类的时候,comapreTo可以不做比较。
  依赖于比较关系的类包括有序集合类TreeSet和TreeMap,以及工具类Collections和Arrays,它们内部都含有搜索和排序算法。
  与equals相似,compareTo也遵循:自反性、对称性、传递性、非空性。并且,没有一种简单地方法可以做到,在扩展一个新的可实例化的类的时候,既增加了新的特征,同时以保持了compareTo约定。

 

读《Effective java 中文版》(13)
第12条:使类和成员的可访问能力最小化
  一个设计良好的模块会隐藏所有的实现细节,把它的API与实现清晰地隔离开来。模块之间只通过它们的API进行通信,一个模块不需要知道其它模块的内部工作情况,此即为信息隐藏(information hiding)或封装(encapsulation)。这样做的理由,是为有效地解除一个系统中各模块之间的耦合关系,其代价是性能的牺牲。


  java中一个实体(类、接口、成员等)的可访问性,由该实体声明所在位置,以及该实体声明中所出现的访问修饰符(private protected 和 public)共同决定的。
  经验表明,应尽可能地使一个类或成员不被外界访问,即应使用最低可能地、且与该软件正确功能相一致的访问级别。
  “使一个不必要的公有类成为包级私有的类”。对于顶层的(非嵌套的)类和接口,只有两种可能的访问级别:包级私有和公有。把一个类或接口做成包级私有,则它成了包的实现的一部分,而不是包导出的API的一部分,从而可在以后自由地修改它;如果做成公有,则有义务永远支持它。
  如果一个包级私有的顶层类或接口只在某一个类的内部使用,则应考虑将其做为后者的一个私有嵌套类(或接口)。
  对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别,可访问性递增顺序为:

私有的(private)-只在类内部可访问
包级私有的(package-private)-默认访问级别(不显式指定修饰符时),包内部的任何类都可以访问
受保护的(protected)-子类或包内的类的可以访问
公有的(public)-任何地方可以访问

  受保护的成员是一类的导出的API的一部分,须永远支持,受保护的成员应尽量少用。
  如果一个方法改写了超类中的一个方法,那么子类中该方法的访问级别低于超类中的级别是不允许的。
  公有类应尽可能地减少公有的域。包含公有可变域(公有的非final域或公有指向可变对象的final域)的类不是线程安全的。一个例外是,通过公有的静态final域来暴露类的常量是允许的。
  具有公有的静态final数组域几乎总是错误的。如:
//protential security hole!
public static fina Type[] VALUES={...};
  应被替换为一个私有数组,以及一个公有的非可变列表,如:
private static Type[] PRIVATE_VALUES={...};
public static final List VALUES= Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  或者被替换为一个私有数组,以及返回数组拷贝的公有方法,如:
private static Type[] PRIVATE_VALUES={...};
public static final Types[] values(){
return (Type[] PRIVATE_VALUES.clone());
};

 


读《Effective java 中文版》(14)
第13条:支持非可变性
  一个非可变类是一个简单的类,它的实例不能被修改。每个实例中包含的所有信息都必须在该实例被创建的时候就提供出来,并在对象的整个生存期内固定不变。如java的String类、原语类型的包装类、BigInteger和BigDecimal等。


  非可变类的5条规则:

不要提供任何会修改对象的方法(也称为mutator)
保证没有可被子类改写的方法,一般做法是加个final
使所有的域都是final的
使所有的域都成为私有的
保证对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须确保该类的客户无法获得指向这些对象的引用,且,永远不要用客户提供的对象引用来初始化这样的域,也不要提供能返回这样的引用的方法。在构造函数、访问方法和readObject方法中使用保护性拷贝技术(参见第24条).

  看一个“复数”的例子:
public final class Complex{
private final float re;
private final fload im;

public Complex(float re,float im){
this.re=re;
this.im=im;
}
public float realPart(){return re;};
public float imaginaryPart(){return im;};

public Complex add(Complex c){
return new Complex(re+c.re,im+c.im);
}
public Complex subtract(Complex c){
return new Complex(re-c.re,im-c.im);
}
public Complex multiply(Complex c){
return new Complex(re*c.re-im*c.im,re*c.im+im*c.re);
}
public Complex divide(Complex c){
float tmp=c.re*c.re+c.im*c.im;
return new Complex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp);
}
public boolean euqals(Object o){
if (o==this) return true;
if(!(o instanceof Complex))return false;
Complex c=(Complex)o;
return (Float.floatToIntBits(re)==Float.floatToIntBits(c.re))&&
Float.floatToIntBits(im)==Float.floatToIntBits(c.im));
}
public int hashCode(){
int result=17+Float.floatToIntBits(re);
result=37*result+Float.floatToIntBits(im);
return result;
}
public String toString(){
return "("+re+"+"+im+"i)";
}
}//此Complex定义是一个非工业级的复数实现,一是因为乘、除法的实现可能导致不正确舍入,二是没有处理复数NaN或无穷大的情况。
  该例中对,对复数的操作都会返回一个新的实例,而不修原来的实例,这被称之为函数式作法(functional),与之对应的是修改实例状态的过程式方法(precedural)。
  非可变对象比较简单,它本质上是线程安全的,它们不要求同步,因而非可变对象可被自由地共享(一个简单地做法,是为提供公有的静态final常量),而不需要进行保护性拷贝,而且也应为非可变类提供clone方法或拷贝构造函数(如String类的拷贝构造函数,应尽量不用)。
  不仅可以共享非可变对象,而且可以共享它们的内部信息。例如,BigInteger的negate方法产生一个新实例,其存放数值的数组与原来的实例是同一个数组。
  非可变对象为其他对象--无论是可变的还是非可变的--提供了大量的构造(buildig block)。
  非可变类真正唯一的缺点是,对于每一个不同的值都要求一个单独的对象。常见的解决思路是提供一个可变的配套类,如String的配置可变类StringBuffer.
  一个类绝不允许其子类改写的方法有三:
声明类为final,见前例
类不为final,但每个方法为final,这法不值得提倡
使所有构造函数为私有或者包级私有,且增加公有的静态工厂来代替公有的构造函数。如:
//Immutable class with static factories instead of construtors
public class Complex{
private final float re;
private final float im;
private Complex(float re,float im){
this.re=re;
this.im=im;
}
public static Complex valueOf(float re,float im){
return new Complex(re,im);
}
...
}这种方法灵活性强,值得提倡。

  由于历史的原因,BigInteger或BigDecimal的方法可以被改写,所以你在开发自己的非可变类时,如果不能确保收到这两个类的实例的非可变性,则需要进行保护性拷贝。如:
public void foo(BigInteger b){
if(b.getClass() != BigInteger.class) b=new BigInteger(b.toByteArray());
...
}
  其实,前面所述的规则过于强,只需满足:没有一个方法能对对象状态产生外部可见(externally visible)的改变。例如:
//Cached, lazily initialized function of an immutable object
private volatile Foo cachedFooVal = UNLIKELY_FOO_VALUE;
public Foo foo(){
Foo result=cachedFooVal;
if(result==UNLIKELY_FOO_VALUE) result=cachedFooVal=fooVal();
return result;
}
//fooVal函数开销较大,所以在前面使用了迟缓初始化的技术。
private Foo fooVal(){...}
  除非有很好的理由要让一个类成为可变类,否则就应该是非可变的,缺点是性能问题,如果对性能要求比较高,可以提供公有的可变配套类。
  如果一个类不能被做成非可变类,仍可应尽可能地限制它的可变性。构造函数应创建完全初始化的对象,所有约束关系应在这时候建立起来,构造函数不应把“只初始化了一部分的实例”传递给其它的方法。

 

读《Effective java 中文版》(15)
第14条:复合优先于继承
  继承是实现代码重用的有力手段,但不适当地使用继承会导致脆弱的软件。

在一个包内使用继承是非常安全的,因在那儿子类和超类在同一个程序员的控制之下。
对于专门为了继承而设计、且有很好文档说明的类,使用继承也是安全的。
对普通的具体类(concret class)时行跨跃包边界的继承,则是非常危险的。

 

  与方法调用不同的是,继承打破了封装性,一个子类依赖于其超类中特定功能的实现细节。超类的实现的变化,则子类可能会被打破。

自用性,即超类中不同公有方法的实现时存在调用关系,当子类改写这些方法时,常会导致子类的脆弱
超类在它的后续方法中增加了新的方法,如果子类不能及时改写这些方法,异常的数据或操作出现。
子类继承超类后,增加了一个新的方法,则当超类在新版中也增加了具有相同原型特征的方法时,可能会出现问题

  有一种办法可以避免上述所有问题:新类,不是扩展一个已有类,而是设置一个私有域,它引用这个已有类的一个实例。这种设计被称为“复合(composition)”。新类中的每个实例方法,都可以调用被包含的已有类实例中对应的方法,并返回它的结果,即为“转发方法(forwarding method)”。这样的类比较稳固,这不依赖于已有类的实现细节。一个类的实例都把另一个类的实现包装起来了,则前者的类叫做包装类(wrapper class)。
  看一个例子:
//wrapper class - uses composition in place of inheritance
public class InstrumentedSet implements Set{
private final Set s;
private int addCount=0;

public InstrumentedSet(Set s){
this.s=s;
}
public boolean add(Object o){
addCount++;
return s.add();
}
public boolean addAll(Collection c){
addCount+=c.size();
return s.addAll(c);
}
public int getAddCount(){
return addCount;
}
//forwarding methods
public void clear() { s.clear(); }
public boolean contains(Object o){return s.contains(o); }
public boolean isEmpty() {return s.isEmpty(); }
public int size() {return s.size(); }
....
public String toString() {return s.toString(); }
}
  上例中,InstrumentedSet类对Set类进行了修饰,增加了计数特性。有时,复合和转发这两项技术的结合被错误地引用为“委托(delegation)”,从技术的角度而言,这不是委托,除非包装对象把自己传递给一个被包装的对象。
  包装类几乎没有什么缺点。需要注意的是,包装类不适合用于回调框架(callback framework)中。在回调框架中,对象把自己的引用传递给其它的对象,以便将来调用回来,当它被包装起来以后,它并不知道外面的包装对象的情况,所以它传递一个指向自己的引用(this)时,会造成回调时绕开外面的包装对象的问题。这被称为SELF问题。
  只有当子类真正是超类的“子类型(subtype)”时,继承才是合适的。即两者之存在“is-a”的关系。java平台中,也有违反这条规则的地方:如Stack不是向量,所以不应扩展Vector;属性列表不是散列表,所以Properties不应扩展Hashtable。在决定使用复合还是扩展时,还要看一试图扩展的类的API有没有缺陷,如果你愿意传播这些缺陷到自己的API中,则用继承,否则可用复合来设计一个新的API。

 

 

读《Effective java 中文版》(16)
第15条:要么专门为继承而设计,并给出文档说明,要么禁止继承
  一个专门为了继承而设计并且具有良好文档说明的类,意味着:


该类的文档必须精确地描述了改写每一个方法所带来的影响  该类必须有文档说明其可改写的方法的自用性:对于每一个公有的或受保护的方法或者构造函数,它的文档必须指明它调用了哪些可改写的方法是以什么顺序调用,每个调用的结果是如何影响后续的处理过程的。  惯例是如果一个方法调用了可改写的方法,那么在它的文档注释的末尾应该包含关于这些调用的描述信息。
为了使程序员能够编写出更加有效的子类,而无需承受不必要的痛苦,一个类必须通过某种形式提供适当的钩子(hook),以便能够进入到它的内部工作流程中,这样的形式可以是精心选择的受保护方法,也可是以保护域,后者少见。  当为了继承的目的而设计一个有可能会被广泛使用的类时,须意识到,对于文档中所说明的自用模式(self-use pattern),以及对于其受保护方法和域所隐含的实现细节,实际上已经做出了永久的承诺。这些承诺使得你在后续的版本中提高这个类的性能或增加很功能非常困难,或者不可能。
构造函数一定不能调用可改写的方法,无论是直接还是间接进行,因为超类的构造函数在子类的构造函数之前运行。看例子: public class Super{ //broken-constructor invokes overridable method public Super(){ m(); } public void m(){}; } final class Sub extends Super{ private final Date date; Sub(){ date=new Date(); } public void m(){ System.out.println(date); } public static void main(String[] args){ Sub s=new Sub(); s.m(); } }   程序运行的结果不会打印出日期两次,第一次打出null.
在一个为了继承而设计的类中,实现Cloneable或Serializable接口,因为clone和readObject方法在行为上非常类似于构造函数,所以无论clone还是readObject都不能调用一个可改写的方法,无论是直接还是间接的方式。  对于readObject方法,子类中改写版本的方法将在子类的状太被反序列化之前先被运行;对于clone方法,改写版本的方法将在子类的clone方法有机会修正被克隆对象的状态之前先被运行。
在一个为了继承而设计的类中实现Serializable接口,且该类有一个readResolve或writeReplace方法,则你必须使readResolve或者writeReplace成为受保护的方法,而不是私有的方法。
  为了继承而设计一个类,要求对这个类有一些实质性的限制。对于那些并非为了安全地进行子类化而设计和编写文档的类(如普通的具体类),禁止子类化。有两种办法可以禁止子类化:
把类声明为final
把所有的构造函数变成私有的或包级私有的,增加一些公有的静态工厂来替代构造函数的位置
  如果必须从这样的类来继承,则要确保这类永远不会调用到它的可改写的方法。

 


读《Effective java 中文版》(17)
第16条:接口优于抽象类
  Java语言中的接口与抽象类的一些区别:

抽象类允许包含某些方法的实现,接口不允许。
为实现一个由抽象类定义的类型,它必须成为抽象类的一个子类。任何一个类,只要它定义了所有要求的方法,并且遵守通用约定,则它就允许实现一个接口。
java只允许单继承,抽象类作为类型定义受到了极大的限制。

 

  看一下接口:

 

  已有的类可以很容易被更新,以实现新的接口。只要:增加要求的方法,在类的声明上增加implements子句。

  接口是定义mixin(混合类型)的理想选择。一个mixin是指这样的类型:一个类除了实现它的基本类型(primary type)之外,还可以实现这个mixin类型,以表明它提供了某些可选择的行为。接口之所以能定义mixin,因为它允许可选的功能可被混合到一个类型的基本类型中,而抽象类不能用于定义mixin类型,同样的理由是因为它们不能被更新到已有的类中:一个类不可能有一个以上的父类,并且在类层次结构中没有适当的地方来放置mixin。
  接口使得我们可以构造出非层次结构的类型结构。看例子:
public interface Singer{
AudioClip sing(Song s);
}
public interface Songwriter{
Song compose(boolean hit);
}
  为解决歌唱家本人也能做曲的情况,可以很简单地做到:
public interface SingerSongwriter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}
  如果用抽象类来做,会是如何?

  接口使得安全地增强一类的功能成为可能,做法是使用第14条介绍的包装类模式。如果用抽象类型来做,则程序员除了使用继承没有别的方法。

  接口不允许包含方法的实现。把接口和抽象类的优点结合起来,对于期望导出的每一个重要接口,都提供一个抽象的骨架实现(skeletal implementaion)类。接口的作用仍是定义类型,骨架实现类负责所有与接口实现相关的工作。按照惯例,骨架实现被称为AbstractInterface(注:此interface是所实现的接口的名字,如AbstractList,AbstractSet)。看一个静态工厂:
//List adapter for int array
static List intArrayAsList(final int[] a){
if (a==null) throw new NullPointerException();
return new AbstractList(){
public Object get(int i){
return new Integer(a[i]);
}
public int size(){
return a.length;
}
public Object set(int i,Object o){
int oldVal=a[i];
a[i]=((Integer)o).intValue();
return new Integer(oldVal);
}
}
}
  这个例子是一个Adapter,它使得一个int数组可以被看作一个Integer实例列表(由于存在int和Integer之间的转换,其性能不会非常好)
  骨架实现的优美之外在于,它们为抽象类提供了实现上的帮助,但又没有强加“抽象类被用做类型定义时候”所特有的严格限制。对一地一个接口的大多数实现来讲,扩展骨架实现类是一个很显然的选择,当然它也只是一个选择而已。
  实现了这个接口的类可以把对于接口方法的调用,转发到一个内部私有类的实例上,而这个内部私有类扩展了骨架实现类。这项技术被称为模拟多重继承。
  编写一个骨架实现类相对比较简单,首先要认真研究接口,并且确实哪些方法是最为基本的(primitive),其他的方法在实现的时候将以它们为基础,这些方法将是骨架实现类中的抽象方法;然后须为接口中的其它方法提供具体的实现。骨架实现类不是为了继承的目的而设计的。(怎么理解?)看例子:
//skeletal implementation
public abstract class AbstractMapEntry implements Map.Entry{
//primitives
public abstract Object getKey();
public abstract Object getValue();

//Entries in modifiable maps must override this method
public Object setValue(Object value){
throw new UnsupportedOperationException();
}
//Implements the general contract of Map.Entry.equals
public boolean equals(Object o){
if (o==this) return true;
if (!(o instanceof Map.Entry)) return false;
Map.Entry arg=(Map.Entry)o;
return eq(getKey(),arg.getKey())&&eq(getValue(),arg.getValue());
}
private static boolean eq(Object o1,Object o2){
return (o1==null?02==null:o1.equals(o2));
}
//implements the general contract of Map.Entry.hashCode
public int hashCode(){
return (getKey()==null?0:getKey.hashCode())^(getValue()==null?0:getValue().hashCode());
}
}

  使用抽象类来定义允许多个实现的类型,比使用接口有一个明显的优势:抽象类的演化比接口的演化要容易的多。在后续的发行版中,如果希望在抽象类中增加一个方法,只增加一个默认的合理的实现即可,抽象类的所有实现都自动提供了这个新的方法。对于接口,这是行不通的。虽然可以在骨架实现类中增加一方法的实现来解决部分问题,但这不能解决不从骨架实现类继承的接口实现的问题。由此,设计公有的接口要非常谨慎,一旦一个接口被公开且被广泛实现,对它进行修改将是不可能的。

 

读《Effective java 中文版》(18)
第17条:接口只是被用于定义类型
  当一个类实现了一个接口的时候,这个接口被用做一个类型(type),通过此类型可以引用这个类的实例。因此,一个类实现了一个接口,就表明客户可以对这个类的实例实施某些动作。为了任何其它目的而定义接口是不合适的。


  常量接口是对接口的不良应用,因它不满足上述条件。常量接口中没有包含任何方法,只包含静态的final域,每个域都导出一个常量。r看下例。如果一个类要使用这些常量,它只要实现这个接口,就可以避免用类名来修饰常量名。但是,实现一个常量接口,会导致把这样的实现细节(指内部使用常量)泄露到该类的导出API中。而且在将来的发行版本中,如果类不再需要这些常量了,但仍要实现这个接口。还有,这样的类的子类,也会为常量所污染。
//Constant interface pattern - do not use!
public interface PhysicalConstants{
static final double AVOGADROS_NUMBER =6.02214199e23;
static final double BOLTZMANN_CONSTANT =1.3806503e-23;
static final double ELECTRON_MASS 9.10938188e-31;
}

  Java平台中有几个常量接口,如:java.io.ObjectStreamConstants,这不值得效仿。
  如果要导出常量,有几种方案:

如果这常量与某个已有类或接口紧紧联系在一起,则应该把它们加入类或常量中。

如果这些常量最好被看作一个枚举类型的成员,那么应该用一个类型安全枚举类(typesafe enum class),参见第21条。

否则,应用一个不可被实例化的工具类(utility class),见第3条。看对上面例子的修改:
//constant utility class
public class PhysicalConstants{
private PhysicalConstants(){};//prevent instantiation
public static final double AVOGADROS_NUMBER =6.02214199e23;
public static final double BOLTZMANN_CONSTANT =1.3806503e-23;
public static final double ELECTRON_MASS 9.10938188e-31;
}

  可以通过将常量放在局部变量中,来减少键盘的录入工作量。如:
private static final double PI=Math.PI;

 


读《Effective java 中文版》(19)
第18条:优先考虑静态成员类
  嵌套类(nested class)是指被定义在另一类的内部的类,它只为它的外围类服务。如果一个嵌套类可能会用于其它的某个环境,那就应为一个顶层类(top-level class)。嵌套类有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)和局部类(local class),其中后三种称为内部类(inner class)。


  静态成员类是一种最简单的嵌套类,最后把它看作一个普通的类,碰巧被声明在另一个类的内部而已,它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,遵守同样的可访问性规则。如果它被声明为私有的,则只能在外围类的内部才可以被访问。静态成员类的一种通常用法是作为公有的辅助类,仅当它与它的外部类一起使用时才有意义。
  从语法上,非静态成员类与静态成员类的差别在于后者有一个static。非静态成员类的每一个实例,都隐含着与外围类的一个外围实例紧密联系在一起。在非静态成员类的实例方法内部,调用外围实例上的方法是可能的,或者使用一个经过修饰的this也可以得到个指向外围实例的引用。在没有外围实例的情况下,要想创建非静态成员类的实例是不可能的。非静态成员类的一种通常用法是定义一个Adapter,它允许外部类的一个实例被看作另一个不相关的类的实例。如Map接口的实现往往使用非静态成员类来实现它们的“集合”视图,这些“集合”视图是由Map的keySet、entrySet、Value方法返回。再看Set集合接口实现迭代器的例子:
//Typical use of a nonstatic member class
public class MySet extends AbstractSet{
// bulk of the class omitted
public Iterator interator(){
return new MyIterator();
}
private class MyIterator implements Iterator{
...
}
}
  如果声明的成员类不要求访问外围实例,那么就把static修饰符放到成员类的声明中。这会减少维护成员类实例对外围实例对象的引用的开销。
  私有静态成员类的一种通常用法是用来代表外围类对象的组件。例如:Map实例把一些Key和Value关联起来,其内部通常有一个Entry对象对应于每个键值对。每个Entry对象都与一个Map对象关联,但Entry的方法(getKey、getValue、setValue)都不需要外围的Map。此时,用私有的静态成员类是最佳选择。
  匿名类没有名字,它不是外围类的一个成员。它并不与其它的成员一起被声明,而在被使用的点上同时声明和被实例化。匿名类可以出现在代码中任何允许表达式出现的地方。匿名类的行为与成员类非常类似,具体取决于它所在环境:如果匿名类出现在一个非静态的环境中,则它有一个外围实例。
  匿名类的适用性有一些限制:

匿名类只能被用在代码中它将被实例化的那个点上。
它没有名字,实例化后不能 再 对它进行引用
匿名通常只实现了其接口或者超类中方法,不会声明新的方法,因为没有访问新方法的途径。
因为匿名类出现于表达中,故它们应该非常短(20行或更少),以增强程序的可读性

  匿名类的常见用法:

  匿名类的一通常用法是创建一个函数对象(function object),如:
//typical use of an anonymous class
//Arrays.sort(args,new comparator(){
public int compare(Object o1,Object o2){
return ((String)o1).length()-((String)o2).length();
}
});

另一个常见用法是创建一个过程对象(process object),如Thread、Runnable、TimerTasker实例。

在一个静态工厂方法的内部(参见第16条)

用在复杂的类型安全枚举类型(它要求为每个实例提供单独的子类)
例子:
//typical use of a public static member class
public class Calculator{
public static abstract class Operation{
private final String name;
Operation(String name){ this.name=name;}
public String toString(){ return this.name};
//perform arithmetic op represented by this constant
abstract double eval(double x,double y);

//doubly nested anonymous classes
public static final Operation PLUS = new Operation("+"){
double eval(double x,double y){return x+y;}
}
public static final Operation MINUS= new Operation("-"){
double eval(double x,double y){return x-y;}
}
public static final Operation TIMES= new Operation("*"){
double eval(double x,double y){return x*y;}
}
public static final Operation DIVIDE=new Operation("/"){
double eval(double x,double y){return x/y;}
}
}
//Return hte results of the specified calculation
public double calculate(double x,Operation op,double y){
return op.eval(x,y);
}
}

  局部类是四种嵌套类中最少使用的类。在任何“可以声明局部变量”的地方,都可以声明局部类。与成员类一样,局部类有名字,可以被重复使用。与匿名类一样,当且仅当局部类被用于非静态环境下的进修,它们才有外围实例。与匿名类一样,它必须非简短,以不会损害外围方法或者初始化器的可读性。
  总之,如果一个嵌套类需要在单个方法之外仍是可见的,或者它太长了,不适合于放在一个方法内部,则应该使用成员类。如果成员类的每个实例都需要一个指向其外围的实例的引用,则把成员类做成非静态的;否则做成静态的。假设一个嵌套类属于一方法的内部,如果只需要在一个地方创建它的实例,并且已经有了一个预先存在的类型可以说明这个类的特征,则把它做成匿名类;否则变成局部类。

 


读《Effective java 中文版》(20)
第19条:用类代替结构
  Java语言中让一个类退化到只包含一些公开的数据域,这样的类与C语言的结构类似,如:
//degenerate classes like this should not be public
class Point{
public float x;
public float y;
}
  这样的类,抛弃了所有数据封装的优点,而你几乎不能对这样的API做什么改动或限制,应该代之以私有域和公有访问方法,如:
//Encapsulated structure class
class Point{
private float x;
private float y;

public Point (float x,float y){
this.x=x;
this.y=y;
}

public float getx(){return this.x;}
public float gety(){return this.y;}
public void setx(float x){this.x=x;}
public void sety(float y){this.y=y;}
}
  JAVA平台库中,有几个类违反了"公有类不应该直接暴露数据域“的告诫,如java.awt中的Point和Dimension。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值