• 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;
    	    }
  • 相关阅读:
    ActiveReports 报表控件官方中文入门教程 (2)-创建、数据源、浏览以及发布
    SpreadJS 中应用 KnockoutJS 技术
    HTML5 Wijmo:控制 Wijmo Grid 插件的编辑模式
    Studio for WPF:使用 C1TileView 创建图片库
    随心所欲导出你的 UI 界面到 PDF 文件
    Studio for Winforms FlexGrid: 创建分类汇总
    Hibernate中事务中事务相关知识点
    Hibernate-一级缓存
    Hibernate-实体详解
    算法之旅-First之选择排序
  • 原文地址:https://www.cnblogs.com/sodmecai/p/2507633.html
Copyright © 2020-2023  润新知