您的位置:首页 > Web前端

Effective Java 的笔记(一)

2012-05-18 13:37 363 查看
最近,在啃《Effective Java》(下文用《E》表示),从中学习到了不少以前在开发过程中没有注意到的一些问题,收获不少。

一、Item48 关于BigDecimal 和float double的问题。

看到它的Item48,讨论了关于float和double类型的问题。以前对此都比较疏忽的,随便使用一个float四舍五入一下就过去了,看完之后,重新认识了一下Java中关于数值的处理。

起因是,使用float或者double无法精确的描述一个数字,比如:0.1

public class Test {
public static void main(String[] args) {
System.out.println(1.00 - 9 * 0.10);
}
}

在IDE中运行的结果为:0.09999999999999998。

如何解决此类问题呢?答案是使用BigDecimal。

翻看JDK,查看BigDecimal的说明如下:

不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。

可以看到,该类型可以描述任意精度的有符号数。表示的方式其实就是我们常说的科学计数法,使用底数和幂来描述一下数字的大小。

public static void main(String[] args) {
BigDecimal bda = new BigDecimal("1.0");
BigDecimal bdb = new BigDecimal("0.9");
System.out.println(bda.subtract(bdb));
System.out.println(1.0 - 0.9);
}

IDE的运行结果如下:

0.1
0.09999999999999998

在《E》中,作者对BigDecimal提出了2种情况不推荐使用:

1、使用BigDecimal的效率比使用int long等类型效率要低,原因显而易见的;

2、如果是解决一个小的问题,就没有必要使用BigDecimal。

二、Item 9 当重写equals方法的时候,总是重写hashCode方法

《E》中的黑体字:每一个重写了equals方法的类必须重写hashCode方法。

JDK中关于hashCode的说明:

在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地 返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用
hashCode
方法都必须生成相同的整数结果。


如果根据
equals(java.lang.Object)
方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

下面看看,如果不写hashCode方法的结果如何:

import java.util.HashMap;
import java.util.Map;

public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;

public PhoneNumber(int areaCode, int prefix,
int lineNumber) {
rangeCheck(areaCode,    999, "area code");
rangeCheck(prefix,      999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode  = (short) areaCode;
this.prefix  = (short) prefix;
this.lineNumber = (short) lineNumber;
}

private static void rangeCheck(int arg, int max,
String name) {
if (arg < 0 || arg > max)
throw new IllegalArgumentException(name +": " + arg);
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix  == prefix
&& pn.areaCode  == areaCode;
}
public static void main(String[] args) {
Map<PhoneNumber, String> m
= new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
}
}

运行结果为:NULL。导致在map.get的时候,在调用equal方法的时候失败,显然是因为违反了hashCode方法的第二条(黑体)的规则。

如果加上hashCode方法,程序运行正常:

@Override public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}

注意:hashCode方法返回的是int,也就是说,有可能不同的2个object,有相同的hashCode值。见JDK文档的关于hashCode说明的第三条:

如果根据
equals(java.lang.Object)
方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。

改情况的发生叫做“hash碰撞”。如果hash碰撞的几率越大,那么在map.get方法执行的过程也越慢。原因看HashMap的get方法的实现。

public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}

table的定义:transient Entry[] table;

表示每个hash节点下面有多个key,所以,判断的条件为:

if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

三、Item8 书写规范的equals方法。

该条例作者用了很大的篇幅,可见该条例的重要性。

首先看JDK中对equals的描述:

Object
类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值
x
y
,当且仅当
x
y
引用同一个对象时,此方法才返回
true
x == y
具有值
true
)。

从文档中可以清楚的看到,该方法是Object类的实现,即只有同时引用一个对象的时候,才相等。所以,我们需要在程序中,根据自己的业务规则,重写equals方法。

下面再来看看JDK是怎么描述2个object相等的:

自反性:对于任何非空引用值
x
x.equals(x)
都应返回
true


对称性:对于任何非空引用值
x
y
,当且仅当
y.equals(x)
返回
true
时,
x.equals(y)
才应返回
true


传递性:对于任何非空引用值
x
y
z
,如果
x.equals(y)
返回
true
,并且
y.equals(z)
返回
true
,那么
x.equals(z)
应返回
true


一致性:对于任何非空引用值
x
y
,多次调用 x.equals(y) 始终返回
true
或始终返回
false
,前提是对象上
equals
比较中所用的信息没有被修改。

对于任何非空引用值
x
x.equals(null)
都应返回
false


那么,如何写出一个正确的equals方法呢?《E》给出了5点要求:

1、用==来判断是否是同一个对象

2、用instanceof来判断数据类型是否一致

3、将传入的参数强制类型转换

4、类中每个“签名”字段的比较,注意对Null对象的处理

5、写完之后,问问自己,equals是否符合JDK中的规范

下面看看一个规范的equals方法:

@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix  == prefix
&& pn.areaCode  == areaCode;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: