zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Effective-java-读书笔记之通用程序设计

JAVA 通用 程序设计 读书笔记 Effective
2023-06-13 09:13:53 时间

第57条 将局部变量的作用域最小化

要使局部变量的作用域最小化, 最有力的方法就是在第一次使用它的地方声明.

几乎每个局部变量的声明都应该包含一个初始化表达式. (例外: try-catch).

for循环允许声明循环变量, 其作用域被限定在正好需要的范围之内. -> 优于while循环.

方法应该小而集中.

第58条 for-each循环优先于传统的for循环

for-each循环(增强型for循环)在简洁性和预防Bug方面有着传统for循环无法比拟的优势, 并且没有性能损失, 应该尽可能地使用for-each循环.

增强型for循环中的:读作in.

但是有三种情况无法使用for-each循环:

  • 过滤删除.
  • 转换更新.
  • 平行迭代. 多个集合的同步位移.

for-each循环可以用在任何实现了Iterable接口的对象上.

第59条 了解和使用类库

举例: 随机数的例子 -> 了解和使用类库. (Java 7不使用Random, 而用ThreadLocalRandom. 另, 还有SplittableRandom.)

不要重新发明轮子.

好处:

  • 充分利用专家知识和前人经验.
  • 时间.
  • 类库的性能会不断提高.
  • 类库功能会扩展.
  • 使代码融入主流, 易读易维护.

关注类库更新新加入的功能. 尤其是java.lang, java.utiljava.io包下的.

还有collections, streams, concurrent等.

第60条 如果需要精确的答案, 请避免使用floatdouble

货币计算不能用floatdouble, 应该用BigDecimal, int或者long.

BigDecimal没有原生类型使用起来方便, 而且会有性能影响. 优点是可以自己选择舍入模式.

int(9位)或long(18位)需要自己处理小数点移位.

第61条 基本类型优先于装箱基本类型

基本类型和装箱基本类型的三个主要区别:

  • 基本类型只有值, 而装箱基本类型则具有与它们的值不同的同一性.
  • 基本类型只有功能完备的值, 而装箱基本类型还有非功能值null.
  • 基本类型通常比装箱基本类型更节省时间和空间.

有问题的情形:

  • 对装箱基本类型运用==操作符进行比较, 几乎总是错误的.
  • 当一项操作中混合装箱基本类型和基本类型时, 会自动拆箱, 如果null被自动拆箱会抛出NullPointerException.
  • 变量被反复自动装箱和拆箱, 会有性能问题.

装箱基本类型的合理用处:

  • 作为集合中的元素, 键和值.
  • 在参数化类型中必须使用装箱基本类型.
  • 在进行反射的方法调用时必须使用装箱基本类型.

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

字符串不适合代替其他的值类型. -> int, float, BigInteger, boolean等.

字符串不适合代替枚举类型, 聚集类型, 也不适合代替能力表(capabilities).

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

若使用不当, 字符串会比其他的类型更加笨拙, 更不灵活, 速度更慢, 也更容易出错.

第63条 当心字符串连接的性能

为连接n个字符串而重复地使用字符串连接操作符(+), 需要n的平方级的时间.

这是由于字符串的不可变而导致的. 当两个字符串被连接在一起时, 它们的内容都需要被拷贝.

连接多个项目, 为了性能, 请使用StringBuilderappend().

第64条 通过接口引用对象

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

这样做程序将会更加灵活 -> 当你决定更换实现的时候, 值需要改变调用构造器的那句.

如果没有适当的接口, 则使用类层次结构中提供了必要功能的最基础的类.

第65条 接口优先于反射机制

反射机制提供了"通过程序来访问关于已装载的类的信息"的能力.

这种能力的代价:

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

也有一些情形, 通过以非常有限的形式利用, 你可以获得反射的好处, 而不被它的cost影响:

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

第66条 谨慎地使用本地方法

Java Native Interface (JNI)允许Java应用程序可以调用本地方法(native method), 即本地程序设计语言(C或者C++)来编写的特殊方法.

使用本地方法来提高性能的做法不值得提倡, 因为JVM实现变得越来越快了.

使用本地方法有一些缺点: 不安全; 与平台相关, 不可自由移植; 更难调试; 进入和退出本地代码时需要相关的固定开销; 需要胶合代码的本地方法编写起来单调乏味, 且难以阅读.

第67条 谨慎地进行优化

不要因为性能而牺牲合理的结构.

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

要考虑API设计决策的性能后果.

为了性能而包装API -> bad idea.

在每次试图做优化之前和之后, 要对性能进行测量.

总而言之, 不要费力去编写快速的程序, 应该努力编写好的程序, 速度自然会随之而来.

在设计系统的时候, 特别是在设计API, 线路层协议和永久数据格式的时候, 一定要考虑性能的因素.

当构建完成之后, 要测量它的性能. 如果不够快, 可以在性能剖析器的帮助下, 找到问题的根源, 然后设法优化系统中相关的部分.

第一个步骤是检查所选择的算法: 再多的底层优化也无法弥补算法的选择不当.

必要时重复这个过程, 在每次改变之后都要测量性能, 直到满意为止.

第68条 遵守普遍接受的命名惯例

Java平台建立了一整套很好的命名惯例(naming convention).

  • 包/模块名: 层次状, 小写字母或数字(很少使用数字), .分隔.
  • 类, 接口: 一个或多个单词, 首字母大写.
  • 方法和域, 局部变量: 首字母小写.
  • 常量域: 一个或多个大写的单词, 下划线分隔.
  • 类型参数: 单个字母: T表示任意的类型, E表示集合元素类型, K和V表示映射的键和值, X表示异常. 任何类型的序列可以是T, U, V或者T1, T2, T3.

一些语法惯例:

  • 可实例化的类通常用单数名词, 不可实例化的辅助类通常用复数名词, 如Collections.
  • 方法名通常是动词或动词短语.
  • 返回布尔值的方法通常以ishas开头.
  • 方法返回非布尔值时, 有时用名词命名, 如size, 有时加get.
  • 转换类型的方法通常用toType.
  • 返回不同视图的方法用asType.
  • 还有typeValue和静态工厂方法等.