Chapter2

Tip1 静态工厂方法代替构造器

公有的静态方法,只是一个返回类实例的静态方法。

静态工厂方法的优势:

优势一:

有名称,如果构造器本身没有正确的描述被返回的对象,具有适当名称的静态工厂方法会更加适用。由于一个类仅能提供一个构造器,程序员可能会调用错误的构造器。用静态方法代替构造器,可以避免构造器的错误使用。

优势二:

不必每次都返回新的对象,直接返回已经创建好的对象就行,适合单例模式、不可实例化、不可变的类,这样可以确保不生成两个相等的实例。

优势三:

(有点没理解好)可以返回原返回类型的任何子类型的对象。

服务提供者框架(Service Provider Framework)

多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现(通过重写服务类中的那个关键函数)。感觉上好像就是一个多态的使用而已,还是没理解到位?

下面是一个实现的例子:

这个例子大概就是,有好多service provider,它们可以提供不同的service。最一般的想法就是,一个provider类里面弄一个doService的方法,但这么做显然不太好,会有许多重复的代码。于是需要一个Service接口,里面至少要有一个doService方法,其他Provider再具体生成的时候都需要去继承这个接口来实现它们自己的doService方法。此外还需要有个Services的类来提供一些工具函数(API)让provider对Service的调用过程变得更方便。Services里面基本全是一些静态方法。比如生成新的Provider实例(这里就用到了上面提到的用静态方法代替构造器),根据已有的Provider实例名来返回实例,把一个新生成的实例注册到Service的管理器中(用一个map 保存实例名字字符串和实例对象的映射关系)

源码如下:

package chapter2_serviceprovider;

public interface Service {

//some methos to do

public void doService();

}

  1. package chapter2_serviceprovider;
  2. public interface Provider {
  3. Service newService();
  4. }
  5. package chapter2_serviceprovider;
  6. import java.util.Map;
  7. import java.util.concurrent.ConcurrentHashMap;
  8. // 1 provide a map to store the relationship of name and provider instance
  9. // 2 provide a registration api to excute the puts method of map
  10. // 3 provide Service access api to return the instance of provider from map
  11. public class Services {
  12. private Services(){}
  13. //providers is a map to store the
  14. //relation of the service name and the provider
  15. private static final Map<String,Provider>providers=
  16. new ConcurrentHashMap<String,Provider>();
  17. public static final String DEFAULT_PROVIDER_NAME="<def>";
  18. //provider registraion api
  19. public static void registerDefaultProvider(Provider p){
  20. registerProvider(DEFAULT_PROVIDER_NAME,p);
  21. }
  22. public static void registerProvider(String name,Provider p){
  23. providers.put(name, p);
  24. }
  25. //Service access api
  26. public static Service newInstance(){
  27. return newInstance(DEFAULT_PROVIDER_NAME);
  28. }
  29. public static Service newInstance(String name){
  30. Provider p=providers.get(name);
  31. if(p==null){
  32. throw new IllegalArgumentException(
  33. "No provider registered with name:"+name);
  34. }
  35. return p.newService();
  36. }
  37. }
  38. package chapter2_serviceprovider;
  39. public class testServiceProvider {
  40. //create different providers
  41. //every provider should realize the newService in their own way
  42. private static Provider DEFAULT_PROVIDER = new Provider() {
  43. public Service newService() {
  44. return new Service() {
  45. public void doService() {
  46. System.out.println("do the service in the default way") ;
  47. }
  48. };
  49. }
  50. };
  51. private static Provider SERVICE_WAYA = new Provider() {
  52. public Service newService() {
  53. return new Service() {
  54. public void doService() {
  55. System.out.println("do the service in waya") ;
  56. }
  57. };
  58. }
  59. };
  60. private static Provider SERVICE_WAYB = new Provider() {
  61. public Service newService() {
  62. return new Service() {
  63. public void doService() {
  64. System.out.println("do the service in wayb") ;
  65. }
  66. };
  67. }
  68. };
  69. public static void main(String[] args) {
  70. //regist the created Providers
  71. //namely create the maping betwwen the provider and its key name
  72. Services.registerDefaultProvider(DEFAULT_PROVIDER);
  73. Services.registerProvider("servicea",SERVICE_WAYA);
  74. Services.registerProvider("serviceb",SERVICE_WAYB);
  75. // create the newinstance
  76. Service s1=Services.newInstance();
  77. Service s2=Services.newInstance("servicea");
  78. Service s3=Services.newInstance("serviceb");
  79. //invoke the service
  80. s1.doService();
  81. s2.doService();
  82. s3.doService();
  83. }
  84. }

优势四

创建参数化实例的时候,可以更加简洁,这里的例子用的是集合中的例子

如果想要通过泛型创建一个Map比如

Map<String,List> m=new HashMap<String,List>()

这样可能显得很繁琐,尤其是使用迭代器循环的时候,可能里里外外要好多层。

如果HashMap提供一个类似的静态工厂方法(貌似目前还没有类似的方法)

Public static<K,V>HashMap<K,V>newinstance(){

return new HashMap<K,V>()

}

这样直接通过HashMap.newinstance()来生成新的实例,会方便许多。

在自己编写类似的工具类的时候,可以借鉴相关的思路,来简化程序

缺点一

光看有点是不对的,如果该类不含有共有的或者受保护的构造器,就不能被实例化。比如说Collections类,它里面全都是static方法和私有的构造器,因此不能被实例化。鼓励使用复合的继承的方式对其进行操作

缺点二

与其他静态方法实际上没有什么本质的区别。

总结

静态方法和共有构造器各有用处,静态方法通常情况下更合适。因此我们的第一反应应该是使用静态工厂,而非是使用共有的构造器。

Tip2 遇到多个构造器参数时,要考虑使用构造器

这部分主要介绍的是Builder模式

具体内容:

下面是一个实际的例子

  1. package chapter2_buildermodel;
  2. //using builder to create the instance of NutritionFacts
  3. public class NutritionFacts {
  4. private final int servingSize;
  5. private final int servings;
  6. private final int calories;
  7. private final int fat;
  8. private final int sodium;
  9. private final int carbohydrate;
  10. //initialize the parameters by a builder
  11. private NutritionFacts(Builder builder){
  12. servingSize=builder.servingSize;
  13. servings=builder.servings;
  14. calories=builder.calories;
  15. fat=builder.fat;
  16. sodium=builder.sodium;
  17. carbohydrate=builder.carbohydrate;
  18. }
  19. public void info(){
  20. System.out.println("the servingSize is "+servingSize);
  21. System.out.println("the servings is "+servings);
  22. System.out.println("the calories is "+calories);
  23. System.out.println("the fat is "+fat);
  24. System.out.println("the sodium is "+sodium);
  25. System.out.println("the carbohydrate is "+carbohydrate);
  26. }
  27. public static class Builder{
  28. //required parameters
  29. private final int servingSize;
  30. private final int servings;
  31. //optionl parameters (shuld provide a default value)
  32. private int calories=0;
  33. private int fat=0;
  34. private int carbohydrate=0;
  35. private int sodium=0;
  36. //every Builder method return this builder instance
  37. public Builder(int servingSize,int servings){
  38. this.servingSize=servingSize;
  39. this.servings=servings;
  40. }
  41. public Builder calories(int val){
  42. calories=val; return this;
  43. }
  44. public Builder fat(int val){
  45. fat=val; return this;
  46. }
  47. public Builder carbohydrate(int val){
  48. carbohydrate=val; return this;
  49. }
  50. public Builder sodium(int val){
  51. sodium=val; return this;
  52. }
  53. //return this initialized instance by the build method at last
  54. public NutritionFacts build(){
  55. //invoke the private constructor of NutritionFacts
  56. //this constructor use a builder instance to initaialze the parameter
  57. return new NutritionFacts(this);
  58. }
  59. }
  60. public static void main(String[] args) {
  61. //create the NutritionFacts instance by the Builder model
  62. NutritionFacts cocaCola=new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
  63. cocaCola.info();
  64. }
  65. }

这样处理起来,比那种使用多个构造方法的形式确实灵活好多,但是还是比较麻烦。在一些类似的脚本语言中,可能对于类似问题的处理会好一些,比如早python中,直接在函数声明的时候就能指定默认的参数。传参的时候也能用显式的方式指定到底是给哪个变量传递的。

总结

如果类的构造器或者静态工厂方法有多个参数,或者以后可能会拓展出多个参数出来,Builder模式是很推荐的选择,对参数的检验也变得更加灵活,更易于编写维护。

当然缺点就是代码可能会显得冗长,只有在多参数的时候才适合使用。

书上提供的另外两种方式是设置多个构造器,或者是使用JavaBean的方式,具体参考书上内容。

Tip3 用私有构造器或者枚举类型强化Singleton属性

单例模式(singleton)似乎是必须要掌握的一个设计模式了,好多时候只需要生成一个单独的实例,具体的可以参考之前整理的这个(http://www.cnblogs.com/Goden/p/3956803.html)

再补充一点,因为反射方式的存在,可以通过反射的方式调用私有的构造器生成新的实例,于是为了防止有人利用这一点恶意破坏程度,可以再构造函数中添加保护机制,如果实例被第二次创建的时候,就会抛出一个异常。

还有就是单例的序列化的问题

之前那个是比较常见的一个单例实现,通过枚举的方式实现单例,看起来似乎更加简洁,并且单元素的枚举类型已成为实现singleton的最佳方法。(枚举这里相关内容具体参考第6章)

枚举就是单例的泛型化,这个说的也是很好,应该加强理解。在一个枚举类中,定义的每个instance都是单例的。

Tip4 私有构造器强化类的不可实例化的属性

有些类是不希望被实例化的,比如某些工具类,如java.lang.Math,或者java.util.Arrays,再或者java.util.Collections等等,对于这种类型的类,通常会提供给一个私有的构造器,来防止这个类被实例化。通常在这种私有的构造器上加一个注释,就是明确的指出,这个类不希望被实例化,比如// supperss default constructor for noninstamtiability

Tip5 避免创建不必要的对象

这个比较容易理解,就是在方法中尽量减少 Object o=new Object()这种语句,这样的话,每次方法被调用都会创建一个新的对象出来,到底是效率低了好多。

根据前面的几条,能使用静态方法的,最好还是使用静态方法。

下面这个例子,是好方法和差方法的对比:

package chapter2_objectReuse;

  1. import java.util.Calendar;
  2. import java.util.Date;
  3. import java.util.TimeZone;
  4. //check if the birth date betwwen baby boomer
  5. public class Personold {
  6. private final Date birthDate=null;
  7. //other fields, methods, and constructor omitted
  8. //Bad Way (the Calendar instance is created every time when the method is invoked):
  9. public boolean isBabyBoomer(){
  10. Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
  11. gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
  12. Date boomStart=gmtCal.getTime();
  13. gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
  14. Date boomEnd=gmtCal.getTime();
  15. return birthDate.compareTo(boomStart)>=0&&birthDate.compareTo(boomEnd)<0;
  16. }
  17. }
  18. package chapter2_objectReuse;
  19. import java.util.Calendar;
  20. import java.util.Date;
  21. import java.util.TimeZone;
  22. public class Personnew {
  23. private final Date birthDate=null;
  24. //other fields, methods, and constructor omitted
  25. //appoint the start time and the end time
  26. private static final Date BOOM_START=null;
  27. private static final Date BOOM_END=null;
  28. // a better way to initialize the parameter
  29. static{
  30. Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
  31. gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
  32. Date BOOM_START=gmtCal.getTime();
  33. gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
  34. Date BOOM_END=gmtCal.getTime();
  35. }
  36. public boolean isBabyBoomer(){
  37. return birthDate.compareTo(BOOM_START)>=0&&birthDate.compareTo(BOOM_END)<0;
  38. }
  39. }

第一个例子中,每次都要新生成一个Calemdar类,影响效率,后面的一个实现中,通过static块的方式,将参数都初始化好存了下来,这样比较好。

后面还说了适配器的情形(有点没看懂)还有自动装箱的情况,优先使用基本类型而不是自动装箱类型,比如计算long的时候写成了Long,这样的话,实例就会被自动装箱,生成许多不必要的实例,影响开销。

现在有点晕了,后面又说,这种避免对象创建的策略也不是绝对的,小对象的创建和回收都是比较廉价的,如果通过创建附加的对象能够提升程序的清晰性,这也是推荐的,看来还是具体情况具体分析了。

Tip6 消除过期引用

这一个tip主要是讨论一个内存泄露的问题。
内存泄露可以由过期引用引起,所谓过期引用就是指永远不会再被接触的引用。
比如下面的这个例子。

package chapter2_overduereference;

  1. import java.util.Arrays;
  2. import java.util.EmptyStackException;
  3. public class Stack {
  4. private Object[]elements;
  5. private int size=0;
  6. private static final int DEFAULT_INITIAL_CAPACITY=16;
  7. public Stack(){
  8. elements=new Object[DEFAULT_INITIAL_CAPACITY];
  9. }
  10. public void push(Object e){
  11. ensureCapacity();
  12. elements[size++]=e;
  13. }
  14. public Object pop(){
  15. if(size==0)
  16. throw new EmptyStackException();
  17. return elements[--size];
  18. }
  19. //ensure space for one more elements
  20. //roughly doubling the capacity each time the array need grow
  21. private void ensureCapacity(){
  22. if(elements.length==size)
  23. elements=Arrays.copyOf(elements, 2*size+1);
  24. }
  25. }

看起来代码也没什么错误,关键是在pop函数的地方,如果先入栈好多,再出栈,那么先前的引用就没有被释放掉,因为对于垃圾回收器来说,前面已经不用的数组区域和后面正在使用的数组区域是没有区别的,因此要用手工的方式使得不用的引用指向null才能保证被垃圾回收期器回收(与垃圾回收器的机制有关?)。这主要是因为stack是自己管理内存的。

好的做法应该是这样:

public Object pop(){

if(size==0)

throw new EmptyStackException();

Object result= elements[--size];

elements[size]=null;

return result;

}



事实上在官方的Stack实现中,也是这样做的:

在pop方法中,调用了一个removeElementAt方法来去掉指定位置的元素,在这个方法的最后,也把去掉的那个元素的引用赋成了null。

还有两种内存泄露的来源,由于没有实例,理解的不是太好。

缓存

监听器和其他回调

内存泄露一般不容易被发现,应该提前就预测到内存泄露可能会发生的地方。

Tip 7 避免使用终结方法

这个tip还是蛮重要的,算是有一点亲身体会。特别是使用finally来关闭连接状态的时候,无论是socket或者是是数据库的连接,并且还是那种多个线程的环境下。
这个后面介绍的部分还是有点没看懂。

除非是作为安群网,或者是为了终止非关键的本地资源,否则请不熬使用终结方法。在一些很少见的情况下,既然使用了终结方法,就要牢记调用super.finalize方法。(这一部分不太了解)如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要把终结方法与共有的非final类关联起来,请考虑使用终结方法的守卫者,以确保即使使子类的终结方法未能调用super.finalize该终结方法也会被执行。

chapter2的更多相关文章

  1. 《深入PHP与jQuery开发》读书笔记——Chapter2

    Pro PHP and jQuery Chapter2 总结 1.理解jQuery脚本的基本行为 jQuery事实上沿用了JavaScript的那一套东西,几乎所有方法都支持链式调用,也就是说方法可以 ...

  2. ###《Effective STL》--Chapter2

    点击查看Evernote原文. #@author: gr #@date: 2014-09-15 #@email: forgerui@gmail.com Chapter2 vector和string T ...

  3. Learning WCF Chapter2 Data Contracts

    A data contract describes how CLR types map to XSD schema definitions. Data contracts are the prefer ...

  4. Learning WCF Chapter2 Service Contracts

    A service contract describes the operations supported by a service,the message exchange pattern they ...

  5. <Programming Collective Intelligence> Chapter2:Making Recommendations

    <Programming Collective Intelligence> Chapter2:Making Recommendations 欧几里得距离评价 皮尔逊相关度评价 它相比于欧几 ...

  6. Invalid bound statement (not found): com.shizongger.chapter2.mapper.UserMapper.insertUser 解决方案

    在配置MyBatis时报错信息如下: Invalid bound statement (not found): com.shizongger.chapter2.mapper.UserMapper.in ...

  7. 解读经典《C#高级编程》第七版 Page50-68.核心C#.Chapter2

    前言 本篇讲述Main方法,控制台,注释,预处理指令,编程规范等.这些概念比较琐碎,为避免长篇大论,主要以列举要点的方式来说明. 01 Main方法 Main方法并不是所有应用类型的入口方法,它只是控 ...

  8. 解读经典《C#高级编程》第七版 Page45-50.核心C#.Chapter2

    前言 本篇讲述枚举和名称空间. 01 枚举 首先需要明确枚举的概念:枚举是用户定义的整数类型.使用枚举的目标是,使用一组容易记忆的名称,来使得代码更容易编写和维护. 我们对比枚举的定义和类的定义,会发 ...

  9. 解读经典《C#高级编程》第七版 Page38-45.核心C#.Chapter2

    前言 控制流是语言中最基础的部分,我们不谈具体的细节,只讲讲一些关键和有趣的点. 01 流控制 条件语句:if, else if, else if语句的使用非常值得细讲,如何是好的使用习惯.有一点非常 ...

  10. 解读经典《C#高级编程》第七版 Page32-38.核心C#.Chapter2

    前言 接下来讲讲预定义数据类型.关于数据类型,其实是非常值得透彻研究的. 01 预定义数据类型 值类型和引用类型 C#将把数据类型分为两种,值类型和引用类型,值类型存储在堆栈上,引用类型存储在托管堆上 ...

随机推荐

  1. logname - 显示用户登录名

    总览 (SYNOPSIS) logname [OPTION]... 描述 (DESCRIPTION) 显示 当前用户 的 名字. --help 显示 帮助信息, 然后 结束. --version 显示 ...

  2. PAT Advanced 1036 Boys vs Girls (25 分)

    This time you are asked to tell the difference between the lowest grade of all the male students and ...

  3. 服务器上搭建jupyter notebook

    参考:https://zhuanlan.zhihu.com/p/44405596 https://blog.csdn.net/cvMat/article/details/79351420 遇到的问题 ...

  4. tf Dataset API

    https://zhuanlan.zhihu.com/p/30751039 https://zhuanlan.zhihu.com/p/37106443 关于其中shuffle时的buffer_size ...

  5. 如何设置Linux虚拟机的IP地址

    本文会详细的解释如何在Linux虚拟机下设置IP地址 我的虚拟机是CentOS 首先,打开你的虚拟机 1.修改主机名 修改完主机名之后,别忘了用:wq命令保存退出 然后我们来设置虚拟机的IP地址 首先 ...

  6. layui token 过期 重新登陆

    这个方法你要全局设置     //jquery全局配置 $.ajaxSetup({     cache: false,     crossDomain: true,       headers :{' ...

  7. python基本数据类型常用方法

    python基本数据类型 1.整型 1.1 int 1.2 bit_lenght # 当前数字的二进制位数,至少用n位表示 r = age.bit_length() >>> a = ...

  8. 前端之JQuery:JQuery文档操作

    jquery之文档操作 一.相关知识点总结1.CSS .css() - .css("color") -> 获取color css值 - .css("color&qu ...

  9. 对15号夏壹队的TD信息通使用体验

    对夏壹队的APP的用户使用体验:首先下载的时候看到这个APP的大小是6M多点不算很大感觉还不错,但是占内存不大也说明了一个问题,它不会有很多的功能. 图标是一个蜜蜂,打开后会有一个登陆界面,一开始没有 ...

  10. ubuntu16.04 安装samba

    安装samba 1.更新当前软件 sudo apt-get upgrade sudo apt-get update sudo apt-get dist-upgrade 2.执行 sudo apt-ge ...