第29条:将局部变量的作用域最小化
将局部变量的作用域最小化,可以增加代码的可读性和可维护性,并降低出错的可能性。
使一个局部变量的作用域最小化,最有力的技术是在第一次使用它的地方声明。如果一个变量在使用之前就已经声明了,那么这只会带来混乱---对于试图理解程序功能的读者来说,这种做法只会分散他们的注意力。
如果一个变量是在使用它的块的之外被声明的,那么程序退出该块之后,该变量仍然是可见的,如果被意外使用的话,则后果将是灾难性的。
几乎每一个局部变量的声明都应该包含一个初始化表达式。如果你还没有足够的信息来初始化,那么你应该推迟这个声明,直到可以初始化为止。
在循环中经常要用到最小化变量作用域这一规则。如果在循环终止之后循环变量的内容不需要被需要的话,则for循环优先于while循环。
举例:
Iterator i=c.iterator();
while(i.hasNext()){
}
Iterator 12=c2.iterator();
while(i.hasNext()){
}
第二个循环包含一个“剪切-粘贴”错误。因为i仍然还在有效范围之内,使得代码可以通过,也不会异常,但是它所做的事情却是错误的。而类似的错误在for循环中则不会通过编译。
最后一项最小化局部变量的作用域的技术是使方法小而集中。
第30条:了解和使用库
通过使用标准库,你可以充分利用这些编写标准的专家知识,以及在你之前其他人的使用经验。
使用标准库的第二个好处是,你不必浪费时间为那些与你工作关系不大的问题提供特别的解决方案。
使用标准库的第三个好处是,它们的性能会不断提高,而无需要你做任何努力。
使用标准库的最后一个好处是,你可以使自己的代码融入主流,这样的代码更容易读,易维护,易被其他的开发人员重用。
在每一个主要的发行版本中,都会有许多新的特性被加入到库中,所以与这些库保持同步是值得的。
总而言之,不要从头发明轮子。如果你要做的事情看起来事很常见的,库中可能已经有一个类完成了,如果不清楚可以先查一查。
第31条:如果要求精确的答案,请避免使用float和double
它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。float和double类对于货币计算尤为不适合。解决这个问题的正确的办法是使用BigDecimal,int或者long进行货币计算。
BigDecimal 使用不方便且性能有点慢。
总而言之,对于要求精确答案的计算任务,请不要使用float或者double。
第32条:如果其他类型更合适,则尽量避免使用字符串。
字符串不适合代替其他的值类型。
字符串不适合代替枚举类型。
字符串不适合代替聚集类型。
字符串不适合代替能力表。
第33条:了解字符串连接的性能
为连接n个字符串而重复的使用字符串连接操作符,要求n的平方级的时间。这是由于字符串是非可变的而导致的不幸结果。当两个字符串被连接的时候,他们的内容都要被拷贝。
为了获得可接受的性能,请使用StringBuffer替代String
第34条:通过接口引用对象。
如果你养成了使用接口作为类型的习惯,那么你的程序将会更加灵活。
有一点值得注意,如果原来的实现提供了某种特殊功能,而这种功能并不是这个接口的通用约定要求的并且周围的代码又依赖于这种功能,那么关键的一点是新实现也要提供同样的功能。例如:Vector是同步的,ArrayList是不同步的。
如果没有合适的接口存在的话,那么用类而不是接口来引用一个对象,是完全合适的。
例如:考虑值类型,比如String,BigInteger。值类型很少会有多个实现。
不存在合适的接口类型的第二中情形是,对于属于一个框架,而框架的基本类型是类,不是接口。
不存在合适的接口类型的最后一种情形是,一个类实现了一个接口,但是它提供了接口中不存在的额外方法。例如LinkedList
第35条:接口优先于映像机制
映像设施java.lang.reflect。提供了“通过程序来访问关于已装载的类的信息”的能力。给定一个class实例,你可以获得Constructor,Method和Field实例,分别代表构造函数,方法和域,这些对象提供了“通过程序来访问类的成员方法名字,域类型,方法原型等信息”的能力。通过调用Constructor,Method和Field实例上的方法你可以调用任何类的任何对象上的任何方法。映像机制允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在。
然后这种能力也需要付出代价:
你损失了编译时类型检查的好处。
要求执行映像访问的代码非常笨拙和冗长。
性能损失。
通常,普通应用在运行时刻不应该以映像方式访问对象。
如果只是在很有限的情况下使用映像机制,那么虽然也会付出少许代价,但你可以获得许多好处。
对于有些程序,他们用到的类在编译时刻是不可用的,但是在编译时刻存在适当的接口或者超类,通过他们可以引用到这些类,如果这种情况,那么你可以以映像方式创建实例,然后通过他们的接口或者超类,以正常的方式访问这些实例。
一种合理的使用映像的做法是,打破以个类对于其他类,方法或者域的依赖性。
第36条:谨慎的使用本地方法。
JNI允许java应用可以调用本地方法。所谓本地方法是指本地程序设计语言比如c,c++来编写特殊的方法。
本地方法主要有三种用途。
1.提供了访问与平台相关的设施的能力。
2。他们也提供了访问老式代码库的能力,通过这些老式代码库进一步可以访问老式数据。
3.应用程序还可以使用本地方法,通过本地语言,实现性能关键部分,以提高系统的性能。
jvm实现变得越来越快了,使用本地方法来提高性能的做法已经不值得提倡了。
使用本地方法有一些严重的缺点。因为本地语言是不安全的(见第24条),所以使用本地方法的应用程序也不在免受内存毁坏的错误影响。
因为本地语言是平台相关的,所以使用本地方法的应用程序也不再是可自由移植。
对以每一个目标平台,本地代码都必须经过重新编译,也有可能要求做一些修改。同时,在进入和退出本地代码时,也需要较高的开销,所以,如果本地代码只是做少量工作的话,本地方法可能会降低性能。
最后,本地方法编写起来单调乏味,并且难以阅读。
简而言之,很少情况下需要使用本地方法来提高性能,如果必须使用本地方法访问底层的资源,或者遗留库,那么尽可能少用本地代码,并且全面进行测试,本地代码的一个bug可以破坏这个应用程序。
第37条:谨慎的进行优化
优化更容易带来伤害,而不是好处,特别是不成熟的优化。
不要因为性能而牺牲合理的结构,努力编写好的程序而不是快的程序。好的程序体现了信息隐藏原则,只要有可能他们就会把设计决定限定在局部的单个模块中,所以单个决定可以被改变,并且不会影响到其他系统其他部分(见12条)。
这并不是意味着在完成程序之前你可以忽略性能问题。实际上的问题可以通过后期的优化被改正。
努力避免那些限制性能的设计决定。
考虑你的api设计决定的性能后果。
为了获得好的性能而对api进行曲改,这是一个非常不好的想法。
在每次试图做优化之前和之后,请对性能进行测量。
第38条:遵守普遍接受的命名惯例
命名惯例分为两个大类:字面的和语法的
字面的命名惯例比较少,但也涉及到包,类,接口,方法和域。
包的名字应该是层次状的,用句号分隔每一部。
包名字的剩余部分应该包括一个或者多个描述该包的组成部分,这些组成部分应该比较简短,通常不超过8个字符。鼓励使用有意义的缩写形式,如util而不是utilities。只取首字母的缩写也是可以接受的。例如awt。
类和接口的名字应该包括一个或者多个单词,每个单词的首字母大写,缩写应该尽量避免,除非是一些首字母缩写和一些通用的缩写。
方法和域的名字与类和接口的名字遵守相同的字面惯例,只不过方法或者域的名字的第一个字母应该为小写。如果一个由首字母缩写组成的单词是一个方法或者域名字的第一个单词,那么它应该是小写形式。
上述规则的唯一例外是“常量域”,它的名字应该包含一个或者多个大写形式的单词,中间由下划线符号分隔开。
局部变量名字的字面命名惯例于成员变量类似,只不过它也允许缩写,单个字符和短字符序列的意义依赖于局部变了所在的上下文环境。
语法命名惯例比字面惯例更加灵活,也更有争议。对于包而言,没有语法命名惯例。类通常以个名词或者名词短语命名。 接口的命名与类相似。
执行某个动作的方法通常用一个动词或者动词短语来命名。
如果一个方法返回被调用对象的一个非boolean的函数或者属性,则往往用一个名词,名词短语,或者以个一动词get开头的动词短语来命名。
有些方法的名字值得专门提及。转化对象类型的方法,返回一个不同类型的对立对象的方法通常被称为toType例如toArray。返回一个试图的方法通常被称为asType例如asList。返回一个与被调用对象同值的原语类型的方法,通常被称为typeValue例如intValue。静态工厂的常用名字为ValueOf和getInstance。
将局部变量的作用域最小化,可以增加代码的可读性和可维护性,并降低出错的可能性。
使一个局部变量的作用域最小化,最有力的技术是在第一次使用它的地方声明。如果一个变量在使用之前就已经声明了,那么这只会带来混乱---对于试图理解程序功能的读者来说,这种做法只会分散他们的注意力。
如果一个变量是在使用它的块的之外被声明的,那么程序退出该块之后,该变量仍然是可见的,如果被意外使用的话,则后果将是灾难性的。
几乎每一个局部变量的声明都应该包含一个初始化表达式。如果你还没有足够的信息来初始化,那么你应该推迟这个声明,直到可以初始化为止。
在循环中经常要用到最小化变量作用域这一规则。如果在循环终止之后循环变量的内容不需要被需要的话,则for循环优先于while循环。
举例:
Iterator i=c.iterator();
while(i.hasNext()){
}
Iterator 12=c2.iterator();
while(i.hasNext()){
}
第二个循环包含一个“剪切-粘贴”错误。因为i仍然还在有效范围之内,使得代码可以通过,也不会异常,但是它所做的事情却是错误的。而类似的错误在for循环中则不会通过编译。
最后一项最小化局部变量的作用域的技术是使方法小而集中。
第30条:了解和使用库
通过使用标准库,你可以充分利用这些编写标准的专家知识,以及在你之前其他人的使用经验。
使用标准库的第二个好处是,你不必浪费时间为那些与你工作关系不大的问题提供特别的解决方案。
使用标准库的第三个好处是,它们的性能会不断提高,而无需要你做任何努力。
使用标准库的最后一个好处是,你可以使自己的代码融入主流,这样的代码更容易读,易维护,易被其他的开发人员重用。
在每一个主要的发行版本中,都会有许多新的特性被加入到库中,所以与这些库保持同步是值得的。
总而言之,不要从头发明轮子。如果你要做的事情看起来事很常见的,库中可能已经有一个类完成了,如果不清楚可以先查一查。
第31条:如果要求精确的答案,请避免使用float和double
它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。float和double类对于货币计算尤为不适合。解决这个问题的正确的办法是使用BigDecimal,int或者long进行货币计算。
BigDecimal 使用不方便且性能有点慢。
总而言之,对于要求精确答案的计算任务,请不要使用float或者double。
第32条:如果其他类型更合适,则尽量避免使用字符串。
字符串不适合代替其他的值类型。
字符串不适合代替枚举类型。
字符串不适合代替聚集类型。
字符串不适合代替能力表。
第33条:了解字符串连接的性能
为连接n个字符串而重复的使用字符串连接操作符,要求n的平方级的时间。这是由于字符串是非可变的而导致的不幸结果。当两个字符串被连接的时候,他们的内容都要被拷贝。
为了获得可接受的性能,请使用StringBuffer替代String
第34条:通过接口引用对象。
如果你养成了使用接口作为类型的习惯,那么你的程序将会更加灵活。
有一点值得注意,如果原来的实现提供了某种特殊功能,而这种功能并不是这个接口的通用约定要求的并且周围的代码又依赖于这种功能,那么关键的一点是新实现也要提供同样的功能。例如:Vector是同步的,ArrayList是不同步的。
如果没有合适的接口存在的话,那么用类而不是接口来引用一个对象,是完全合适的。
例如:考虑值类型,比如String,BigInteger。值类型很少会有多个实现。
不存在合适的接口类型的第二中情形是,对于属于一个框架,而框架的基本类型是类,不是接口。
不存在合适的接口类型的最后一种情形是,一个类实现了一个接口,但是它提供了接口中不存在的额外方法。例如LinkedList
第35条:接口优先于映像机制
映像设施java.lang.reflect。提供了“通过程序来访问关于已装载的类的信息”的能力。给定一个class实例,你可以获得Constructor,Method和Field实例,分别代表构造函数,方法和域,这些对象提供了“通过程序来访问类的成员方法名字,域类型,方法原型等信息”的能力。通过调用Constructor,Method和Field实例上的方法你可以调用任何类的任何对象上的任何方法。映像机制允许一个类使用另一个类,即使当前者被编译的时候后者还根本不存在。
然后这种能力也需要付出代价:
你损失了编译时类型检查的好处。
要求执行映像访问的代码非常笨拙和冗长。
性能损失。
通常,普通应用在运行时刻不应该以映像方式访问对象。
如果只是在很有限的情况下使用映像机制,那么虽然也会付出少许代价,但你可以获得许多好处。
对于有些程序,他们用到的类在编译时刻是不可用的,但是在编译时刻存在适当的接口或者超类,通过他们可以引用到这些类,如果这种情况,那么你可以以映像方式创建实例,然后通过他们的接口或者超类,以正常的方式访问这些实例。
一种合理的使用映像的做法是,打破以个类对于其他类,方法或者域的依赖性。
第36条:谨慎的使用本地方法。
JNI允许java应用可以调用本地方法。所谓本地方法是指本地程序设计语言比如c,c++来编写特殊的方法。
本地方法主要有三种用途。
1.提供了访问与平台相关的设施的能力。
2。他们也提供了访问老式代码库的能力,通过这些老式代码库进一步可以访问老式数据。
3.应用程序还可以使用本地方法,通过本地语言,实现性能关键部分,以提高系统的性能。
jvm实现变得越来越快了,使用本地方法来提高性能的做法已经不值得提倡了。
使用本地方法有一些严重的缺点。因为本地语言是不安全的(见第24条),所以使用本地方法的应用程序也不在免受内存毁坏的错误影响。
因为本地语言是平台相关的,所以使用本地方法的应用程序也不再是可自由移植。
对以每一个目标平台,本地代码都必须经过重新编译,也有可能要求做一些修改。同时,在进入和退出本地代码时,也需要较高的开销,所以,如果本地代码只是做少量工作的话,本地方法可能会降低性能。
最后,本地方法编写起来单调乏味,并且难以阅读。
简而言之,很少情况下需要使用本地方法来提高性能,如果必须使用本地方法访问底层的资源,或者遗留库,那么尽可能少用本地代码,并且全面进行测试,本地代码的一个bug可以破坏这个应用程序。
第37条:谨慎的进行优化
优化更容易带来伤害,而不是好处,特别是不成熟的优化。
不要因为性能而牺牲合理的结构,努力编写好的程序而不是快的程序。好的程序体现了信息隐藏原则,只要有可能他们就会把设计决定限定在局部的单个模块中,所以单个决定可以被改变,并且不会影响到其他系统其他部分(见12条)。
这并不是意味着在完成程序之前你可以忽略性能问题。实际上的问题可以通过后期的优化被改正。
努力避免那些限制性能的设计决定。
考虑你的api设计决定的性能后果。
为了获得好的性能而对api进行曲改,这是一个非常不好的想法。
在每次试图做优化之前和之后,请对性能进行测量。
第38条:遵守普遍接受的命名惯例
命名惯例分为两个大类:字面的和语法的
字面的命名惯例比较少,但也涉及到包,类,接口,方法和域。
包的名字应该是层次状的,用句号分隔每一部。
包名字的剩余部分应该包括一个或者多个描述该包的组成部分,这些组成部分应该比较简短,通常不超过8个字符。鼓励使用有意义的缩写形式,如util而不是utilities。只取首字母的缩写也是可以接受的。例如awt。
类和接口的名字应该包括一个或者多个单词,每个单词的首字母大写,缩写应该尽量避免,除非是一些首字母缩写和一些通用的缩写。
方法和域的名字与类和接口的名字遵守相同的字面惯例,只不过方法或者域的名字的第一个字母应该为小写。如果一个由首字母缩写组成的单词是一个方法或者域名字的第一个单词,那么它应该是小写形式。
上述规则的唯一例外是“常量域”,它的名字应该包含一个或者多个大写形式的单词,中间由下划线符号分隔开。
局部变量名字的字面命名惯例于成员变量类似,只不过它也允许缩写,单个字符和短字符序列的意义依赖于局部变了所在的上下文环境。
语法命名惯例比字面惯例更加灵活,也更有争议。对于包而言,没有语法命名惯例。类通常以个名词或者名词短语命名。 接口的命名与类相似。
执行某个动作的方法通常用一个动词或者动词短语来命名。
如果一个方法返回被调用对象的一个非boolean的函数或者属性,则往往用一个名词,名词短语,或者以个一动词get开头的动词短语来命名。
有些方法的名字值得专门提及。转化对象类型的方法,返回一个不同类型的对立对象的方法通常被称为toType例如toArray。返回一个试图的方法通常被称为asType例如asList。返回一个与被调用对象同值的原语类型的方法,通常被称为typeValue例如intValue。静态工厂的常用名字为ValueOf和getInstance。