最近,在hibernate中使用JPA注解方式配置bean
遇到联合主键时采用
@EmbeddedId
private BeanPK id;再顶一个 BeanPK 对象
@Embeddable
public class BeanPK implements Serializable{}看文档中,要求@Embeddable的BeanPK需要重写hashCode、equals方法;
equals好说;
但hashCode问题就来了
就我所知 java中的hashCode是根据对象的内存地址计算得到的;
String特殊一些,根据字符串内容计算得到的;而String常出现在常量池中。
这样java可以保证每个对象的hashCode是一个固定值。而我重写BeanPK中的hashCode方法,只能根据BeanPK中的属性的hashCode计算而出。
这样一旦BeanPK中的某个属性改变(例如:调用BeanPK.setXXXX()方法)后 hashCode的值就会改变。这样就会带来几个问题:
1.当BeanPK放入到集合中
  由于对象存放到集合中的位置依赖对象的hashCode值。
  那么,当对象放入集合后,再改变hashCode的值,集合就找不到该对象了。
  Set中也就无法过滤重复对象。
例子:
  BeanPK pk = new BeanPK("id","name");
  Bean b = new Bean(pk,"age");
  Map<BeanPK,Bean> map = new HashMap<BeanPK,Bean>();
  map.put(pk,b);
  pk.setName("newName");//BeanPK 的hashCode会根据id和name计算,setName后hashCode改变
  map.get(pk);//由于hashCode改变,Map已经找不到匹配的key了,这里返回null
2.由于@Embeddable要求实现Serializable
  那么当反序列后,hashCode不一致会不会带来同样的问题?希望高手指定一下,
另外:在使用@Embeddable时,需不需要重写hashCode方法?

解决方案 »

  1.   

    我刚刚大二 我说说我对于hashcode的比较浅显的理解。我前段时间也有同样的疑问。后来我查了查。
    set是要求无序不重复的 而要保证添加进去的每个对象都是不重复的那么只能重写该对象的equals和hashcode方法。在添加的时候就已经改变了hashcode。也许文不对题 但我就是这样去记住使用的 期待大神的解答。
      

  2.   

    大二就这么厉害了。。hashCode不一定是根据内存得到的。string对象的hashCode,就是每个字符乘以31的次方作为分别的权重计算得到的。你可以看下源码。实际上hash函数的设计本来就很复杂。像你这种情况要避免变动的成员用来计算hashCode。不过内容变了对象本来就不是同一个了吧。。比如你这个对象,性别变成女了。。还是你自己么。。
      

  3.   

    Map的key要求是一个不可变的值对象。可变的不应该作为key。
      

  4.   

    那么 在JPA 文档中对于联合主键@EmbeddedId 大多都说要 重写 hashcode。
    那么作为 实体识别 的 @EmbeddedId 能否做key呢!?
      

  5.   



    就我所知 java中的hashCode是根据对象的内存地址计算得到的;
    String特殊一些,根据字符串内容计算得到的;而String常出现在常量池中。
    ”常规设计的时候,当页面需要批量添加Bean实体时
    Action 通常 会传递一个 List<Bean> 或者 Map<BeanPK,Bean> 进入Service层
    但在Action层中 BeanPK中的 id 还没生成,可能需要到Service层或者Dao层时才分配id那么在设计的时候 是否应该在 Action 和 Service 以及Dao 层中传递不同的集合?
    (也就是说 从Action 开始传递的那个集合 到Service 或者 Dao 层 可能由于BeanPK的hashcode改变 而造成 get() 是一个null)
    在设计中如何避免此类问题,
    我的印象中 除了 String 以及 基本类型的包装类 以外 基本上都是使用 Object的hashcode方法
    而 Object的hashcode 是使用 JNI 方式 直接使用内存地址 计算得出的hashcode既然如此,是否可在设计时 @EmbeddedId 的对象 不重写hashcode方法,只重写equals方法???
      

  6.   


    String 是immutable的类,所以不存在你说的问题,另外 String 的 hashCode 实现比较特殊,可以叫“lazy hashcode”,不建议模仿。"既然如此,是否可在设计时 @EmbeddedId 的对象 不重写hashcode方法,只重写equals方法??? "
    那么你的equals方法又要依据什么来判断呢?
    只重写equals方法通常是不对的。因为可能出现两个对象 equals 为 true,而hashCode却不相等的情况,这与HashMap和HashSet是一定不兼容的。“Action 通常 会传递一个 List<Bean> 或者 Map<BeanPK,Bean> 进入Service层
    但在Action层中 BeanPK中的 id 还没生成,可能需要到Service层或者Dao层时才分配id”这句话听起来就是有问题的,"BeanPK中的 id 还没生成",如果BeanPK是依赖id来比较equals,那id应该满足以下条件:1 - 在构造方法中就已经安全的赋值
    2 - 在对象构造以后不能再改变简单的说,就是 final + immutable(或primitive)
    用于hashCode的成员也一样要满足以上条件。换句话说,如果BeanPK中的id不满足以上条件,那id不应该用作equals和hashCode的依据。
      

  7.   


    看来 BeanPK中的属性 需要使用 final 才能避免一些问题。
    但这里还有一个问题,
    由于 我使用hibernate,当hibernate从数据库中取出对象后,貌似实例对象时使用的是对象的无参构造器,然后调用 set方法
    那么 hibernate 会不会调用 BeanPK() 然后再 setId(String id) 呢!!???
    如果hibernate按照上述步骤实例,那么 id 设置成final就不行咯! 
      

  8.   


    上面说的情况比较严格,说的是安全的 hash key 应该满足的,——按这个标准所有的 JavaBean 大概都不满足条件(或者只能用默认的 equals 和 hashCode)……——事实上只要保证:当一个对象作为 key 被加进 HashMap 或者 HashSet 以后,使用 HashMap 和 HashSet 期间,这个对象的 equals 和 hashCode 不发生变化就行了。