`
384444165
  • 浏览: 257581 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Effective Java读书笔记、感悟——2.2 其余Object通用方法

阅读更多

 

最近一直在忙着完成作业,看书和写博客的进度大大受到折扣。假期回去把最近Nachos的实验重新做一遍,好好总结、测试过后写份博客,中间还是学到了很多东西,虽然学不到什么API的使用。不多说了,继续Java,这才是正道(要遭批斗的言论)。这次的博客中有很多问题都没有注意到,只是做了简单的笔记记录和一些小地方的注释,着重说明了hashCode,尤其clone没有更深入的探究,但是使用过程中有时候只是简单的clone,所以尽量也使用的时候详细的阅读以下该类的clone注释(还有个原因是clone使用需要谨慎,书中提出了推荐的方法,由于最近时间紧张也就没有选择深入探究clone)。

 

二:覆盖equals时总要覆盖hashCode

Object规范中提到:(非原文,仅理解和摘要)

à执行期间对象的equals比较操作用到的信息没有被修改,那么调用多次hashCode应该始终返回同一个整数,多次运行可以不同。

à两个对象根据equals比较相等,那么他们的hashCode返回相同的值。

àequals不等,hashCode不一定不等,但是给完全不同的对象产生不同的散列码提高性能

如果没有覆盖hashCode而违反的关键约定是第二条。

如果试图和HashMap(在应用程序中被非常广泛的用到)HashSetHashtable一起使用作为key(作为key之后会根据key产生的hashcode进行散列,如果很多不同的对象返回了相同的key就将散列表变成了很少的长链表,降低性能),不保证hashCode被覆盖将带来问题。

 

书中提到的散列函数(可以在其他自己需要构造散列表的时候使用):

 

1、把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。

2、对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤:

  a. 为该域计算int类型的散列码c:

    i.    如果该域是boolean类型,则计算(f ? 1 : 0)。

    ii.   如果该域是byte、short、char或者int类型,则计算(int)f。

    iii.  如果该域是long类型,则计算(int)(f ^ (f >>> 32))。

    iv.  如果该域是float类型,则计算Float.floatToIntBits(f)。

    v.   如果该域是double类型,则计算Double.doubleToLongBits(f),然后照2.a.iii。

    vi.   如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals方法来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个范式,然后针对这个范式调用hashCode。如果这个域的值为null,则返回0。

    vii.  如果该域是一个数组,则需要把每一个元素当做单独的域来处理,也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组域中的每个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法。

  b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:result = 31 * result + c ;

3、返回result。

4、写完了hashCode方法之后,问问自己“相等的实例是否都具有相等的散列码”。

 

之所以采用31是因为它是一个奇素数。如果乘数是偶数,并且惩罚溢出的话信息会丢失,因为与2相乘等于移位运算。使用素数的好处并不明显大,但是习惯使用素数计算散列结果。31还有一个很好的特性,即用移位和减法来代替乘法可以得到更好的性能:31*i==(i<<5)-I。现代的VM可以自动完成这种优化。合理利用移位操作提高效率是好的编程习惯

 

这里先从外围,举最常用的Java的HashMap来说明一下HashCode的作用:

下面是put方法,其中是根据用户的hashCode产生的值的基础上做了一次散列计算。用户散列函数效果太差会直接影响到HashMap的使用性能。(尤其我们经常会将自己编写的Bean等作为value传入,如果没有覆盖hashCode)

 

 

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

        modCount++;
        addEntry(hash, key, value, i);
        return null;
}

 

 

下面看下HashMap的hash方法做了什么:

 

 

/**
     * Applies a supplemental hash function to a given hashCode, which
     * defends against poor quality hash functions.  This is critical
     * because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 

 

可以阅读两个部分的注释,使用null作为key不是好的做法。同时下面的注释也需要关注,我尝试翻译却总做不太好,就直接阅读英语注释吧,也不会出现翻译带来的偏差。

 

这里主要是说明一下hashcode的作用,上面函数对用户返回的hashcode做了不同的散列,使。Hashtable中是对hashcode做了如下的处理得到的数组索引

 

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

 

为什么用到hashCode的地方都要这样做呢,hashCode默认返回的值是相同的呢还是不同的呢,Object中并没有属性,我们在使用HashMap的时候没有切身的感觉到未覆盖hashcode带来的麻烦。如果相同上面的处理带不来任何用处,但是如果是基本都不同的那么上面的代码就能带来很大的性能优化了。直接看代码

 

/**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     * <p>
     * The general contract of {@code hashCode} is:
     * <ul>
     * <li>Whenever it is invoked on the same object more than once during
     *     an execution of a Java application, the {@code hashCode} method
     *     must consistently return the same integer, provided no information
     *     used in {@code equals} comparisons on the object is modified.
     *     This integer need not remain consistent from one execution of an
     *     application to another execution of the same application.
     * <li>If two objects are equal according to the {@code equals(Object)}
     *     method, then calling the {@code hashCode} method on each of
     *     the two objects must produce the same integer result.
     * <li>It is <em>not</em> required that if two objects are unequal
     *     according to the {@link java.lang.Object#equals(java.lang.Object)}
     *     method, then calling the {@code hashCode} method on each of the
     *     two objects must produce distinct integer results.  However, the
     *     programmer should be aware that producing distinct integer results
     *     for unequal objects may improve the performance of hash tables.
     * </ul>
     * <p>
     * As much as is reasonably practical, the hashCode method defined by
     * class {@code Object} does return distinct integers for distinct
     * objects. (This is typically implemented by converting the internal
     * address of the object into an integer, but this implementation
     * technique is not required by the
     * Java<font size="-2"><sup>TM</sup></font> programming language.)
     *
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();

 

够倒霉的,本地方法,没有在Object.java中实现,并且注释中提到的全部都是Effective Java中提到的规范。继续查看文章。这段代码找不到,但是可以根据Effective Java中提到的覆盖equals时要覆盖hashCode,且两者的关系可以知道查看equals也许有所帮助:

 

public boolean equals(Object obj) {
        return (this == obj);
}
 

内存地址?按照equals的内容应该是吧。编写测试程序吧,toString可以用来参考

 

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

 可以用equals和hashcode分配去验证,开始的时候发现应该是对的,但是随着数据量的增加,慢慢好像发生了问题,hashcode开始有重复(CHECKSIZE为1w时),下面是测试代码:

 

public static void main(String[] args) {
        List<Object> listHashCode =  new ArrayList<Object>();
        List<Object> listObj =  new ArrayList<Object>();
        
        //hashcode是否是内存地址
        for (int i = 0; i < CHECKSIZE; i++) {
            Object obj=new Object();
            if (listHashCode.contains(obj.toString())) {
                System.out.println(obj.toString() +"  exists in the listHashCode. "+ i+" ; Is obj contain in listObj :"+listObj.contains(obj));
            }
            else {
            	listHashCode.add(obj.toString());
            }
            listObj.add(obj);
        }
 }   
 
但是同时这里也说明了hashcode默认基本都是不同的,再细追究下去可能要走死胡同了,也没有意义。hashcod也没有必要说给不同对象都产生不同的值,上面的例子只会出现一次重复,可见已经可以取得较好的散列性能。
(还是补充说明一下Java开发人员给出的原因:hashCode()的缺省实施通过将对象的内存地址对映于一个整数值来生成。由于在某些架构上,地址空间大于int值的范围,两个不同的对象有相同的 hashCode()是可能的。如果您忽略了hashCode(),您仍旧可以使用System.identityHashCode()方法来接入这类缺省值。)

网上很多地方举了String的例子,我们看下

/**
     * Returns a hash code for this string. The hash code for a
     * <code>String</code> object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using <code>int</code> arithmetic, where <code>s[i]</code> is the
     * <i>i</i>th character of the string, <code>n</code> is the length of
     * the string, and <code>^</code> indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0) {
            int off = offset;
            char val[] = value;
            int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
}
 
可以看到, String类是使用它的 value值作为参数然后进行运算得出hashcode的。换句话说, 只要值相同的String不管是不是一个对象,hash值全部相等。虽然不得到所有不同对象不同的hashcode的最优情况,但是也有不错的性能了。
这部分就说到这了,再深入下去也没有意义了,使用规范就是开始说到的了,详细建议还是看下原书,指的一提的就是注意一同修改(可以直接忽略底层实现)和都不修改(直接使用底层实现,这种方式是针对Object的子类,如果其他的子类需要关注一下父类的实现是否对自己可用)是两种正确的使用方法。

 

三:始终要覆盖toString(很重要同样也很好理解)

默认的toString为:‘类名@散列码的无符号十六进制表示法’。

à在实际应用中,toString方法应该返回对象汇总包含的所有值得关注的信息,且这些信息是可以自描述的。

à无论是否制定格式,都应该在文档中明确的表明你的意图,且都要为信息提供一种编程式的访问途径。

 

四:谨慎的覆盖clone

 

àCloneable接口的作用是决定了Object中受保护的clone方法的实现行为:如果一个类实现了CloneableObjectclone方法就返回该对象的逐域拷贝,否则抛出CloneNotSupportedException。(极端的非典型接口实现方法)

à如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。

àclone方法就是另一个构造器,你必须确保它不会伤害到原始的对象,并确保正确的创建被clone对象中的约束条件。

à实现对象拷贝的好方法是提供一个拷贝构造器或拷贝工厂。

 

五:考虑实现Comparable接口

àJava平台类库中的所有值类都实现了Comparable接口。

àequals不同的是,在跨越不同类的时候,compareTo可以不做比较,直接抛出ClassCastException异常。(较equal此处更方便实现,问题较少)

àcompareTo没有规定返回值的大小,只规定了正负,可以利用这个特性规定返回值的含义或用来简化代码(是指不需要过多的判断值的所属区域,只需要判断正负)。但是compareTo的返回值不应该被用于逻辑处理,这样就破坏了方法的单一职责,并且为继承该类的子类带来了不必要的麻烦,如果程序员没有详细阅读注释很容易造成错误。并且由于返回值对属性的依赖可能为以后的修改带来风险。

分享到:
评论

相关推荐

    Effective Java读书笔记.pdf

    "Effective Java读书笔记" Effective Java是一本关于Java编程语言的经典...Effective Java读书笔记总结了Java语言的发展历程、静态工厂方法的应用、构造器模式的使用等重要知识点,为Java开发者提供了有价值的参考。

    effective java 读书笔记

    《Effective Java》是Java开发领域的经典著作,作者Joshua Bloch深入浅出地阐述了编写高效、健壮的Java代码的技巧和最佳实践。以下是对该书部分内容的详细解释: 1. **产生和销毁对象** - Item1:静态工厂方法相比...

    2021年EFFECTIVEJAVA读书笔记.docx

    Effective Java 读书笔记 - 枚举与注解 本文总结了Effective Java 中关于枚举与注解的知识点,涵盖了枚举类型的优点、使用指南、避免使用 int 常量、使用 EnumSet 和 EnumMap 等。 枚举类型的优点 枚举类型提供了...

    effectiveJava的笔记

    《Effective Java》是Java开发领域的经典著作,由Joshua Bloch编写,旨在提供一系列实用的编程准则和最佳实践。这本书的第三版包含了大量更新,涵盖了Java语言和平台的新发展,如Java 8和Java 9的新特性。以下是对...

    精版Effective STL读书笔记

    根据给定的文件信息,以下是对“精版Effective STL读书笔记”的详细解析,重点提炼了STL(标准模板库)中的关键知识点。 ### 标题:“精版Effective STL读书笔记” 此标题暗示了文档是针对《Effective STL》一书的...

    Effective Java第三版1

    《Effective Java》是Java编程领域的一本经典著作,由Joshua Bloch撰写,该书的第三版继续提供了关于如何编写高效、优雅、可维护的Java代码的指导。以下是基于给出的目录和部分内容提取的一些关键知识点: ### 第一...

    Effective-Java读书笔记

    《Effective Java》是Java...以上仅是《Effective Java》一书中部分核心知识点的概述,实际的读书笔记中会更详细地解释这些概念,并给出具体的示例代码。通过深入学习和实践,开发者可以极大地提升其Java编程的水平。

    《Effective Java》读书分享.pptx

    "Effective Java 读书分享" 《Effective Java》读书分享.pptx 是一本 Java 编程语言指南,旨在帮助开发者编写高质量、可维护的 Java 代码。该书包含 90 个条目,每个条目讨论一条规则,涵盖了 Java 编程语言的...

    effective c++读书笔记

    从给出的部分内容来看,读书笔记主要聚焦于以下几个知识点: 1. C++语言的联邦概念:C++是一个由多个次语言构成的语言联邦,这包括了C语言核心、面向对象的C++、模板C++以及标准模板库(STL)。这种理解对于深入...

    effective-java.pdf

    标题“effective-java.pdf”与描述“effective-java.pdf”表明本文档是关于Java编程实践的指南,且内容可能来自于一本名为《Effective Java》的书籍,该书是由Joshua Bloch编写,被广泛认为是Java编程的权威指南。...

    读书笔记:Effective Java中文版第3版笔记.zip

    读书笔记:Effective Java中文版第3版笔记

    Effective-Java读书笔记(上)

    ### Effective Java读书笔记(上) #### 第一章 引言 本书主要针对Java开发者提供了大量实用的编程指导建议,帮助读者提升代码质量和程序性能。在本章节中,我们将重点介绍对象的创建与销毁,以及一些重要的设计...

    effective python学习笔记.pdf

    记录了我的effective-Python学习笔记,精简了effective-Python中重要的部分。effective-Python是一本值得多看几遍的书,但是看后面的几遍的时候完全可以直接看自己的学习笔记。此学习笔记侧重与比较实用的部分即前四...

    effective C++读书笔记

    本文总结了Effective C++读书笔记,涵盖了C++的四个主要次语言:C、Object-Oriented C++、Template C++和STL。同时,文章还讨论了高效编程守则,包括使用const、enum和inline替换#define,使用const来告诉编译器和...

    effectiveJava课件分享

    在编程领域,特别是Java开发中,"Effective Java"是一本非常经典的书籍,由Joshua Bloch撰写,书中提出了一系列最佳实践和设计原则,以帮助开发者编写出更高效、更安全的代码。根据提供的标题和描述,我们将探讨三个...

    读书笔记:Effective Java中文版第二版示例、笔记.zip

    读书笔记:Effective Java中文版第二版示例、笔记

    读书笔记:Effective Java中文版学习项目.zip

    读书笔记:Effective Java中文版学习项目

    Effective java 3 学习记录.docx

    本学习记录主要介绍了 Effective Java 3 中的静态工厂方法和 Builder 模式两部分内容。 一、静态工厂方法 静态工厂方法是指返回类实例的命名规则,例如:from、of、valueOf、instance 或 getinstance、create 或 ...

Global site tag (gtag.js) - Google Analytics