论坛首页 Java企业应用论坛

JDK源代码学习系列一---java.util(1)

浏览 21268 次
精华帖 (0) :: 良好帖 (2) :: 新手帖 (3) :: 隐藏帖 (9)
作者 正文
   发表时间:2011-02-14  
谢谢大家捧场,个人学习的一个记录,欢迎参加讨论,有空多去看看源代码吧。
0 请登录后投票
   发表时间:2011-02-14  
看贴留名,这是规则
0 请登录后投票
   发表时间:2011-02-14  
emsn 写道
谢谢大家捧场,个人学习的一个记录,欢迎参加讨论,有空多去看看源代码吧。

我今天在公司看到一个同事在看Java.util。然后发现这个贴,公司在杭州
0 请登录后投票
   发表时间:2011-02-14  
虽然youjianbo_han_87兄贴出了源码,但还请emsn兄把这个系列的第一课做一个总结吧。
0 请登录后投票
   发表时间:2011-02-14  
yangleilt 写道
源码在哪里看呀!!我有jdk文档,但是看到的都是相当于uml图的那种解释!没有源码呀??

莫非你没有用IDE(Eclipse或者NetBeans)? 用IDE了,直接Ctrl+鼠标按住这个类,就会自动跳到JDK API源码了
0 请登录后投票
   发表时间:2011-02-14   最后修改:2011-02-14
IDE用的是JRE就没源码, JDK就有源码.

Java Collection包是大奖作品, 经典代码啊~~
0 请登录后投票
   发表时间:2011-02-14   最后修改:2011-02-14
emsn 写道
youjianbo_han_87 写道
1. 先鄙视下论坛规则,我好久没上了,竟然要回答什么尿尿问题。
2. 贴出 JDK1.5_update_22中 HashMap的 get方法的源码:
/**
     * Returns the value to which the specified key is mapped in this identity
     * hash map, or <tt>null</tt> if the map contains no mapping for this key.
     * A return value of <tt>null</tt> does not <i>necessarily</i> indicate
     * that the map contains no mapping for the key; it is also possible that
     * the map explicitly maps the key to <tt>null</tt>. The
     * <tt>containsKey</tt> method may be used to distinguish these two cases.
     *
     * @param   key the key whose associated value is to be returned.
     * @return  the value to which this map maps the specified key, or
     *          <tt>null</tt> if the map contains no mapping for this key.
     * @see #put(Object, Object)
     */
    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;
    }

通过代码可以得知,如果一个Key对应2个Value,看到注释的部分吗? 他按顺序找到后,直接就 Return a.value了。而不会循环Person2.

源代码一贴,神秘感就没了,哈哈



源码就是硬道理,不过[youjianbo_han_87]可能理解错了楼主的意图了,楼主是在put后,修改了key的关键域字段,人为改变了对象的hashCode和equals的行为(相对put时的状态),给我们设计提了个醒哦:key尽量使用状态不可变的类(偶一般都是Long、Integer、String做key)

 


这是个坏的例子:既然person使用id做为关键域,逻辑上关键域就不应当再修改了,否则程序或代码行为不可预知。


呵呵,以前项目中用map做缓存,都是用的String做key,就是看中了String不可变,建议缓存key对象中这样的关键域做成了final,呵呵调用的想是坏都不行(也遇到过和楼主一样的情形,调用者重用了返回的key,搞三搞四,调试了蛮久才发现,后来基本上返回的尽量new或者clone一个对象)

map只认put时的key状态,put后对key修改了关键域->改变了hashCode和equals行为,当然再次get时会按照新的hashCode和equals来定位Entry了

 

0 请登录后投票
   发表时间:2011-02-14   最后修改:2011-02-14

呵呵再补充下,摘录自我的wiki:

 

 

对于所有对象都通用的方法

 

  • Object中的方法:equals,hashCode,toString,clone 和 finalize都有明确的通用约定,所以我们需要遵守,因为不少的类都是按照通用约定来工作的

 

覆盖equals时请遵守通用约定

 

  • 覆盖equals方法有讲究的
  • 一下任意条件满足,都不应该覆盖equals
    1. 类的每个实例本质上都是唯一的,如:Thread
    2. 不关心类是否提供了“逻辑相等”的测试功能:如java.util.Random.equals
    3. 超类已经覆盖了equals,并且子类认为是合适的,如:Map的equals实现继承了?AbstractMap.equals,List的equals实现继承了?AbstractList的equals

  • 类是私有的或是包级私有的,可以确定永远不会被调用equals方法: 需要覆盖equals:throw new ?AssertionError("不要调用我");

  • 如果类有自有的“逻辑相等”就需要覆盖equals方法了
  • equals通用约定:
    1. 自反性:对于任何非空x,x.equals(x)==true
    2. 对称性:对于任何非空x、y,x.equals(y)==y.equals(x);
    3. 传递性:对于任何非空x、y、z,如果x.equals(y)==true、y.equals(z)==true那么x.equals(z)==true;
      1. java.sql.Timestamp和java.util.Date的问题:"鉴于 Timestamp 类和上述 java.util.Date 类之间的不同,建议代码一般不要将 Timestamp 值视为 java.util.Date 的实例。Timestamp 和 java.util.Date 之间的继承关系实际上指的是实现继承,而不是类型继承。"
    4. 一致性:只要x、y没有修改过,那么每次执行x.equals(y)都获得相同的结果
      1. java.net.URL.equals的调用依赖于网络,每次调用它们的内在可能变化
    5. 对于非空x: x.equals(null)==false
  • 高质量的equals方法
    1. 使用==检查参数是否为这个对象的引用:if (o == this) return true;
    2. instance of进行类型检查
    3. 把参数转化为正确的类型
    4. 每个关键域检查它们是否匹配
      1. 非float和double的基本类型直接==比较
      2. float使用Float.compare比较
      3. double使用Double.compare比较
      4. 对于对象引用直接调用equals比较
      5. 如果是数组域,如果每个元素都是关键域,那么每一个都要比较,jdk1.5后可以通过Arrays.equals解决
    5. 注意比较顺序带来的性能影响:最易变的先比较
    6. 写完equals问问:是否对称、传递、一致
      1. 覆盖equals时总要覆盖hashCode
      2. 不要企图让equals过于智能
      3. 不要讲equals中的参数替换为其他类型,这不是覆盖而是重载,所以需要@Override注解

 

覆盖equals时总要覆盖hashCode

 

  • hashCode 的常规协定是:
    1. 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
    2. 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
    3. 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
  • hashCode生成简单办法:计算所有的关键域,排除可由其他关键域计算得到的域
    1. 把非0的常数值保存到result中,如17
    2. 对于每一个关键域f(equals中涉及的每个域),完成以下步骤:
      1. 为该域计算int型散列码c:
        1. boolean return f?1:0
        2. byte、char、sort或int return f;
        3. long return (int)(f^(f>>>32))

        4. float return Float.floatToIntBits(f)
        5. double return Double.doubleToLongBits(f)再作为long型来计算散列值
        6. 引用对象return null?0:f.hasCode();
        7. 如果是数组,递归处理每一个元素
      2. 合并result = 31 * result + c
0 请登录后投票
   发表时间:2011-02-15  
skzr.org 写道
emsn 写道
youjianbo_han_87 写道
1. 先鄙视下论坛规则,我好久没上了,竟然要回答什么尿尿问题。
2. 贴出 JDK1.5_update_22中 HashMap的 get方法的源码:
/**
     * Returns the value to which the specified key is mapped in this identity
     * hash map, or <tt>null</tt> if the map contains no mapping for this key.
     * A return value of <tt>null</tt> does not <i>necessarily</i> indicate
     * that the map contains no mapping for the key; it is also possible that
     * the map explicitly maps the key to <tt>null</tt>. The
     * <tt>containsKey</tt> method may be used to distinguish these two cases.
     *
     * @param   key the key whose associated value is to be returned.
     * @return  the value to which this map maps the specified key, or
     *          <tt>null</tt> if the map contains no mapping for this key.
     * @see #put(Object, Object)
     */
    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;
    }

通过代码可以得知,如果一个Key对应2个Value,看到注释的部分吗? 他按顺序找到后,直接就 Return a.value了。而不会循环Person2.

源代码一贴,神秘感就没了,哈哈



源码就是硬道理,不过[youjianbo_han_87]可能理解错了楼主的意图了,楼主是在put后,修改了key的关键域字段,人为改变了对象的hashCode和equals的行为(相对put时的状态),给我们设计提了个醒哦:key尽量使用状态不可变的类(偶一般都是Long、Integer、String做key)

 


这是个坏的例子:既然person使用id做为关键域,逻辑上关键域就不应当再修改了,否则程序或代码行为不可预知。


呵呵,以前项目中用map做缓存,都是用的String做key,就是看中了String不可变,建议缓存key对象中这样的关键域做成了final,呵呵调用的想是坏都不行(也遇到过和楼主一样的情形,调用者重用了返回的key,搞三搞四,调试了蛮久才发现,后来基本上返回的尽量new或者clone一个对象)

map只认put时的key状态,put后对key修改了关键域->改变了hashCode和equals行为,当然再次get时会按照新的hashCode和equals来定位Entry了

 

我不知道哥们你看懂了没,我根本就忽略了他Put的过程,我只是用源码说明,如果Map中一个Key对应了2个Value,如例子中的Person1和Person2为何只能取到前者,至于Put,你看看源码不就知道了。。。。。

0 请登录后投票
   发表时间:2011-02-15  
emsn 写道
yangleilt 写道
源码在哪里看呀!!我有jdk文档,但是看到的都是相当于uml图的那种解释!没有源码呀??

这位同学,下下来的jdk里有源代码的压缩包,名字叫src.zip,可以解压看也可以用eclipse attach进去看,推荐后者。注意,不是jdk文档,你的文档可能是jdk用javadoc 生成的文档。

以前还没发现呀!谢谢了!
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics