使类和成员的可访问能力最小化
public表示这个类在任何范围都可用。
protected表示只有子类和包内的类可以使用
private-package(default)表示在包内可用
private表示只有类内才可以用
在设计的时候应该尽可能的使每一个类或者成员不被外界所访问。在设计一个类的时候应该尽量的按照4321得顺序设计。如果一个类只是被另一个类使用,那么应该考虑把它设计成这个类的内部类。通常 public的类不应该有public得字段,不过我们通常会用一个类来定义所有的常量,这是允许的。不过必须保证这些字段要么是基本数据类型要么引用指向的对象是不可修改的。不然他们将可能被修改。例如下面的定义中data就是不合理的,其他人可以改变数组中的内容,有安全漏洞,后面两个没有问题。
解决data安全隐患的方法有两种:
①将公有方法替换为一个私有数组,以及一个公有的非可变列表
②把公有的数组替换为一个公有的方法,它返回私有数组的一份拷贝
总之,应该防止把任何杂散的累接口和成员变成API的一部分,除了公有静态final域的特殊情形之外,公有类不应该包含公有域,并且确保有静态final域所引用的对性是不可变的。
支持非可变性
为了使一个类成为非可变类,要遵循下面五条规则:
①不要提供任何会修改对像的方法;
②保证没有可被子类改写的方法;
③使所有的域都是final的;
④使所有的域都成为私有的;
⑤保证对于任何可变组件的互斥访问。(如果一个类指向可变对象的域,则必须确保该类的客 户无法活得指向这些对象的引用,并且永远不要用客户提供的对象引用来初始化这样的域,也不要在任何一个访问方法中返回该对象的引用);
以上规则比真正的要求强了一点,为了提高性能可以有所方式,如:保证没有一个方法能够对对象的状态产生外部可见的改变,许多非可变的类拥有一个或者多个非final的冗余域,把一个开销昂贵的计算结果缓存在这些域中。
非可变对象本质上是线程安全的,它们不要求同步。非可变对象可以被自由地共享。你不仅可以共享非可变对象,甚至也可以共享它们的内部信息。非可变对象为其他对象--无论是可变的还是不可变的--提供了大量的构件。
非可变类真正唯一的缺点是,对于每一个不同的值都要求一个单独的对象。String就是这样的。通常有个解决的办法就是提供一个帮助类来弥补,例如StringBuffer类。
如果一个类不能被做成非可变类,那么你仍然应该尽可能地限制它的可变性。
构造函数应该创建完全初始话的对象,所有的约束关系应该在这时候建立起来,构造函数不应该吧“只构造看一部分的实例”传递给其他的方法,不应该在后者函数之外子踢狗一个公有的初始化方法。
使一个类成为非可变类有如下三种方法:
①将一个类声明为final类型的;
②让该类中的每一个方法都成为final的,这种方法的好处在于其子类可以继续扩展新的方法;
③把类的构造函数声明为私有的或者包级私有的,增加静态工厂方法,来代替公有的构造函数;(该方法虽然不常用,但却是最值得推荐的)
复合优先于继承
实现代码重用最重要的办法就是继承,但是继承破坏了封装,导致软件的键壮性不足。如果子类继承了父类,那么它从父类继承的方法就依赖父类的实现,一旦他改变了会导致不可预测的结果。如果子类和超类在不同的包中,并且超类并不是为了扩展而设计的,那么继承会导致脆弱性。作者介绍了InstrumentedHashSet作为反例进行说明,原因就是没有明白父类的方法实现。作者给出的解决办法是通过化合来代替继承,尤其是当存在一个适当的接口来实现一个包装类的时候,用包装类和转发方法来解决问题。把想扩展的类作为本类的一个private final得成员变量。把方法参数传递给这个成员变量并得到返回值。这样做的缺点是这样的类不适合回掉框架。继承虽然好,我们却不应该滥用,只有我们能确定它们之间是is-a得关系的时候才使用。
要么专门为继承而设计,并给出文档说明,要么禁止继承
对并没有文档说明的类进行继承是非常危险的,它的公有方法有可能被改变。在设计一个专门用来继承的类时必须注意以下几点(不适用于final类):
①必须精确地描述改写每个方法带来的影响,虽然这样的描述违法了文档格言“好的API文档应该描述一个方法做了什么工作,而不是描述它如何做”,但这也是继承破坏了程序的封装性而导致的。
②允许继承的类的构造函数一定不能调用可被改写的方法,无论是直接进行还是间接进行。因为超类的构造函数会在子类的构造函数之前运行,所以子类中改下版本的方法将会在子类的构造函数运行之前就被调用。如:
“函数一定不能调用可被改写的方法”的实现方式:把每个可改写的方法的代码体移到一个私有的“辅助方法”中,并且让每个可改写的方法调用他的私有辅助方法,然后用“直接调用可改写方法的私有辅助方法”来代替“可改写方法的每个自用调用”。
在为了继承而设计类的时候,不推荐实现Cloneable和Serializable接口。clone和readObject方法在行为上和构造函数相似——“一定不能调用可被改写的方法,无论是直接进行还是间接进行”,对于readObject方法,子类中改写版本的方法将子类的状态被反序列化之前运行,而对于clone方法,改写版本的方法将在子类的clone方法有机会修正被克隆对象的状态之前被运行,无论哪种情形,它们都会不可避免的导致程序失败。
对于既不是final类,也不是为了子类化而设计和编写文档的普通类而言,防止出现问题的最好方法是禁止子类化,方法有两种:
①把这个类声明为final的。
②把所有的构造函数变成私有的,或者包级私有的,并增加一个公有的静态工厂方法来代替构造函数。
继承的另一个替代方案就是利用包装类模式
接口优于抽象类
接口和抽象类的区别:
①抽象类允许包含默写方法的实现,而接口是不允许的;
②一个类要实现抽象类,它必须成为抽象类的一个子类,而实现接口的类只要定义了所要求的方法,并遵守通用的约定,不管这个类位于类层次的哪个地方;
接口可以构造出非层次结果的类型框架,比如一个接口可以继承多个其他的接口。还可以安全地增加一个类的功能。
当然,也可以把接口和抽象类的有点结合起来,对于你期望导出的每一个总要的接口,头提供一个抽象的骨架实现类,这样,接口的作用仍然是定义类型,骨架实现类负责所以与接口实现相关的工作。
抽象类也有明显的优势,它可以在一个类的后续的版本中方便的增加一个新的方法,但不影响到其他相关的类,而接口则做不到这一点。
总之,接口通常是定义具有多个实现类型的最佳途径(例外的情况:当演化的容易性比灵活性和功能更为重要的时候,应该使用抽象类来定义类型,但也必须理解抽象类的局限性,并确保可以接受这些局限性),如果已经导出了一个重要的接口,那么,也应该考虑同时提供一个骨架实现类。最后,应该尽可能的谨慎设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。
接口只是被用于定义类型
接口只是用来定义一个类型,不要把接口用来做其他的事情(如在接口中定义常量,这种常量接口模式是对接口的不良使用)。
如果要导出常量,可以有以下几种方式:
①如果这些常量与某个已有的类或者接口有着紧密的联系,则可以把常量添加到这个类或者接口中。
②定义一个类型安全的枚举类,把这些常量看做枚举类型的成员。
③使用一个可以实例化的工具类(构造函数设为privat类型)来导出这些常量。
优先考虑静态成员类
嵌套类只为它的外围类提供服务。
嵌套类分为四种:静态成员类、非静态成员类、匿名类和局部类(后面三种称为内部类)
如果一个嵌套类的实例可以在它外类类的实例之外独立存在,则这个嵌套类应该设置成静态成员类(即:如果你声明的成员类不要访问外围实例,那么应该把static修饰符放到成员类的声明中)。
匿名类的用法:
①创建一个函数对象;
②创建过程对象;
③在静态工厂方法的内部使用;
④在复杂的类型安全枚举类型(它要求为每个实例提供单独的子类)中,用于公有的静态final域的初始化器中;
总结:四种不同的嵌套类都有自己不同的用途,如果一个嵌套类需要在单个方法之外任然是可见的,或者它太长,不适合放在一个方法内部,那么应该使用成员类,如果成员类的每个实例都需要一个指向其外围实例的引用,则把成员类做成非静态的;否则就做成静态的,假设一个嵌套类属于一个方法的内部,如果你只需要在一个地方创建它的实例,并且已经有了一个预先存在的类型可以说明这个类的特征,则把它做成匿名类;否则就做成局部类。
public表示这个类在任何范围都可用。
protected表示只有子类和包内的类可以使用
private-package(default)表示在包内可用
private表示只有类内才可以用
在设计的时候应该尽可能的使每一个类或者成员不被外界所访问。在设计一个类的时候应该尽量的按照4321得顺序设计。如果一个类只是被另一个类使用,那么应该考虑把它设计成这个类的内部类。通常 public的类不应该有public得字段,不过我们通常会用一个类来定义所有的常量,这是允许的。不过必须保证这些字段要么是基本数据类型要么引用指向的对象是不可修改的。不然他们将可能被修改。例如下面的定义中data就是不合理的,其他人可以改变数组中的内容,有安全漏洞,后面两个没有问题。
public class Con
{
public static final int[] data = {1,2,3};// it is bad
public static final String hello = "world";
public static final int i = 1;
}
public class Con { public static final int[] data = {1,2,3};// it is bad public static final String hello = "world"; public static final int i = 1; }
解决data安全隐患的方法有两种:
①将公有方法替换为一个私有数组,以及一个公有的非可变列表
private static final Type[] PRIVATE_VALUES = {...};
public static final List VALUES = Collections.unmodifiableLis(Arrays.asList(PRIVATE_VALUES));
private static final Type[] PRIVATE_VALUES = {...}; public static final List VALUES = Collections.unmodifiableLis(Arrays.asList(PRIVATE_VALUES));
②把公有的数组替换为一个公有的方法,它返回私有数组的一份拷贝
private static final Type[] PRIVATE_VALUES = {...};
public static final Type[] values(){
return (Type[])PRIVATE_VALUES.clone();
}
private static final Type[] PRIVATE_VALUES = {...}; public static final Type[] values(){ return (Type[])PRIVATE_VALUES.clone(); }
总之,应该防止把任何杂散的累接口和成员变成API的一部分,除了公有静态final域的特殊情形之外,公有类不应该包含公有域,并且确保有静态final域所引用的对性是不可变的。
支持非可变性
为了使一个类成为非可变类,要遵循下面五条规则:
①不要提供任何会修改对像的方法;
②保证没有可被子类改写的方法;
③使所有的域都是final的;
④使所有的域都成为私有的;
⑤保证对于任何可变组件的互斥访问。(如果一个类指向可变对象的域,则必须确保该类的客 户无法活得指向这些对象的引用,并且永远不要用客户提供的对象引用来初始化这样的域,也不要在任何一个访问方法中返回该对象的引用);
以上规则比真正的要求强了一点,为了提高性能可以有所方式,如:保证没有一个方法能够对对象的状态产生外部可见的改变,许多非可变的类拥有一个或者多个非final的冗余域,把一个开销昂贵的计算结果缓存在这些域中。
非可变对象本质上是线程安全的,它们不要求同步。非可变对象可以被自由地共享。你不仅可以共享非可变对象,甚至也可以共享它们的内部信息。非可变对象为其他对象--无论是可变的还是不可变的--提供了大量的构件。
非可变类真正唯一的缺点是,对于每一个不同的值都要求一个单独的对象。String就是这样的。通常有个解决的办法就是提供一个帮助类来弥补,例如StringBuffer类。
如果一个类不能被做成非可变类,那么你仍然应该尽可能地限制它的可变性。
构造函数应该创建完全初始话的对象,所有的约束关系应该在这时候建立起来,构造函数不应该吧“只构造看一部分的实例”传递给其他的方法,不应该在后者函数之外子踢狗一个公有的初始化方法。
使一个类成为非可变类有如下三种方法:
①将一个类声明为final类型的;
②让该类中的每一个方法都成为final的,这种方法的好处在于其子类可以继续扩展新的方法;
③把类的构造函数声明为私有的或者包级私有的,增加静态工厂方法,来代替公有的构造函数;(该方法虽然不常用,但却是最值得推荐的)
复合优先于继承
实现代码重用最重要的办法就是继承,但是继承破坏了封装,导致软件的键壮性不足。如果子类继承了父类,那么它从父类继承的方法就依赖父类的实现,一旦他改变了会导致不可预测的结果。如果子类和超类在不同的包中,并且超类并不是为了扩展而设计的,那么继承会导致脆弱性。作者介绍了InstrumentedHashSet作为反例进行说明,原因就是没有明白父类的方法实现。作者给出的解决办法是通过化合来代替继承,尤其是当存在一个适当的接口来实现一个包装类的时候,用包装类和转发方法来解决问题。把想扩展的类作为本类的一个private final得成员变量。把方法参数传递给这个成员变量并得到返回值。这样做的缺点是这样的类不适合回掉框架。继承虽然好,我们却不应该滥用,只有我们能确定它们之间是is-a得关系的时候才使用。
要么专门为继承而设计,并给出文档说明,要么禁止继承
对并没有文档说明的类进行继承是非常危险的,它的公有方法有可能被改变。在设计一个专门用来继承的类时必须注意以下几点(不适用于final类):
①必须精确地描述改写每个方法带来的影响,虽然这样的描述违法了文档格言“好的API文档应该描述一个方法做了什么工作,而不是描述它如何做”,但这也是继承破坏了程序的封装性而导致的。
②允许继承的类的构造函数一定不能调用可被改写的方法,无论是直接进行还是间接进行。因为超类的构造函数会在子类的构造函数之前运行,所以子类中改下版本的方法将会在子类的构造函数运行之前就被调用。如:
package com.ilibaba.test;
import java.util.*;
public class SubClass extends SuperClass {
private final Date date;
public SubClass() {
date = new Date();
}
public void m() {
System.out.println(date);
}
public static void main(String[] args) {
SubClass s = new SubClass();
s.m();
}
}
class SuperClass {
public SuperClass() {
m();
}
public void m() {
System.out.println("uuu");
}
}
package com.ilibaba.test; import java.util.*; public class SubClass extends SuperClass { private final Date date; public SubClass() { date = new Date(); } public void m() { System.out.println(date); } public static void main(String[] args) { SubClass s = new SubClass(); s.m(); } } class SuperClass { public SuperClass() { m(); } public void m() { System.out.println("uuu"); } }
“函数一定不能调用可被改写的方法”的实现方式:把每个可改写的方法的代码体移到一个私有的“辅助方法”中,并且让每个可改写的方法调用他的私有辅助方法,然后用“直接调用可改写方法的私有辅助方法”来代替“可改写方法的每个自用调用”。
在为了继承而设计类的时候,不推荐实现Cloneable和Serializable接口。clone和readObject方法在行为上和构造函数相似——“一定不能调用可被改写的方法,无论是直接进行还是间接进行”,对于readObject方法,子类中改写版本的方法将子类的状态被反序列化之前运行,而对于clone方法,改写版本的方法将在子类的clone方法有机会修正被克隆对象的状态之前被运行,无论哪种情形,它们都会不可避免的导致程序失败。
对于既不是final类,也不是为了子类化而设计和编写文档的普通类而言,防止出现问题的最好方法是禁止子类化,方法有两种:
①把这个类声明为final的。
②把所有的构造函数变成私有的,或者包级私有的,并增加一个公有的静态工厂方法来代替构造函数。
继承的另一个替代方案就是利用包装类模式
接口优于抽象类
接口和抽象类的区别:
①抽象类允许包含默写方法的实现,而接口是不允许的;
②一个类要实现抽象类,它必须成为抽象类的一个子类,而实现接口的类只要定义了所要求的方法,并遵守通用的约定,不管这个类位于类层次的哪个地方;
接口可以构造出非层次结果的类型框架,比如一个接口可以继承多个其他的接口。还可以安全地增加一个类的功能。
当然,也可以把接口和抽象类的有点结合起来,对于你期望导出的每一个总要的接口,头提供一个抽象的骨架实现类,这样,接口的作用仍然是定义类型,骨架实现类负责所以与接口实现相关的工作。
抽象类也有明显的优势,它可以在一个类的后续的版本中方便的增加一个新的方法,但不影响到其他相关的类,而接口则做不到这一点。
总之,接口通常是定义具有多个实现类型的最佳途径(例外的情况:当演化的容易性比灵活性和功能更为重要的时候,应该使用抽象类来定义类型,但也必须理解抽象类的局限性,并确保可以接受这些局限性),如果已经导出了一个重要的接口,那么,也应该考虑同时提供一个骨架实现类。最后,应该尽可能的谨慎设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。
接口只是被用于定义类型
接口只是用来定义一个类型,不要把接口用来做其他的事情(如在接口中定义常量,这种常量接口模式是对接口的不良使用)。
如果要导出常量,可以有以下几种方式:
①如果这些常量与某个已有的类或者接口有着紧密的联系,则可以把常量添加到这个类或者接口中。
②定义一个类型安全的枚举类,把这些常量看做枚举类型的成员。
③使用一个可以实例化的工具类(构造函数设为privat类型)来导出这些常量。
优先考虑静态成员类
嵌套类只为它的外围类提供服务。
嵌套类分为四种:静态成员类、非静态成员类、匿名类和局部类(后面三种称为内部类)
如果一个嵌套类的实例可以在它外类类的实例之外独立存在,则这个嵌套类应该设置成静态成员类(即:如果你声明的成员类不要访问外围实例,那么应该把static修饰符放到成员类的声明中)。
匿名类的用法:
①创建一个函数对象;
②创建过程对象;
③在静态工厂方法的内部使用;
④在复杂的类型安全枚举类型(它要求为每个实例提供单独的子类)中,用于公有的静态final域的初始化器中;
总结:四种不同的嵌套类都有自己不同的用途,如果一个嵌套类需要在单个方法之外任然是可见的,或者它太长,不适合放在一个方法内部,那么应该使用成员类,如果成员类的每个实例都需要一个指向其外围实例的引用,则把成员类做成非静态的;否则就做成静态的,假设一个嵌套类属于一个方法的内部,如果你只需要在一个地方创建它的实例,并且已经有了一个预先存在的类型可以说明这个类的特征,则把它做成匿名类;否则就做成局部类。