Effective Java 的笔记(一)

 

最近,在啃《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 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 xy,当且仅当 xy 引用同一个对象时,此方法才返回 truex == y 具有值 true)。

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

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

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
  • 传递性:对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true
  • 一致性:对于任何非空引用值 xy,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.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;
	    }

转载于:https://www.cnblogs.com/sodmecai/archive/2012/05/18/2507633.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值