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();

}

package chapter2_serviceprovider;

public interface Provider {

	Service newService();
} package chapter2_serviceprovider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; // 1 provide a map to store the relationship of name and provider instance
// 2 provide a registration api to excute the puts method of map
// 3 provide Service access api to return the instance of provider from map
public class Services {
private Services(){} //providers is a map to store the
//relation of the service name and the provider
private static final Map<String,Provider>providers=
new ConcurrentHashMap<String,Provider>(); public static final String DEFAULT_PROVIDER_NAME="<def>"; //provider registraion api
public static void registerDefaultProvider(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME,p);
} public static void registerProvider(String name,Provider p){
providers.put(name, p);
} //Service access api
public static Service newInstance(){
return newInstance(DEFAULT_PROVIDER_NAME); } public static Service newInstance(String name){
Provider p=providers.get(name);
if(p==null){
throw new IllegalArgumentException(
"No provider registered with name:"+name);
}
return p.newService();
} } package chapter2_serviceprovider;
public class testServiceProvider { //create different providers
//every provider should realize the newService in their own way
private static Provider DEFAULT_PROVIDER = new Provider() {
public Service newService() {
return new Service() { public void doService() {
System.out.println("do the service in the default way") ;
}
};
}
}; private static Provider SERVICE_WAYA = new Provider() {
public Service newService() {
return new Service() {
public void doService() {
System.out.println("do the service in waya") ;
}
};
}
}; private static Provider SERVICE_WAYB = new Provider() {
public Service newService() {
return new Service() {
public void doService() {
System.out.println("do the service in wayb") ;
}
};
}
}; public static void main(String[] args) { //regist the created Providers
//namely create the maping betwwen the provider and its key name
Services.registerDefaultProvider(DEFAULT_PROVIDER);
Services.registerProvider("servicea",SERVICE_WAYA);
Services.registerProvider("serviceb",SERVICE_WAYB); // create the newinstance
Service s1=Services.newInstance();
Service s2=Services.newInstance("servicea");
Service s3=Services.newInstance("serviceb"); //invoke the service
s1.doService();
s2.doService();
s3.doService(); }
}

优势四

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

如果想要通过泛型创建一个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模式

具体内容:

下面是一个实际的例子

package chapter2_buildermodel;
//using builder to create the instance of NutritionFacts
public class NutritionFacts { private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate; //initialize the parameters by a builder
private NutritionFacts(Builder builder){
servingSize=builder.servingSize;
servings=builder.servings;
calories=builder.calories;
fat=builder.fat;
sodium=builder.sodium;
carbohydrate=builder.carbohydrate;
} public void info(){
System.out.println("the servingSize is "+servingSize);
System.out.println("the servings is "+servings);
System.out.println("the calories is "+calories);
System.out.println("the fat is "+fat);
System.out.println("the sodium is "+sodium);
System.out.println("the carbohydrate is "+carbohydrate); } public static class Builder{
//required parameters
private final int servingSize;
private final int servings; //optionl parameters (shuld provide a default value)
private int calories=0;
private int fat=0;
private int carbohydrate=0;
private int sodium=0; //every Builder method return this builder instance
public Builder(int servingSize,int servings){
this.servingSize=servingSize;
this.servings=servings;
} public Builder calories(int val){
calories=val; return this;
} public Builder fat(int val){
fat=val; return this;
} public Builder carbohydrate(int val){
carbohydrate=val; return this;
} public Builder sodium(int val){
sodium=val; return this;
} //return this initialized instance by the build method at last
public NutritionFacts build(){
//invoke the private constructor of NutritionFacts
//this constructor use a builder instance to initaialze the parameter
return new NutritionFacts(this);
} } public static void main(String[] args) {
//create the NutritionFacts instance by the Builder model
NutritionFacts cocaCola=new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
cocaCola.info();
}
}

这样处理起来,比那种使用多个构造方法的形式确实灵活好多,但是还是比较麻烦。在一些类似的脚本语言中,可能对于类似问题的处理会好一些,比如早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;

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone; //check if the birth date betwwen baby boomer
public class Personold { private final Date birthDate=null; //other fields, methods, and constructor omitted
//Bad Way (the Calendar instance is created every time when the method is invoked):
public boolean isBabyBoomer(){
Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
Date boomStart=gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
Date boomEnd=gmtCal.getTime();
return birthDate.compareTo(boomStart)>=0&&birthDate.compareTo(boomEnd)<0;
} } package chapter2_objectReuse; import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone; public class Personnew {
private final Date birthDate=null;
//other fields, methods, and constructor omitted
//appoint the start time and the end time
private static final Date BOOM_START=null;
private static final Date BOOM_END=null; // a better way to initialize the parameter
static{
Calendar gmtCal=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
Date BOOM_START=gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
Date BOOM_END=gmtCal.getTime();
} public boolean isBabyBoomer(){
return birthDate.compareTo(BOOM_START)>=0&&birthDate.compareTo(BOOM_END)<0;
} }

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

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

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

Tip6 消除过期引用

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

package chapter2_overduereference;

import java.util.Arrays;
import java.util.EmptyStackException; public class Stack {
private Object[]elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPACITY=16; public Stack(){
elements=new Object[DEFAULT_INITIAL_CAPACITY]; }
public void push(Object e){
ensureCapacity();
elements[size++]=e;
}
public Object pop(){
if(size==0)
throw new EmptyStackException();
return elements[--size];
} //ensure space for one more elements
//roughly doubling the capacity each time the array need grow
private void ensureCapacity(){
if(elements.length==size)
elements=Arrays.copyOf(elements, 2*size+1);
} }

看起来代码也没什么错误,关键是在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. Insomni'hack teaser 2019 - Misc - echoechoechoecho

    参考链接 https://ctftime.org/task/7456 题目内容 Echo echo echo echo, good luck nc 35.246.181.187 1337 解题过程 主 ...

  2. Saving James Bond - Easy Version

    题目来源: 浙江大学在慕课网上开设的<数据结构>课,陈越老师.何钦铭老师主讲,课后作业的一道题. 题目描述: 题目思路: 这道题目本质上讲就是列出图的连通集,但是这个连通集的起点是有约束的 ...

  3. bzoj4542 [Hnoi2016]大数 莫队+同余

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4542 题解 我们令 \(f_i\) 表示从 \(i\) 到 \(n\) 位组成的数 \(\bm ...

  4. 前端每日实战:16# 视频演示如何用纯 CSS 创作一个渐变色动画边框

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/odpRKX 可交互视频教程 此视频 ...

  5. 【转载】 原生js判断某个元素是否滚动到底部

    document.querySelector('.content').addEventListener('scroll',function () { //读取内容区域的真实高度(滚动条高) // co ...

  6. Kibana后台进程启动和关闭

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/12073202.html 后台启动Kibana ./bin/kibana & 查找Kibana进 ...

  7. 数组Array方法: indexOf、filter、forEach、map、reduce使用实例

  8. 13 Spring Boot Shiro使用JS-CSS-IMG

    filterChainMap.put("/403", "anon");filterChainMap.put("/assets/**", &q ...

  9. Apache简介

    1.什么是Apache 注:Apache是目前使用最广泛的Web服务器软件. 2.发展历史 注:客户端mosaic程序是Netscape浏览器的前身,后来演变成mozilla浏览器,即我们使用的Fir ...

  10. vue使用过滤器 filters:{}

    在项目开发过程中,经常会用到过滤器,下面就来说说我用的用法 我从后台获取到一个时间字段,是2017-03-23的格式,但是我要的是年月日分开显示,那就要用到过滤器了 在没有用过滤器的时候,是这样的: ...