《Effective Java》学习笔记 —— 通用程序设计

时间:2021-10-19 21:48:36

  本章主要讨论局部变量、控制结构、类库、反射、本地方法的用法及代码优化和命名惯例。

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

  * 在第一次使用的它的地方声明局部变量(就近原则)。

  * 几乎每个局部变量的声明都应该包含一个初始化表达式。如果还没有足够的信息进行初始化,就延迟这个声明(例外:try-catch语句块)。

  * 如果在循环终止之后不再需要循环变量的内容,for循环优先于while循环。

  * 使方法小而集中(职责单一)。

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

  * 如果正在编写的类型表示的是一组元素,即使选择不实现Collection,也要实现Iterable接口,以便使用for-each循环。

  * for-each循环在简洁性和预防Bug方面有着传统for循环无法比拟的优势,且没有性能损失。但并不是所有的情况都能用for-each循环,如过滤、转换和平行迭代等。

  存在Bug的传统for循环代码示例:

 import java.util.*;

 /**
* @author https://www.cnblogs.com/laishenghao/
* @date 2018/10/7
*/
public class OrdinaryFor {
enum Suit {
CLUB, DIAMOND, HEART, SPADE,
}
enum Rank {
ACE, DEUCE, THREE, FOUR, FIVE,
SIX, SEVEN, EIGHT, NINE, TEN,
JACK, QUEEN, KING,
} public List<Card> createDeck() {
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values()); List<Card> deck = new ArrayList<>();
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) {
deck.add(new Card(i.next(), j.next()));
}
}
return deck;
} static class Card {
final Suit suit;
final Rank rank; public Card(Suit suit, Rank rank) {
this.suit = suit;
this.rank = rank;
} // other codes
}
}

采用for-each循环的代码(忽略对Collection的优化):

     public List<Card> createDeck() {
Suit[] suits = Suit.values();
Rank[] ranks = Rank.values(); List<Card> deck = new ArrayList<>();
for (Suit suit : suits) {
for (Rank rank : ranks) {
deck.add(new Card(suit, rank));
}
}
return deck;
}

第47条 了解和使用类库

  * 优先使用标准类库,而不是重复造*。

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

  * float和double尤其不适合用于货币计算,因为要让一个float或double精确的表示o.1(或10的任何其他负数次方值)是不可能的。

System.out.println(1 - 0.9);

上述代码输出(JDK1.8):

《Effective Java》学习笔记 —— 通用程序设计

  * 使用BigDecimal(很慢)、int或者long进行货币计算。

 

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

  * 在性能方面基本类型优于装箱基本类型。当程序装箱了基本类型值时,会导致高开销和不必要的对象创建。

  * Java1.5中增加了自动拆装箱,但并没有完全抹去基本类型和装箱基本类型的区别,也没有减少装箱类型的风险。

  如下代码在自动拆箱时会报NullPointerException:

  Map<String, Integer> values = new HashMap<>();
int v = values.get("hello");

  

  再考虑两个例子:

例子1:输出true

Integer num1 = 10;
Integer num2 = 10;
System.out.println(num1 == num2);

例子2:输出false

    Integer num1 = 1000;
Integer num2 = 1000;
System.out.println(num1 == num2);

  为啥呢?

  我们知道 “==” 比较的是内存地址。而Java默认对-128到127的Integer进行了缓存(这个范围可以在运行前通过-XX:AutoBoxCacheMax参数指定)。所以在此范围内获取的Integer实例,只要数值相同,返回的是同一个Object,自然是相等的;而在此范围之外的则会重新new一个Integer,也就是不同的Object,内存地址是不一样的。

  具体可以查看IntegerCache类:

     /**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/ private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[]; static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h; cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
} private IntegerCache() {}
}

IntegerCache

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

  * 字符串不适合代替其他的值类型。

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

  * 字符串不适合代替聚集类型(一个实体有多个组件)。

  * 字符串也不适合代替能力表(capacityies;capacity:能力,一个不可伪造的键被称为能力)。  

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

  * 构造一个较小的、大小固定的对象,使用连接操作符(+)是非常合适的,但不适合运用在大规模的场景中。

  * 如果数量巨大,为了获得可以接受的性能,请使用StringBuilder(非同步),或StringBuffer(线程安全,性能较差,一般不需要用到)。

第52条 通过接口引用对象

  * 这条应该与“面向接口编程”原则一致。

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

如声明一个类成员应当优先采用这种方法:

private Map<String, Object> map = new HashMap<>();

而不是:

private HashMap<String, Object> map = new HashMap<>();

  * 如果没有合适的接口存在,则完全可以采用类而不是接口。

  * 优先采用基类(往往是抽象类)。

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

  * 反射的代价:

    (1)丧失了编译时进行类型检查的好处。

    (2)执行反射访问所需要的代码非常笨拙和冗长(编写乏味,可读性差)。

    (3)性能差。

  * 当然,对于某些情况下使用反射是合理的甚至是必须的。

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

  * 本地方法(native method)主要有三种用途:

    (1)提供“访问特定于平台的机制”的能力,如访问注册表(registry)和文件锁(file lock)等。

    (2)提供访问遗留代码库的能力,从而可以访问遗留数据(legacy data)。

    (3)编写代码中注重性能的部分,提高系统性能(不值得提倡,JVM越来越快了)。

  * 本地方法的缺点:

    (1)不安全(C、C++等语言的不安全性)。

    (2)本地语言与平台相关,可能存在不可移植性。

    (3)造成调试困难。

    (4)增加性能开销。在进入和退出本地代码时需要一定的开销。如果本地方法只是做少量的工作,那就有可能反而会降低性能(这点与Java8的并行流操作类似)。

    (5)可能会牺牲可读性。

第55条 谨慎地进行优化

  * 有三条与优化相关的格言是每个人都应该知道的:

    (1)More computing sins are committed in the name of efficiency (without necessarily achieving it)than for any other single reason——including blind stupidity.

       —— William AWulf

    (2)We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

      —— Donald E. Knuth

    (3)We follow two rules in the matter of optimization:

      Rule 1. Don't do it.
      Rule 2(for experts only). Don't do it yet——that is, not until you have a perfectly clear and unoptimized solution.

      —— M. J. Jackson

  以上格言说明:优化的弊大于利,特别是不成熟的优化。

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

    实现上的问题可以通过后期优化,但遍布全局且限制性能的结构缺陷几乎是不可能被改正的。但并不是说在完成程序之前就可以忽略性能问题。

  * 努力避免那些限制性能的设计决策,考虑API设计决策的性能后果。

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

  * 把标准的命名惯例当作一种内在的机制来看待。

本文地址:https://www.cnblogs.com/laishenghao/p/effective_java_note_general_programming.html