建立和销毁对象
一、静态工厂方法代替构造器
- 静态工厂方法有名称,能确切地描述正被返回的对象。
- 没必要每次调用都建立一个新的对象。
- 能够返回原返回类型的任何子类对象。
- 建立参数化类型实例时更加简洁,好比调用构造 HashMap 时,使用
Map<String,List<String> m = HashMap.newInstance()
,与 Map<String,List<String>m> = new HashMap<String,List<String>>();
。
二、遇到多个构造器参数时要考虑用构建器
- 静态工厂和构造器不能很好地扩展到大量的可选参数。
- JavaBean 模式下使用 setter 来设置各个参数,没法仅经过检验构造器参数的有效性来保证一致性,会试图使用不一致状态的对象。
- Builder 的建造者模式:使用必须的参数调用构造器,获得一个 Builder 对象,再在 builder 对象上调用相似 setter 的方法设置各个可选参数,最后调用无参的 build 方法生成不可变对象,new Instance.Builder(必须参数).setter(可选参数).build()。
- Builder 模式让类的建立和表示分离,使得相同的建立过程能够建立不一样的表示。
三、避免建立没必要要的对象
- 对于 String 类型,
String s = new String("")
每次执行时都会建立一个新的实例,而使用 String s = ""
则不会,由于对于虚拟机而言,包含相同的字符串字面常量会重用,而不是每次执行时都建立一个新的实例。
- 优先使用基本类型而不是装箱的基本类型,避免无心识的自动装箱。
四、消除过时的对象引用
- 缓存时优先使用 WeakHashMap,LinkedHashMap 这些数据结构,及时清掉没用的项。
- 显示取消监听器和回调,或进行弱引用。
对于全部对象都通用的方法
五、覆盖 equals
- 若是类具备本身特有的"逻辑相等",但超类尚未覆盖 equals 以实现指望的行为。
-
高质量equals的方法git
- 使用 == 操做符检查”参数是否为这个对象的引用“。
- 使用 instanceof 操做符检查“参数是否为正确的类型”。
- 把参数转换成正确的类型。
- 对于该类中的每一个关键域,检查参数中的域是否与该对象中对应的域相匹配。
- 不要将 equals 声明的 object 对象替换为其余的类型,由于这样是无法覆盖 Object.equals,只是提供了一个重载。
六、覆盖 equals 时老是覆盖 hashCode
- 相等的对象必须具备相等的散列码,若是没有一块儿去覆盖 hashcode,则会致使俩个相等的对象未必有相等的散列码,形成该类没法结合全部基于散列的集合一块儿工做。
七、老是覆盖 toString
- Object 提供的 toString,实现是类名+@+散列码的无符号十六进制。
- 本身覆盖的 toString,返回对象中包含的全部值得关注的信息。
- 不足:当类被普遍使用,一旦指定格式,那就会编写出相应的代码来解析这种字符串表示法,以及把字符串表示法嵌入持久化数据中,以后若改变这种表示法,则会遭到破坏。
八、考虑实现 Comparable 接口
- 若是类实现了comparable 接口,即可以跟许多泛型算法以及依赖该接口的集合实现协做,好比可使用 Array.sort 等集合的排序。
类和接口
九、使类和成员的可访问性最小化
- 隐藏内部实现细节,有效解耦各模块的耦合关系
-
访问级别github
- private:类内部才可访问
- package-private(缺省的):包内部的任何类可访问
- protected:声明该成员的类的子类以及包内部的类可访问
- public:任何地方都可访问
十、复合优于继承
- 继承打破了封装性,除非超类是专门为了扩展而设计的。超类若在后续的发行版本中得到新的方法,而且其子类覆盖超类中与新方法有关的方法,则可能会发生错误。
- 复合:在新的类中增长一个私有域,引用现有类。它不依赖现有类的实现细节,对现有类进行转发。
十一、接口优于抽象类
- 抽象类容许包含某些方法的实现,但为了实现由抽象类定义的类型,类必须成为抽象类的一个子类,且是单继承。
- 接口容许咱们构造非层次结构的类型框架,安全地加强类的功能。
- 对每一个重要的接口都提供一个抽象的骨架实现类,把接口和抽象类的优势结合(接口不能包含具体的方法,抽象类使用继承来增长功能)。它们为抽象类提供了实现上的帮助,但又不强加抽象类被用做类型定义时所特有的严格限制。
- 抽象类的演变比接口的演变要容易得多,在后续版本中在抽象类中始终能够增长新的具体方法,其抽象类的全部子类都将提供这个新的方法,而接口不行。
十二、接口只用于定义类型
- 当类实现接口时,接口充当能够引用这个类的实例的类型,为了任何其余目的而定义接口时不恰当的。
- 常量接口时对接口的不良使用。实现常量接口,会致使把这样的实现细节泄漏给该类的导出 API 中,当类再也不须要这些常量时,还必须实现这个接口以确保兼容性。若是非final类实现了该常量接口,它的全部子类的命名空间都将被接口中的常量污染。
1三、优先考虑静态成员类
- 静态成员类是最简单的嵌套类,能够当作普通的类,只是被声明在另外一个类的内部。
- 非静态成员类的每一个实例都隐含着与外部类的一个外部实例相关联。没有外部实例的状况下,是没法建立非静态成员类的实例。每一个非静态成员类的实例都包含一个额外的指向外部对象的引用,会致使外部实例在垃圾回收时仍然保留。
- 匿名类没有名字,在使用的同时被声明和实例化。当匿名类出如今非静态环境中时有外部实例,在静态环境中也不能拥有任何静态成员。匿名类必须保持简短,保持可读性。
- 局部类,在任何能够声明局部变量的地方声明局部类,有名字,在非非静态环境中定义才有外部实例,不能包含静态成员,同时必须保持简短。
枚举和注解
1四、用 enum 代替 int 常量
- 枚举类型是指由一组固定的常量组成合法值的类型,经过公有的静态 final 域为每一个枚举常量导出实例的类,没有构造器,是单例的泛型化。
- int 枚举模式在类型安全性和使用方便性没有任何帮助,打印的 int 枚举变量只是一个数字。
- String 枚举模式虽然提供了可打印的字符串,但会致使性能问题,还依赖于字符串的比较操做。
- 枚举类型能够经过 toString 将枚举转换成可打印的字符串,还容许添加任意的方法和域,并实现任意的接口。
- 性能缺点:装载和初始化枚举时会有空间和时间的成本。
方法
1五、检查参数的有效性
- 对于公有方法,用 Javadoc 的 @throw 标签在文档中说明违反参数限制时会抛出的异常。
- 对于未被导出的方法(私有的),可使用断言来检查参数。断言若是失败会抛出 AssertionException,若是没起到做用也不会有成本开销。
- 每当编写方法或构造器时,要考虑它的参数有哪些限制,应该把这些限制写到文档中,而且在方法体的开头处进行显示的检查。
1六、必要时进行保护性拷贝
- 对方法的每一个可变参数,或返回一个指向内部可变组件的引用时,须要进行保护性拷贝,避免在使用过程当中可变对象进行了修改。
- 保护性拷贝是在检查参数的有效性以前进行的,而且有效性检查是针对拷贝以后的对象。
1七、 慎用重载
- 重载方法的选择是静态的,选择工做时在编译时进行,彻底基于参数的编译时类型。
- 覆盖方法的选择是动态的,选择的依据是被调用方法所在对象的运行时类型。
- 不要导出俩个具备相同参数数目的重载方法,若是参数数目相同,则至少有一个对应的参数在俩个重载方法中具备根本不一样的类型,不然就应该保证,当传递一样的参数时,全部的重载方法的行为必须一致。
1八、返回零长度的数组或集合,而不是 null
- 对于返回 null 而不是零长度数组或集合的方法,几乎每次用到该方法时都须要进行 null 值的判断,这样很曲折同时很容易出错。
通用程序设计
1九、基本类型优于装箱基本类型
- 基本类型只有值,而装箱基本类型能够具备相同的值和不一样的同一性。对装箱基本类型运用 == 操做符几乎老是错误的。
- 基本类型只有功能完备的值,而每一个装箱基本类型除了它对应的基本类型的全部功能值外,还有个非功能值:null。当在一项操做中混合使用基本类型和装箱基本类型时,装箱基本类型会自动拆箱,若是 null 对象引用被自动拆箱,会获得空指针异常。
- 基本类型一般比装箱基本类型更节省时间和空间,装箱基本类型会致使高开销和没必要要的对象建立。
20、小心字符串链接的性能
- 字符串是不可变的,当俩个字符串链接时须要对其内容进行拷贝,链接 n 个字符串须要 n 的平方级时间。由于第 n 次拼接的字符串,须要 n-1 次的字符串和第 n 次的字符串拷贝,和他们拼接后的拷贝,这样 an - an-1 = n-1+1+n = 2n;这样能够获得 an = n*(n-1),及 O(N^2) 的拼接时间。
2一、经过接口引用对象
- 若是有合适的接口类型存在,那么对于参数、返回值、变量和域来讲,就都应该使用接口类型进行声明。如,
List<>vector = new Vector<>();List list = new ArrayList<>();
,这样程序会更加灵活,当更换实现时,所要作的只是改变构造器中的类。
- 若是没有合适的接口存在,彻底能够用类而不是类接口来引用对象。若是含有基类,则优先使用基类来引用这个对象而不是它的实现类。
异常
2二、只针对异常的状况才使用异常
- 异常是为了在异常状况下使用而设计的,不要将他们用于普通的控制流,而不要编写破事他们这么作的 API。
- 基于异常的循环模式不只模糊了代码的意图,下降了性能( JVM 不会对异常的代码块进行优化),并且它还不能保证正常工做。
2三、对可恢复的状况使用受检异常,对编程错误使用运行时异常
2四、抛出与抽象相对应的异常
- 当方法传递由低层抽象抛出的异常与所执行的任务没有明显联系时,会致使困扰且让实现细节污染了更高层 API。
- 更高层的实现应该捕获低层的异常,同时抛出能够按照高层抽象进行解释的异常(异常转译)。
2五、努力使失败保持原子性
- 失败原子性:失败的方法调用应该使对象保持在被调用以前的状态。
- 设计不可变对象,永远不会使已有的对象保持在不一致的状态中。
-
对于可变对象:编程
- 执行操做以前检查参数的有效性。
- 调整计算处理过程的顺序,使得任何可能失败的计算部分都在对象状态被修改以前发生。
- 编写一段恢复代码,由它来拦截操做过程当中发生的失败,以及对象回滚到操做开始以前的状态上,主要用于永久性的数据结构。
- 在对象的一份临时拷贝上执行操做,不破坏传入对象的状态。
并发
2六、同步访问
序列化
2七、谨慎地实现 Serializable 接口
- 一旦一个类被发布,就大大下降了“改变这个类的实现” 的灵活性。若接受了默认的序列化形式,而且之后要改变类的内部结构,会致使序列化形式的不兼容。其次序列化对应流的惟一标识符 UID,在没有显示声明序列版本 UID,那么改变类的信息,将产生新的序列版本 UID,破坏它的兼容性。
- 增长了出现 bug 和安全漏洞的可能性。反序列化机制中没有显示的构造器,很容易忘记要确保:反序列化过程必需要保证全部“由真正的构造器创建起来的约束关系”,而且不容许攻击者访问正在构造过程当中的对象的内部信息。
- 测试负担增长。当一个可序列号的类被修订时,须要检查“在新版本中序列化一个实例,而后再旧版本中反序列号”,反之亦然,这种测试不可自动构建,测试工做量与“可序列化的类的数量和发行版本号”的乘积成正比。
本文发表于我的博客:http://lavnfan.github.io/,欢迎指教。安全