EffectiveJava第八章:通用程序设计

来源:互联网 发布:淘宝店铺店标在线制作 编辑:程序博客网 时间:2024/04/28 10:56

讨论Java语言的具体细节,讨论了局部变量的处理、控制结构、类库的用法、各种数据类型的用法,反射(reflection)和本地方法(native method)的用法。

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

将局部变量的作用域最小化,可以增强代码的可读性和可维护性,并降低出错的可能。

  • 在第一次使用局部变量的地方声明它。
    如果变量在使用之前声明,这只会造成混乱,不利于阅读;
    在代码块之外声明变量的话,该变量的生命周期会被拉长,且被外部可见,被引用时可能会出错。

  • 几乎每个局部变量的声明都应该包含一个初始化表达式,如果还没有足够的信息来对一个变量进行有意义的初始化,那就推迟该声明,知道可以初始化为止。

  • 在使用循环语句时,如果在循环语句结束后不再需要循环变量了,那么for语句就优于while语句,且可读性更强,避免出bug。

  • 拆分方法,使方法小而集中,避免局部变量跨域被使用。

46. for-each循环优先于传统for循环

  • for-each语句不会有性能损失,甚至数组也一样;甚至比普通for还有些性能优势(因为for-each只计算一次边界值,普通for如果也这么写的话,性能应该一样)。

  • 在对多个集合进行嵌套迭代时,for-each语句要简洁的多。

        for (String string : strings) {            for (Animal animal : list) {                System.out.println(string + ", " + animal);            }        }        for (Iterator<String> stringIterator = strings.iterator(); stringIterator.hasNext(); ) {            String next = stringIterator.next();            for (Iterator<Animal> iterator = list.iterator(); iterator.hasNext(); ) {                System.out.println(next + ", " + iterator.next());            }        }
  • for-each循环不仅可以遍历集合和数组,还可以遍历所有实现了Iterable<E>接口的对象。
    如果你所创建的类型表示的是一组元素,几遍你选择不让它实现Collection,也要实现Iterable。这样便于使用for-each进行循环。

  • 不能使用for-each情况的三种情况:

    • 过滤
      在遍历时删除元素
    • 需要索引时
      在遍历时需要索引来修改或取代元素

47. 了解和使用类库

  • 使用标准类库的好处:

    1. 通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。
    2. 不必浪费为那些与工作不太相关的问题提供特别的解决方案。
    3. 标准类库的性能会随着时间的推移而不断提高,无需你做任何努力;也会添加新的功能。
    4. 可以使自己的代码融入主流,这样的代码更易读、更易维护、更易被大多数的开发人员重用。
  • 知识应该保持更新,不使用类库是因为不知道这些类库的存在。

  • 每个程序员应该掌握的类库:

    1. java.lang
    2. java.util
    3. 某种程度上的java.io
    4. Collections Framework
    5. java.util.concurrent

不要重新发明轮子,去阅读源码,使用它。

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

float和double类型主要是为了科学计算和工程计算而设计的。它执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的。并不是提供完全精确的结果,所以不应该用于需要精确结果的场合

  • float和double尤其不适合用于货币计算。在货币计算上,使用BigDecimal、int或long来精确计算。
    如果数值没有超过9位十进制数字,使用int;
    如果没有超过18位数字,使用long;
    超过18位,则使用BigDecimal。

  • BigDecimal提供了8种四舍五入模式,方便,不考虑性能的话可以使用。

49. 基本类型优先于装箱基本类型

  • 自动装箱(auto-boxing)和自动拆箱(auto-unboxing),这些特性模糊了但没有完全模去基本类型和装箱基本类型之间的区别。

  • 基本类型和装箱基本类型的区别:

    1. 基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。意思是两个装箱基本类型可以具有相同的值和不同的同一性。
    2. 基本类型只有功能完本的值,而每个装箱类型除了它对应的基本类型的值以外,还有一个null值。
    3. 基本类型通常比装箱类型更省时间和空间。
  • 装箱基本类型使用==进行比较几乎总是错误的。
    new Integer(42) == new Integer(42)的值为false。

  • 装箱基本类型在自动拆箱时,如果初始值为null,则会抛出空指针异常。它可以在任何位置发生。

  • 什么时候使用装箱类型:

    1. 作为集合中的元素、键、值
    2. 在参数化类型(泛型)中,必须使用装箱类型作为类型参数
    3. 在发射调用时,必须使用装箱类型

总之,在可以选择时,优先使用基本类型。
自动装箱减少了使用装箱类型的繁琐性,但并没有减少它的风险。

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

本条目讨论一些不应该使用字符串的情形。

  • 字符串不适合代替其他的值类型
    当一段数据被网络、文件或键盘设备读取时,在流中数据是以字符串存在的,但如果它是数值,就应该被转换为适当的数值类型;是布尔值就转换为boolean类型;如果不存在这样的类型,就编写一个类型;

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

  • 字符串不适合代替聚集类型
    如果一个实体有多个组件,用一个字符串来表示这个实体通常是不恰当的。
    String compoundKey = className +"#" + i.next(),这个compoundKey在使用中,如果也有其他的字符串中带有#,则会难以区分,比较混乱。

  • 字符串也不适合代替能力表。
    字符串被用于某种功能进行授权访问。

总之,如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,应该避免使用字符串来表示。

51. 当心字符串连接的性能

字符串连接操作符+是把多个字符串合并为一个字符串的便利途径。
字符串的连接操作不适合运用在大规模的场景中,为连接n各字符串而重复的使用字符串连接操作符,需要n的平方级的时间。这是因为字符串的不可变,当两个字符串拼接时,它们的内容都要被拷贝。

  1. 使用StringBuilder代替String。
  2. 使用字符数组,每次只处理一个字符串,而不是将他们组合起来

52. 通过接口引用对象

如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明。

  • 使用接口作为类型,会使程序更加灵活。
    改变实现的时候会更加方便,且不会影响外部代码。因为新的实现性能更高。

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

  • 如果类提供了接口中不存在的方法,就可以使用类作为引用。

53. 接口优先于反射机制

核心反射机制(core reflection facility)提供了通过程序来访问关于已装载的类的信息。

  • 反射机制允许你能够操作它们的底层对等体。通过调用反射所提供的方法创建实例、调用方法、并访问底层类中的域。
    反射机制还允许一个类使用另一个类,即使当前者被编译时后者还不存在。

  • 反射的代价:

    • 丧失了编译时类型检查的好处,包括异常检查
    • 执行反射访问所需要的代码非常笨拙和冗长
    • 性能损失。

反射机制对于特定的复杂的应用来说是必要的。
如果你的程序必须要与编译时未知的类一起工作,如有可能,就应该仅仅使用反射机制来实例化对象,而访问对象时则使用编译时已知的某个接口或超类。

54. 谨慎的使用本地方法

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

  • 本地方法的三种用途:

    • 访问特定于平台的机制,如访问注册表和文件锁
    • 访问遗留代码库,从而可以访问遗留数据
    • 编写程序中注重性能的部分,以提高系统的性能。(不提倡)
  • 本地方法的缺点:

    1. 本地语言是不安全的,所以使用本地方法的应用程序也不再能免受内存毁坏错误的影响。
    2. 使用本地方法的程序也难以调试
    3. 在进入和推出本地代码时,需要相关的固定开销

总之,在使用本地方法前三思。为了安全等情况可以使用。

55. 谨慎的进行优化

优化的弊大于利,特别是不成熟的优化。

  • 不要因为性能而牺牲合理的结构。要努力编写好的程序而不是快的程序。

  • 努力避免那些限制性能的设计决策。

  • 要考虑API设计决策的性能后果。
    使公有的类型成为可变的,这可能会导致大量不必要的保护性拷贝。
    在使用复合模式的公有类中使用继承,会把这个类与它的超类永远绑定在一起,从而人为的限制了子类的性能;
    在API中使用实现类而不是接口,会束缚在一个实现类上,即便之后出现更快的实现你也无法使用;

  • 一旦谨慎的设计了程序,并产生了一个清晰、明了、结构良好的实现,那么就到了考虑优化的时候了。
    在优化前后对性能进行测量,量化你的效果。

56. 遵守普遍接受的命名惯例

Java平台建立了一整套很好的命名惯例,这些命名惯例分为两大类:字面的(typographical)和语法的(grammatical)。

  • 字面的命名惯例涉及 包、类、接口、方法、域和类型变量。
    如果违反了这些惯例,在使用时会变得困难,难以维护;给程序员带来困惑和烦恼,并使他们做出错误的假设,造成程序出错。

  • 包的名称应该是层次状的,用句号.分割每个部分。
    包名应该以你的组织的Internet域名开头,并且顶级域名放在前面如com.android。标准类库和一些可选的包,以java、javax开头,用户创建的包绝对不能以这两个开头。
    包的其余部分应该包括一个或多个描述该包的组成部分。这些组成部分应该比较简单,不超过8个字符。使用有意义的缩写。如com.android.develop

  • 类和接口的名称、包括枚举、注解,都应该包括一个或多个单词,每个单词首字母大写。
    方法、变量、局部变量的名称同样,不过第一个首字母小写。

  • 常量域:所有字母大写。

  • 泛型名称:通常由单个字母组成。
    T表示任意类型;
    E表示集合的元素类型;
    K、V表示映射的键、值类型;
    X表示异常;
    序列可以是上述的任何一组:T1,T2

  • 执行某个动作的方法通常用动词或者动词短语来命名。

  • 对于返回boolean值得方法,其名称往往以is开头,很少用has
    对于返回非boolean值得方法,方法名以get开头;

  • 转换对象类型的方法、返回不同类型的独立对象的方法,通常被称为toType,如toString和toArray;
    返回视图的方法通常被称为asType,如asList;
    返回一个与被调用对象同值得基本类型方法,通常被称为typeValue,如intValue;
    静态工厂常用名称为valueOfofgetInstancenewInstancegetTypenewType等。

如果长期养成的习惯用语与此不同,请不要盲目遵从这些命名惯例。请运用常识。

0 0