Effective JAVA2 学习

1:将局部变量的作用域最小

2:for-each循环优先于传统的for循环

/**
 * Can you spot the bug:
 */
enum Suit {1, 2, 3, 4, 5}
enum Rank {10, 9, 8, 7, 6}

...
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values());

List<Card> deck = new ArrayList<Card>();
for(Iterator<Suit> i = suits.itertor(); i.hasNext()) {
    for(Iterator<Rank> j = ranks.iterator(); j.hasNext()) {
        deck.add(new Card(i.next() + j.next()));//Here!
    }
}


for-each循环在简洁性和预防bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。遗憾的是,有三种常见的情况无法用for-each循环:
1.过滤-----如果需要遍历集合,并删除选定的元素,就需要显式的迭代器,以便可以调用它的remove方法。
2.转换-----如果需要遍历列表或者数组,并取代它部分或者全部的元素值,就需要选择列表迭代器或者数组索引,以便设定元素的值。
3.平行迭代-----如果需要平行的遍历多个集合,就需要显式的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。

3:如果需要精确的答案,请避免使用float和double
正确方法是使用BigDecimal,int或者long

然而使用BigDecimal有两个缺点,与使用基本运算类型相比,这样做很不方便,而且很慢。
如果数值范围没超过9位十进制数字,用int,如果不超过18位,使用long,如果超过了18位,只能用BigDecimal

4:基本类型优先于装箱基本类型
基本类型与装箱基本类型之间由三个主要区别:
第一,基本类型只有值,而装箱基本类型则具有与他们的值不同的同一性。换句话说,两个装箱基本类型可以具有相同的值和不同的同一性。第二,基本类型只有功能完备的值,而每个装箱基本类型除了它对应基本类型的所有功能值之外,还有个非公能值:null。最后一点区别是基本类型通常比装箱基本类型更节省时间和空间。

看例子:
//Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = new Comparator<Integer>() {
    public int compare(Integer first, Integer second) {
        return first < second ? -1 : (first == second ? 0 : 1);//Here
    }
};

对装箱基本类型运行==操作符几乎总是错误的

再看一个:
public class Unbelievable {
    static Integer i;
    public static void main(String[] args) {
        if(i == 42)
            System.out.println("Unbelievable!");
    }
}

它不是打印出Unbelievable----它在计算表达式(i == 42)的时候抛出了NullPointerException异常。问题在于,i是Integer,而不是int,就像所有的对象引用一样,它的初始值是null。(i == 42)时,当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱。如果null对象自动拆箱,就会得到一个NullPointerException异常。

最后一个例子:
//Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for(long i = 0; i < Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

程序不小心将一个局部变量(sum)声明为装箱基本类型Long,变量被反复的装箱,拆箱导致明显的性能下降。

5,谨慎进行优化

有三条有关优化的格言
“很多计算上的果实都被归咎于效率(没有必要达到的效率),而不是任何其他的原因---甚至包括盲目的做事。”

“不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。”

“在优化方面,我们应该遵守两条规则:
规则1:不要进行优化。
规则2(仅针对专家):还是不要进行优化---也就是说,在你还没有绝对清晰的为优化方案之前,请不要进行优化。”

不要因为性能而牺牲合理的就够。要努力编写好的程序而不是快的程序。这并不意味着,在完成程序之前就可以忽略性能问题。必须在设计过程中考虑到性能问题。努力避免那些限制性能的设计决策。

6,覆盖equals时请遵守通用约定
equals方法实现了等价关系
自反性(reflexive):对于任何非null的引用值,x.equals(x)必须返回true。
对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
传递性(transitive):对于任何非null的引用值x,y和z,如果x.equals(y)为true,y.equals(z)为true,那么x.equals(z)也为true。
一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。
对于任何非null的引用值x,x.equals(null)必须返回false

实现高质量equals方法的诀窍:
1.使用==操作符检查“参数是否为这个对象的引用”
2.使用instanceof操作符检查“参数是否为正确的类型”
3.把参数转换成正确的类型
4.对于该类中的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。其中float和double域要分别调用Float.compare和Double.compare,对他们进行特殊处理是有必要的,因为存在着Float.NaN,-0.0f以及类似的double常量。有些对象引用域包含null可能是合法的,所以,为了避免可能NullPointerException异常,则使用下面的习惯来比较这样的域 (field == null ? o.field == null : field.equals(o.field))
5.当你编写完成了equals方法之后,应该问自己三个问题:它是都是对称的,传递的,一致的

告诫:
覆盖equals时总是覆盖hashCode
不要企图让equals方法过于智能
不要将equals声明中的Object对象替换为其他的类型因为它只是重载了原来的equals(Object o)方法

你可能感兴趣的:(J#)