Effective Java 的笔记(一)
2012-05-18 13:37
363 查看
最近,在啃《Effective Java》(下文用《E》表示),从中学习到了不少以前在开发过程中没有注意到的一些问题,收获不少。
一、Item48 关于BigDecimal 和float double的问题。
看到它的Item48,讨论了关于float和double类型的问题。以前对此都比较疏忽的,随便使用一个float四舍五入一下就过去了,看完之后,重新认识了一下Java中关于数值的处理。
起因是,使用float或者double无法精确的描述一个数字,比如:0.1
在IDE中运行的结果为:0.09999999999999998。
如何解决此类问题呢?答案是使用BigDecimal。
翻看JDK,查看BigDecimal的说明如下:
不可变的、任意精度的有符号十进制数。BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。因此,BigDecimal 表示的数值是 (unscaledValue × 10-scale)。
可以看到,该类型可以描述任意精度的有符号数。表示的方式其实就是我们常说的科学计数法,使用底数和幂来描述一下数字的大小。
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方法的结果如何:
运行结果为:NULL。导致在map.get的时候,在调用equal方法的时候失败,显然是因为违反了hashCode方法的第二条(黑体)的规则。
如果加上hashCode方法,程序运行正常:
注意:hashCode方法返回的是int,也就是说,有可能不同的2个object,有相同的hashCode值。见JDK文档的关于hashCode说明的第三条:
如果根据
改情况的发生叫做“hash碰撞”。如果hash碰撞的几率越大,那么在map.get方法执行的过程也越慢。原因看HashMap的get方法的实现。
table的定义:transient Entry[] table;
表示每个hash节点下面有多个key,所以,判断的条件为:
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
三、Item8 书写规范的equals方法。
该条例作者用了很大的篇幅,可见该条例的重要性。
首先看JDK中对equals的描述:
从文档中可以清楚的看到,该方法是Object类的实现,即只有同时引用一个对象的时候,才相等。所以,我们需要在程序中,根据自己的业务规则,重写equals方法。
下面再来看看JDK是怎么描述2个object相等的:
自反性:对于任何非空引用值
对称性:对于任何非空引用值
传递性:对于任何非空引用值
一致性:对于任何非空引用值
对于任何非空引用值
那么,如何写出一个正确的equals方法呢?《E》给出了5点要求:
1、用==来判断是否是同一个对象
2、用instanceof来判断数据类型是否一致
3、将传入的参数强制类型转换
4、类中每个“签名”字段的比较,注意对Null对象的处理
5、写完之后,问问自己,equals是否符合JDK中的规范
下面看看一个规范的equals方法:
一、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; }
相关文章推荐
- effective java 学习笔记(一) 2012-4-24
- Effective java笔记(七),通用程序设计
- Effective Java笔记-第9章:异常
- Effective Java 学习笔记 (7)
- Effective java笔记4--方法
- Effective Java笔记——第4章类和接口
- effective-java 学习笔记 覆写hashCode
- Effective java笔记3--类和接口1
- Effective java笔记(九),并发
- Effective Java学习笔记 第60条: 优先使用标准的异常
- Effective Java 学习笔记(8)
- Effective java笔记3--类和接口2
- Effective Java学习笔记 4 通过私有构造器增强不可实例化的能力
- Effective Java 学习笔记(二)
- effective java 笔记之创建和销毁对象
- Effective Java笔记之改写equals的通用约定
- Effective java笔记(十),序列化
- Effective Java 学习笔记(1)
- Effective Java 学习笔记(24)
- Effective Java 学习笔记(第51条:当心字符串的连接性能)