通用程序设计

第四十五条、将局部变量的作用域最小化

  1. 将局部变量的作用最小化,可以增强代码的可读性和可维护性,并降低出错的可能性。异于C语言要求局部变量必须要在一个代码块的开头处进行声明,Java允许在任何可以出现语句的地方声明变量

  2. 最有力的方法是在第一次使用它的地方声明。过早地声明局部变量不仅会使它的作用域过早地扩展,而且结束地过晚了。局部变量的作用域从它被声明的点开始拓展,一直到外围块的结束处。

  3. 几乎每个局部变量的声明都应该包含一个初始表达式。如果你还没有足够的信息来对一个变量进行有意义的初始化,就应该推迟这个声明,直到可以初始化为止。(有个例外是try-catch语句有关,变量被一个方法初始化,而这个方法可能会抛出一个checked exception,则该变量必须在try的内部被初始化。)

  4. 循环中提供了特殊的机会来将变量的作用域最小化。for循环都允许声明循环变量(loop variable),它的作用域被限定在正好需要的范围内:循环体、循环体之前的初始化、测试、更新部分。所以,如果在循环终止后不再需要循环变量的内容,for循环就优先于while循环。for循环相较于while循环的另一个优势在于:更简短,从而增加了可读性。


第四十六条、for-each循环优先于传统的for循环

  1. 在Java1.5之前,对集合进行遍历的首选做法:

    for(Iterator i = c.iterator();i.hasNext();){
        doSomething((Element)i.next());
    }
    

    对数组进行遍历的首选做法:

    for(int i = 0 ; i < a.length ; i++){
        doSomething(a[i]);
    }
    

    但是它们并不完美:迭代器和索引变量都会造成一些混乱,也代表着出错的可能。
    Java1.5 引入for-each循环,通过完全隐藏迭代器或者索引变量,避免了混乱和出错的可能:

    for(Element e:elements){
        doSomething(e);
    }
    

    冒号代表在。。。里面,可以读作对于元素elements中的每个元素e。这个在性能上可能还稍有优势。

  2. 在对多个集合进行嵌套式迭代的时候,for-each循环的优势更加明显。
    for-each循环不仅让你遍历集合和数组,还让你遍历任何实现Iterable接口的对象。这个简单的接口由单个方法组成:

    public interface Iterable<E>{
        Iterator<E> iterator();
    }
    
  3. 有三种情况下无法使用for-each循环:

    • 过滤:如果需要遍历集合,并删除选定的元素,就需要使用显式的迭代器,以便可以调用它的remove方法;

    • 转换:如果需要遍历列表或者数组,并取代他部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值;

    • 平行迭代:如果需要并行地遍历多个集合,就需要显式地控制迭代器或者索引变量,以便所有的迭代器或者索引变量都可以同步前移。


第四十七条、了解和使用类库

  1. 通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。而且不必浪费时间为那些与工作不太相关的问题提供特别的解决方案。而且它们的性能往往随着时间的推移而不断提高。可以使自己的代码融入主流。

  2. 每个Java程序员应该熟悉java.langjava.util还有java.io中的内容,关于其他类库的知识可以随时学习。

  3. 两个工具:

    • Collection Framework(集合框架)被加入到java.util包中,是一个统一的体系结构,用来表示和操作集合,允许它们对集合进行独立于表示细节的操作,从而减轻了编程的负担且提高了效率和性能。
    • java.util.concurrent 包中有一组并发实用工具,既包含高级的并发工具来简化多线程的编程任务,还包含低级别的并发基本类型,允许专家们自己编写更高级的并发抽象。

第四十八条、如果需要精确的答案,请避免使用float和double

  1. float和double类型主要是为了科学计算和工程计算而设计的。它们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果。尤其不适用于货币计算,让float和double精确地表示0.1是不可能的。

  2. 使用BigDecimal、int或者long进行货币计算:如果想让系统来记录十进制小数点,且不介意一丝不方便,请使用BigDecimal;如果性能特别关键,而且又不介意自己记录十进制的小数点,涉及的数值不是很大,可以使用int和long。如果数值超过18位数字,就必须使用BigDecimal


第四十九条、基本类型优先于装箱基本类型

  1. java有一个类型系统由两个部分组成:

    基本类型(primitive)如:int、double和boolean;引用类型(reference type)如:String和List。每个基本类型都有一个对应的引用类型,称作装箱基本类型(boxed primitive)。int/double/boolean对应于Integer、Double和Boolean。自动装箱和自动拆箱模糊了但是没有完全抹去基本类型和装箱基本类型之间的区别。

  2. 两者之间的主要区别:

    • 基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。
    • 基本类型只有功能完备的值,装箱基本类型还有个非功能值null。
    • 基本类型通常比装箱基本类型更节省时间和空间。
  3. 对装箱基本类型使用==操作符几乎总是错误的;当一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,如果null对象引用被自动拆箱,就会得到一个NullPointerException

  4. 什么时候应该使用装箱基本类型呢?

    • 作为集合中的元素、键和值,不能将基本类型放在集合中,因此必须使用装箱基本类型;
    • 在参数化类型中,必须使用装箱基本类型作为参数类型;
    • 在反射的方法调用时,必须使用装箱基本类型。
  5. 总结:当可以选择的时候,基本类型优先于装箱基本类型。基本类型更加简单也更加快速,如果必须使用装箱基本类型,则要特别小心,自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险。


第五十条、如果使用其他类型更合适,则尽量避免使用字符串

  1. 字符串的优点:被用来表示文本,十分通用且Java支持的比较好。

  2. 不应该使用字符串的情形:

    • 字符串不适合代替其他的值,当一段数据从文件、网络或者键盘设备,进入到程序中,通常是以字符串的形式存在,如果它是数值,就应该被转换为适当的数值类型等等;
    • 字符串不适合替代枚举类型:见第三十条
    • 字符串不适合代替聚集类型:如果一个实体有多个组件,用字符串表示这个实体通常是很不恰当的,更好的做法当然是编写一个类来描述这个数据集,通常是一个私有的静态成员类。
    • 字符串不适合代替能力表(capabilities):有时候,字符串被用于对某种功能进行授权访问,例如考虑设计一个提供线程局部变量机制,这个机制提供的变量在每个线程中都有自己的值。几年前面对这样的设计任务时,有些人利用客户提供的字符串键,对每个线程局部变量的内容进行访问授权。
  3. 总之:如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,就应该避免用字符串来表示对象。基本类型、枚举类型和聚集类型经常被错误地用字符串来代替。

第五十一条、当心字符串连接的性能

  1. +(字符串连接操作符)是把多个字符串合并为一个字符串的便利途径。要想产生单独一行的输出,或者构造一个字符串来表示一个较小的、大小固定的对象,使用+是非常合适的。但是它不适合应用在大规模的场景中,为连接n个字符串而重复的使用字符串连接操作符,需要n的平方级的时间因为字符串不可变而导致的,当两个字符串被连接在一起的时候,它们的内容都要被拷贝。

  2. 为了获得可以接受的性能,请使用StringBuilder替代String,两种方法性能差别巨大,原先的做法开销是随着数量呈平方增加,改进后是线性增加。

         public String statement(){
             StringBuilder b = new StringBuilder(numItems() *LINE_WIDTH);
             for(int i = 0; i< numItems();i++){
                 b.append(lineForItem(i));
             }
             return b.toString();
         }

    原先的:

        public String statement(){
          String result = "";
          for (int i = 0;i < numItems(); i++){
              result += lineForItem(i);
          }
          return result;
        }
  3. 总结:不要使用字符串连接操作符来合并多个字符串,除非性能无关紧要。相反,应该使用StringBuilder的append方法。


第五十二条、通过接口引用对象

  1. 如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型的声明。只有当你利用构造器创建某个对象的时候,才真正需要引用这个对象的类。如:List<Subscriber> subscribers = new Vector<Subscriber>();

  2. 如果养成了这个习惯,程序将更加灵活。当决定更换实现的时候,只需要改变构造器中类的名称。周围的代码都可以正常工作,对于这种变化并不在意。

  3. 如果没有合适的接口存在,完全可以用类而不是接口来引用对象。

    比如:值类(String和BigInteger)。值类很少会用多个实现编写,通常是final的,并且很少有对应的接口。

    第二种情形是:对象属于一个框架,而框架的基本类型就是类,如果对象属于这种基于类的框架,就应该使用相关的基类(往往是抽象类)来引用这个对象。

    最后一种情形是:类实现了接口,但是它提供了接口中不存在的额外方法。

第五十三条、接口优先于反射机制

  1. 核心反射机制(core reflection facility)

    java.lang.reflect提供了“通过程序访问关于已装载的类的信息”的能力。给定一个Class实例,可以获得Constructor、Method和Field实例,分别代表该Class类所表示的类的Constructor、Method和Field。

    这些对象提供了“通过程序来访问类的成员名称、域类型、方法签名等信息”的能力

    而且,Constructor、Method、Field实例能够使你通过反射机制操作它们的底层对等体:通过调用上述三项上的方法,可以构造底层类的实例。调用底层类的方法,并访问底层类中的域。

    反射机制reflection允许一个类使用另一个类,即使当前者被编译时后者还不存在。

  2. 反射机制的代价:

    • 丧失了编译时类型检查的好处,包括异常检查。

    • 执行反射访问所需要的代码非常笨拙和冗长。

    • 性能损失:比普通方法要慢不少。

  3. 核心反射机制最初是为了基于组建的应用创建工具而设计的。这类工具需要根据装载类,并且用反射功能找出它们支持哪些方法和构造器。这些工具允许用户交互式地构造出访问这些类的应用程序,但是所产生出来的这些应用程序能够以正确的方式访问这些类,而不是以反射的方式。反射只是在设计的时候被用到。

  4. 通常,普通应用程序在运行时不应该以反射的方式访问对象。有一些复杂的应用程序需要使用反射机制。比如:类浏览器对象监视器代码分析工具解释型的内嵌式系统。在RPC(远程过程调用)系统中使用反射机制也是十分合适的。

  5. 反射机制的好处:

    对于有些程序,它们必须用到在编译时无法获取的类,但是在编译时存在适当的接口或者超类,通过它们可以引用这个类。以反射方式创建实例,然后通过它们的接口或者超类,正常的方式访问这些实例。Class.newInstance方法就已经提供不带参数的构造方法。

  6. 总结:反射机制是一种功能强大的机制,对于特定的复杂系统编程任务,它是非常必要的,但是它也有一些缺点。如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时使用编译时已知的某些接口或者超类。


第五十四条、谨慎地使用本地方法

  1. Java Native Interface(JNI)允许Java应用调用本地方法Native Method,本地方法是指用本地程序设计语言(如C或C++)来编写特殊的方法。本地方法在本地语言中可以执行任意的计算任务,并返回到Java程序设计语言。

  2. 本地方法的用途:

    • 提供了”访问特定于平台的机制“的能力,比如访问注册表和文件锁。

    • 提供了访问遗留代码库的能力,从而可以访问遗留数据。

    • 本地方法可以通过本地语言,编写应用程序中注重性能的部分,以提高系统的性能。

  3. 使用本地方法来提高性能的方法不值得提倡:

    随着JVM实现变得越来越快。使用本地方法的缺点在于:由于本地语言不是安全的,会引发内存毁坏的错误;由于本地语言是与平台相关的,使用本地方法的应用程序不再是可自由移植的;使用本地方法的程序也更加难以调试。在进入和退出本地方法的时候需要相关固定的开销;胶合代码难以阅读


第五十五条、谨慎地进行优化

  1. 三条关于优化的格言:

    • 很多计算上的过失都被归咎于效率(没有必要达到的效率)。而不是任何其他的原因——甚至包括盲目地做傻事。
    • 不要去计较效率上的一些小小得失,在97%的情况下,不成熟的优化才是一切问题的根源。
    • 在优化方面,我们应该遵守两条规则:
      • 不要进行优化,
      • 还是不要进行优化。
  2. 优化的弊大于利,尤其是不成熟的优化。这并不意味着,在完成程序之前就可以忽略性能问题,必须要在设计过程中考虑性能问题:努力避免那些限制性能的设计决策;要考虑API设计决策的性能后果。一旦谨慎地设计了程序,并且产生了一个清晰、简明、结构良好的实现,那么就到了考虑优化的时候了。


第五十六条、遵守普遍接受的命名惯例

  1. java平台建立了一整套很好的命名惯例naming convention,不严格地说,这些命名惯例分为两大类:字面的(typographical)和语法的(grammatical)。

  2. 字面的惯例简要的介绍:

    • 包的名称应该是层次状的,用句号分割每个部分。每个部分都包括小写字母和数字(很少),任何将在你的组织之外使用的包,其名称都应该以你的组织的Internet域名开头,并且顶级域名放在前面,例如:edu.cmu,com.sun,gov.nsa标准类库和一些可选的包,其名称以java和javax开头,这属于这一规则的例外。用户创建的包的名称绝对不能以java和javax开头;

    • 包名称的剩余部分应该包括一个或者多个描述该包的组成部分。这些描述应该比较简短,一般不超过8个字符,鼓励使用有意义的缩写:如utilawt等。

    • 类和接口的名称,包括枚举和注解类型的名称,都应该包括一个或者多个单词,每个单词的首字母大写(如TimerTimerTask);应该尽量避免缩写。

    • 方法和域的名称与类和接口的名称一样,遵循相同的字面惯例,只是第一个字母应该小写。如果首字母缩写组成的单词是一个方法或者域名称的第一个单词,它就应该是小写形式。

    • ”常量域“它的名称应该包含一个或者多个大写的单词,中间用下划线符号隔开

    • 局部变量名称的字面惯例与成员名称类似,只不过它也允许缩写,单个字符和短字符序列的意义取决于上下文。

    • 参数类型名称通常由单个字母组成:通常是下面五种类型之一:T表示任意的类型E表示集合的元素类型K和V表示映射的键和值类型X表示异常。任何类型的序列都可以是T、U、V或者T1、T2、T3。

  3. 语法命名惯例:比字面惯例更加灵活但是有争议。

    • 类通常用一个名词或者名词短语命名;

    • 接口的命名与类类似,或者用一个已-able或者-ible结尾的形容词来命名:RunableIterable

    • 执行某个动作的方法通常用动词或者动词短语来命名,对于返回boolean值的方法,其名称往往以单词”is“开头,后面跟名词或者名词短语,或者任何具有形容词功能的单词或者短语:isEmptyisEnabled

    • 如果方法返回被调用对象的一个非boolean的函数或者属性,通常使用名词、名词短语或者以动词”get“开头的动词短语来命名:如size,hasCode或者getTime

    • 转换对象类型的方法、返回不同类型的独立对象的方法,通常称为toType;

    • 返回视图的方法通常称为asType:asList

    • 返回一个与被调用的对象同值的基本类型方法,通常被称为typeValue:intValue;

    • 静态工厂的常用名称为:valueOfofgetInstancenewInstancegetTypenewType

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值