EffectiveJava学习笔记

第二章 创建与销毁对象


 1、考虑使用静态工厂方法代替构造器

优点:

  静态工厂方法在方法命名上更具有可读性【Good】

  静态工厂方法不需要每次在被调用的时候都构造一个新的对象【Good】

  静态工厂方法还可以返回该类型的子类对象【Good】

  静态工厂方法还可以简化参数化类型的对象创建【Not Good】

缺点:

  如果在一个类中将构造函数设为priavte,只提供静态工厂方法来创建对象,那么就不能通过继承的方式来扩展该类;

  解决办法:可以进行组合;【OK】

  静态工厂方法与其他静态方法实际上没有任何区别;【Good】

  解决办法:命名静态工厂方法的时候遵循一定的规则。

  valueOf、of、getInstance、newInstance、getType、newType

备注:参考链接,http://hellojinjie.com/2014/04/03/effective-java%EF%BC%9A%E4%BD%BF%E7%94%A8%E9%9D%99%E6%80%81%E5%B7%A5%E5%8E%82%E6%96%B9%E6%B3%95/

2、遇到多个构造器参数时要考虑用构建器【Builder模式】

优点:

  使用Builder Pattern。【Good,大于4个参数,使用这种方式不需要考虑线程安全问题

缺点:

  注重性能上的情况下,就会出现问题。(由于需要先创建构建器,再生成可用对象)

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

  使用单元枚举类型实现Singleton。【Good】

4、通过私有构造器强化不可实例化的能力

优点:

  工具类不希望被实例化,将构造器变成私有,并且构造器中防止被内部调用抛出一个异常。【Good】

缺点:

  子类不能调用父类的构造器。

5、避免创建不必要的对象【改】【作用域】

  对象为不可变的,就可以被重用。

  静态代码块初始化,在类第一次使用时才进行初始化。【Good】

备注:优先使用基本类型而不是装箱类型。

           维护对象池,特别是数据库连接池。

6、消除过期的对象引用【内存泄露问题】

  注意1:只要类是自己管理内存,程序员就应该警惕内存泄漏问题,一旦元素被释放,则该元素包含的任何对象引用都应该被清空。

  注意2:内存泄漏的另一个常见来源是缓存(WeakHashMap代表缓存,该键的外部引用而不是由值决定时,WeakHashMap才有用)(缓存项的生命周期是否有意义,可用LinkedHashMap的removeEldestEntry)

  注意3:内存泄漏的第三个常见来源是监听器和其他回调(必须要有注册的和取消注册的,WeakHashMap中的键

7、避免使用终结方法(finalize

缺点:

  在于不能保证会被及时地执行;

  根本就不保证它们会被执行;

  如果在终结方法中抛出异常,这种异常可以被忽略并且该对象的终结过程也会终止;

  性能损失;(时间)

解决方法:优先使用try{} finally {} 方法;

  本地对等体还没查阅。

第三章 对于所有对象都通用的方法


 8、覆盖equals时请遵守通用约定值类才需要覆盖,逻辑上要求相等而不是想了解他们是否指向同一个对象)

满足条件:

  自反性、对称性x.equals(y)与y.equals(x)结果要一样、传递性、一致性,null返回fasle。

备注:

  instanceof 左边的对象是否是它右边的类的实例或者是子类的一个实例。

  Object中的equals方法是 this == object.

  Object中的hashcode方法是返回32位内存地址。

结论:【Good】

  1)使用==操作符检查“参数是否为这个对象的引用”

  2)使用instanceof操作符检查“参数是否为正确的类型”(可以检查null)

  3)把参数转换成正确的类型(强制类型转换)

  4)对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应域相匹配(除了float与double的基本类型都可以用==进行比较,对于float与double可使用Float.compare与Double.compare方法,对象引用域使用equals,)

  5)当你编写完成了equals方法之后,应该问自己三个问题:它是否对称的、传递的、一致的?

  覆盖equals时总是要覆盖hashCode,不要将equals声明中的Object对象替换为其他的类型。

9、覆盖equals时总是要覆盖hashCode

  (equals相等,hashCode必须相等;equals不相等,hashCode可能相等或者不相等)

结论:【Good】

  1)  把某个非零分常数值,比如有说17,保存在一个名为result的int类型的变量

  2)  对于对象中每个关键域f(指equals方法中涉及的每个域,排除冗余域),完成以下步骤:

    A) 为该域计算int类型的散列码c:(不同类型处理不一样)

    B) 将c代入下面公式得到hashCode的返回值:result = 31 * result + c

  3)  返回result

  4)  询问自己“相等的实例是否都具有相等的散列码”

10、始终要覆盖toString【Good

  要返回对象中包含的所有值得关注的信息

11、谨慎地覆盖clone【无需构造器可以创建对象】

  如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。

备注:

  clone方法就是另一个构造器,你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。

  (后面HashTable未看完)

12、考虑实现Comparable接口(允许同性比较,允许执行顺序比较)

  一个值类,具有非常明显的内在排序关系,比如:按字母顺序、按数值顺序或者按年代顺序。

  1)  sgn(x.compareTo(y)) == -sgn(y.compareTo(x))

  2)  (x.compareTo(y) > 0 && y.compareTo(z) > 0) 暗示着 x.compareTo(z) > 0

  3)  建议:(x.compareTo(y) == 0 ) == (x.equals(y))

备注:

  实现接口时,注意两个比较field是否存在溢出现象,如果不存在,则可以使用返回值为符号优化程序,否则,返回0,-1,1。

第四章 类与接口(接口都是public


 13、使类和成员的可访问性最小化(private default protected public)【Good】

  1)  尽可能地使每个类【private与 public】或者成员不被外界访问;

  2)  若类实现了serializable接口,这些类成员可能会被“泄漏”到到处的API中。

  3)  规则限制:如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别

备注:

  静态常量命名:这种域的名称由大写字母组成,单词之间用下划线隔开。

  除了共有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final引用的对象都是不可变的

14、在公有类中使用访问方法而非公有域【Good】

  多用getter与setter方法,别把类成员变成public。

15、使可变性最小化【搞成不可变类】

  不可变类:实例不能被修改的类,即每个实例中包好的所有信息都必须在创建该实例时候就提供,并在对象的整个生命周期内固定不变。(例如String

  1)  不要提供任何会修改对象状态的方法

  2)  保证类不会被扩展(一般做法:final)

  3)  使所有的域都是final的

  4)  使所有的域都成为私有(不建议)

  5)  确保对于任何可变组件的互斥访问

备注:

  不可变类的运算后,是创建并返回新的不可变类实例,而不是修改这个实例。(线程安全,只有创建时的状态)

  构造器应该创建完全初始化的对象,并建立起所有的约束关系,不要在构造器或者静态工厂之外再提供公有的初始化方法。能加final关键字尽量加。

16、复合优先于继承

  包内继承是安全的【Good】

复合:

  新的类中增加一个私有域,它引用现有类的一个实例。(包装类和可重用的转发类)

备注:

  包装类不适用在回调框架中。(回调,对象把自身的引用传递给其他对象,用于后续的调用)

继承:

  违背封装原则,只有子类与父类之间确实存在父子类型关系时,使用继承才是恰当的。

17、要么为继承而设计,并提供文档说明,要么就禁止继承

  参考AbstractList中的removeRange方法。

  对于为了继承而设计的类,唯一的测试方法就是编写子类。(3个子类,无一使用protected的成员,就应该把它作为private)

  构造器决不能调用可被覆盖的方法。

  父类不应该实现了Cloneable与Serializabale接口,无论是clone还是readObject,都不可以调用可覆盖的方法,不管直接还是间接,若要实现Serializable,必须把readResolve或者writeReplace方法设为保护。

18、接口优于抽象类【Good】

  (未读完)

19、接口只用于定义类型

  错误:(不应该被用于导出常量

   

  正确:

    如果这些常量与某个现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中。

  比如:使用枚举类型【最好】。

20、类层次优于标签类【Good】

  使用抽象类+子类办法,替代标签类。

备注:参考链接http://blog.csdn.net/hosterZhu/article/details/9834011

 21、用函数对象表示策略【Good】

  函数指针的主要用途就是实现策略模式。

  接口+具体策略。

Eg:

   

22、优先考虑静态成员【Good,防止内存泄漏】【如果声明类成员不要求访问外围实例,就要始终把static修饰符放在它的声明当中。

  四种嵌套类:静态成员类(辅助类,外部类一起使用),非静态成员类、匿名类与局部类,后面三种为内部类。【嵌套类存在的目的应该只为了它的外围类提供服务,如果嵌套类将来可能用于其他的某个环境中,它就是顶层类。

   非静态成员类:每个实例都隐含着与外围类的一个外围实例相关联(使用enclosingInstance.new MemberClass(args)构建)(例如:Adapter)

  匿名类:动态地创建函数对象、Runnable、Thread或者TimerTask。

  局部类:用得最少。

备注:参考链接,http://www.cnblogs.com/wangliyue/p/4468962.html

第五章 泛型


23、请不要在新代码中使用原生态类型【Good,因为失去了编译时类型检查的安全性,所以使用<>,编译时就发现

  泛型类和接口统称为泛型。

  无限制通配符类型:?,可读做未知某个类型的集合,安全。

24、消除非受检警告【Good】(需要在声明中使用,尽可能在小的范围中使用)

  如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以使用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。(必须添加注释说明为什么这样做是安全的)

25、列表优先于数组【Good,数组和泛型不能很好地混合使用,如果混合使用得到编译时错误或者警告,应用列表代替数组

  创建泛型数组是非法的,因为数组是不安全的。

  数组是具体化的,运行时检查他们的类型信息;

  泛型是不可具体化,运行时表示法包含的信息比编译时包含的信息更少的类型。

26、优先考虑泛型【GOOD】

   【一看标题就知道】

27、优先考虑泛型方法GOOD】【静态工具方法尤其适合于泛型化,具有类型推导作用】

(115-116未看)

   

28、利用有限制通配符来提升API的灵活性广泛使用的类库

29、优先考虑类型安全的异构容器

将Class对象作为键。

   

使用:

   

结果:

   

第六章 枚举与注解


 30、用enum代替int常量(java枚举本质是int值,只有0,1,2常量时才进行替换)【GOOD

优点:

  1、  传递类型错误的值时,会导致编译时错误;

  2、  toString方法将枚举转换成可打印字符串;

备注:枚举就是个单例类,不能被继承。Enum就是不可变类,所有域都是final

         【策略枚举还没看】

31、用实例域代替序数

错误:

   

正确:

   

避免使用ordinal,这是返回枚举类型的序数除非是运用在EnumSet与EnumMap。

32、用EnumSet代替位域(位域就是用OR位运算将几个常量合并到一个集合中)

错误:

   

正确:

   

33、用EnumMap代替序数索引

  由于编译器无法知道序数和数组索引之间的关系。

34、用接口模拟可伸缩的枚举

  【跳过没看】

35、注解优先于命名模式

  【使用注解得配合反射一起用更好】

36、坚持使用Override注解

  表示:被注解的方法声明覆盖了超类型中的一个声明,编译过程给你报错误消息。

37、用标记接口定义类型【参考Serializable】(如果想要定义类型,一定要使用接口)

优于标记注解:

  1、  标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型。

  2、  标记接口可以被更加精确地锁定。

标记注解优于标记接口的最大优点:通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多信息。

使用场景:

         注解:标记应用到任何元素而不是类或者接口;

         接口:永远限制这个标记只用于特殊接口的元素。

第七章 方法


38、检查参数的有效性

  养成检查方法参数有效性的习惯(eg.引用对象不为null,字符串长度大于0,不超基本类型的最大最小值。)

39、必要时进行保护性拷贝(因为java中传递对象是引用)

  保护性拷贝是在检查参数的有效性之前进行,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。(万一多线程改变了该值呢?)

  

         防御:

        

         使返回可变内部域的保护性拷贝。

        

         防御:

        

备注:

  类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝组件。

40、谨慎设计方法签名

技巧:

  1、  方法命名

  2、  避免过长的参数列表

    a)  少于四个,多于四个使用Builder

    b)   频繁使用的,创建辅助类(纸牌这个对象,传入点数和花色即可)

41、慎用重载

  【肯定在某种情况下不能使用重载,因此跳过没看】

42、慎用可变参数

  【了解可变参数概念,因此跳过没看】

43、返回零长度的数组或者集合,而不是null

  【GOOD,看标题就知道内容了】

44、为所有到处的API元素编写文档注释

  【跳过不看,以后用到再看】

第八章 通用程序设计


45、将局部变量的作用域最小化

  【GOOD,看标题就知道内容,跳过不看】

46、for-each循环优于传统的for循环

  【一看标题就知道干嘛,因此跳过没看,只看结果】

有三种情况是无法使用for-each循环【删、改】

  1、  过滤:遍历并删除指定的元素,就需要使用显示的迭代器,以便可以调用它的remove方法。

  2、  转换:遍历并替换指定索引的元素。

  3、  平行迭代:并行地遍历多个集合(即控制同步前移,还没有遇到这种情况)

47、了解和使用类库【java.lang、java.util、java.io

  通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。

  1、  在每个java发行版本都应该了解新的特性;

  2、  每个程序员应该把时间花在应用程序上,而不是底层的细节上。

  Eg:Random.nextInt à代替random.nextInt()。

48、如果需要精确的答案,请避免使用float和double【BigDecimal

  使用BigDecimal、int或者long进行货币计算;

  使用BigDecimal类型代替double。

49、基本类型优先于装箱基本类型【考虑==操作符】

  【GOOD,一看标题就知道结果,可跳过】

50、如果其他类型更合适,则避免使用字符串

场景:

  1、  字符串不适合代替其他的值类型【当数据(网络、文件)传到程序之中,应该转换为适当的数值类型】

  2、  字符串不适合代替枚举类型

  3、  字符串不适合代替聚集类型

51、当心字符串连接的性能【使用StringBuilder代替String,在需要重复操作的情景下】

  记住一点:想要禅城单独一行的输出,或者构造一个字符串来表示一个较小的、大小固定的对象是可以用+连接,但是不能运用在为连接n个字符串而重复使用+。需要n的平方级的时间【使用StringBuilder代替String】

52、通过接口引用对象

  【GOOD,向上转型】

不能使用:

  假设具体类中有我们需要使用的具体函数,那么不能使用。

否则:

  一律使用接口或者抽象类,这样我们的程序更加灵活。

53、接口优于反射机制

  【反射机制的使用场景:类浏览器、对象监视器、代码分析工具、解释型的内嵌式系统以及RPC(远程过程调用)】

54、谨慎地使用本地方法

  【本地方法使用场景:访问底层的资源、遗留代码库,另外本地语言是不安全的】

55、谨慎地进行优化【努力编写好的程序而不是快的程序】

  在你还没有绝对清晰的未优化方案之前,请不要进行优化。

  使用性能剖析工具帮助优化,并且使用更优化的算法来替代旧的算法。

56、遵守普通接受的命名惯例【驼峰命名法】

  包名称:不超过8个字符,鼓励使用有意义的缩写:字母的前缀4个或者首字母缩写;

  类和接口的名称(包含枚举和注解):一个或者多个单词(第一个单词大写,尽量避免缩写,除非是通用的max或者min);

  方法和域的名称:一个或者多个单词(第一个单词小写);

  常量域:静态+final,所有单词大写+下划线分割

  局部变量名称:根据环境随意写;

备注语法命名:

  1、接口可后面加able

  2、is开头可用于返回boolean值

  3、get用于获取属性的方法命名,to转换对象类型的方法(toString)。

第九章 异常


57、只针对异常的情况才使用异常

  【不要将他们用于普通的控制流,数组越界的情况(为什么有人这样用,因为JVM每次都会去判断数组是否越界,以为这样会损耗性能,其实随着时间的推移,会被修复的,所以不必要担心)】

58、对可恢复的情况使用受检异常,对编程错误使用运行时异常

  期望调用者能够适当地恢复:

  抛出受检异常(电话费不足的例子)

不能恢复:

  运行时异常+错误。

59、避免不必要地使用受检的异常

 

60、优先使用标准的异常

常用的异常:

   

备注:建立在语义的基础上,而不是建立在名称的基础上。

61、抛出与抽象相对应的异常

异常转译:

  更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。

62、每个方法抛出的异常都要有文档

【一看标题就知道内容,不看】

63、在细节消息中包含能捕获失败的信息

【意思大概:看Log】

64、努力使失败保持原子性

失败原子性:

  失败的方法调用应该使对象保持在被调用之前的状态。

三个办法:

  1、  一个不可变的对象;

  2、  在执行操作之前检查参数的有效性;

  3、  临时拷贝。

65、不要忽略异常

  【GOOD】

第十章 并发


66、同步访问共享的可变数据(使用volatile

  当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。【如果读和写的操作没有被同步,同步就不会起作用。】

  虚拟机-->

  回顾volatile,印象笔记。

  另外,不要使用Thread.stop会使数据遭到破坏。

67、避免过度同步

  可重入的锁简化了多线程的面向对象程序的构造,但是会将活性失败变成安全性失败。

  CopyOnWriteArrayList通过重新拷贝整个底层数组,适用于观察者列表。【GOOD

  通常,你应该在同步区域内做尽可能少的工作,获得锁,检查共享数据,根据需要转换数据,然后放掉锁。如果你必须要执行某个很耗时的动作,则应该设法把这个动作移到同步区域外面。(避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。)

68、executor和task优先于线程【使用线程池管理线程】

  工作队列:

   

  ThreadPoolExecutor:允许你控制线程池操作的几乎每个方面。

  ScheduledThreadPoolExecutor代替Timer

69、并发工具优先于wait和notify

  Executor Framework:线程池

  并发集合:

  优先使用ConcurrentHashMap替换Collections.synchronizedMap或者Hastable。

  阻塞操作:BlockingQueue,从队列中删除并返回了头元素,如果队列为空,就等待。(用于工作队列,生产者-消费者队列)

 

  同步器:一些使线程能够等待另一个线程的对象。

  常用: CountDownLatch和Semaphore。

  (倒计数锁存器)CountDownLatch:一次性的障碍,允许一个或者多个线程等待一个或者多个其他线程来做某些事情。

 

  始终应该使用wait循环模式来调用wait方法,永远不要再循环之外调用wait方法,循环会在等待之前和之后测试条件,优先使用notifyAll,而不是使用notify,若使用notify,要确保程序的活性。

 

  疑问:在没有通知的情况下,等待线程也可能(但很少)会苏醒起来?【没碰到这种情况】

  备注:

    system.nanoTime:在间歇式的定时当中,始终应该优先使用。(替代System.currentTimeMills)

70、线程安全性的文档化

  【未看】

71、慎用延迟初始化【正常初始化由于延迟初始化】

  延迟初始化,如果永远不需要这个值,这个域就永远不会被初始化,适用于静态域、实例域。

  若要用的话:

  对于实例域,使用双重检查模式;

   

  对于静态域,使用lazy initialization holder class idiom

   

  特殊情况:对于实例域可接受重复初始化的,可考虑单重检查模式。

   

72、不要依赖于线程调度器

  不依赖Thread.yield或者线程优先级。

73、避免使用线程组(初衷作为一种隔离applet(小程序)的机制)

  使用线程池。

posted @ 2017-08-16 23:07  eeSiJian  阅读(195)  评论(0编辑  收藏  举报