Hibernate 系列 08 - 对象识别机制


 

目录导读:

  Hibernate 系列 学习笔记 目录

 

本篇目录:

  为了区别不同的对象,有两种识别方法:

  1. 内存地址识别(“==”号识别)

  2. equals()和hashCode()识别

 

1. 以内存地址识别

  如果两个对象的内存地址相同,毫无疑问,它们是相同的。

  如果要比较的是对象携带的信息,使用内存地址识别就不可用,因为地址不同的对象,它们所代表的的信息可能是一样的。

 

  例如有两个字符串,代码如下:

 1 public class CNBlogsTest {
 2     public static void main(String[] args) {
 3         String str1 = new String("cnblogs");
 4         String str2 = new String("cnblogs");
 5         
 6         if (str1 == str2)           // 判断内存地址是否相同
 7             System.out.println("str1和str2的内存地址相同。");
 8         else if(str1.equals(str2))  // 判断它们的值是否相同
 9             System.out.println("str1和str2的值相同。");
10     }
11 }

 

  由于str1和str2是用两个new命令开辟出来的字符串空间,它们的内存地址是不一样的,而它们所携带的信息都是cnblogs,所以运行上例程序打印出来的结果是:

str1和str2的值相同。

 

 

2. 以对象携带的信息识别

  在Hibernate中Session的操作可能会有一些疑惑。

 

  Session不是线程安全的,不同的Session维护者自己的缓存空间(在Hibernate中用Map实现),这个这个缓存空间存放着纳入了持久层的持久对象。

  但是按道理讲,从数据库同一记录取得的字段所组装成的对象应该是同一个对象,然后由不同的Session从数据库同一条记录上分别取得对象,它们的内存地址是不一样的。

  尽管它们所携带的信息一致。

 

  如下面的程序所示:

1 Configuration cfg = new Configuration().configure();
2 SessionFactory sf = cfg.buildSessionFactory();
3 Session session = sf.getCurrentSession();
4 Transaction tx = session.beginTransaction();
5 Object obj1 = session.get(Student.class, 12);
6 Object obj2 = session.get(Student.class, 12);
7 tx.commit();
8 session.close();
9 System.out.println(obj1==obj2); // 结果为true

  上面这个代码片段最终输出结果为true,表示obj1和obj2引用的是同一对象,它们的内存地址相同。

 

  但如果是下面的代码的话:

 1 Configuration cfg = new Configuration().configure();
 2 SessionFactory sf = cfg.buildSessionFactory();
 3 
 4 // Session1
 5 Session session1 = sf.getCurrentSession();
 6 Transaction tx1 = session1.beginTransaction();
 7 Object obj1 = session1.get(Student.class, 12);
 8 tx1.commit();
 9 session1.close();
10 
11 // Session2
12 Session session2 = sf.getCurrentSession();
13 Transaction tx2 = session2.beginTransaction();
14 Object obj2 = session2.get(Student.class, 12);
15 tx2.commit();
16 session2.close();
17 
18 System.out.println(obj1==obj2);        // 结果为false
19 System.out.println(obj1.equals(obj2)); // 结果为false

  以上代码最终的运行结果为false、false。

 

  对于第一个false,是因为不同的Session用不同的Map缓存Session级别的持久对象。

  因此,虽然它们是从数据库的同一条记录中取数据,但两个Session把组装的对象放在了不同的内存地址中。

  如图所示:

  

 

  对于第二个false,有的朋友可能会有点儿懵逼,equals()方法不就是比较对象信息的嘛?既然是同一条记录组装的对象,为什么还是false嘞?

  这是因为Student类默认的equals()方法继承自java.lang.Object类,Object类的equals()方法的源码如下:

1 package java.lang;
2 public class Object {
3     public boolean equals(Object obj) {
4         return (this == obj);
5     }
6 }

 

  可以看到,Object类的equals()方法使用的仍是内存地址判断,由于obj1和obj2的内存地址不一样,所以使用继承自Object的equals()方法得到的依然还是false。

 

  平时所熟悉的String类的equals()方法是重写了Object的equals()方法。

  当调用String类的a.equals(b)方法时,是将a字符串和b字符串一个字符一个字符进行比较,因此String类的equals()方法可以比较不同内存地址的字符串是否相同。

  String类的equals()方法源码如下:

 1 package java.lang;
 2 
 3 import java.io.ObjectStreamField;
 4 import java.io.UnsupportedEncodingException;
 5 import java.nio.charset.Charset;
 6 import java.util.ArrayList;
 7 import java.util.Arrays;
 8 import java.util.Comparator;
 9 import java.util.Formatter;
10 import java.util.Locale;
11 import java.util.Objects;
12 import java.util.StringJoiner;
13 import java.util.regex.Matcher;
14 import java.util.regex.Pattern;
15 import java.util.regex.PatternSyntaxException;
16 
17 public final class String
18     public boolean equals(Object anObject) {
19         if (this == anObject) {
20             return true;
21         }
22         if (anObject instanceof String) {
23             String anotherString = (String)anObject;
24             int n = value.length;
25             if (n == anotherString.value.length) {
26                 char v1[] = value;
27                 char v2[] = anotherString.value;
28                 int i = 0;
29                 while (n-- != 0) {    // 只要两个String之中有一个字符不同,则认为二者不同
30                     if (v1[i] != v2[i])
31                         return false;
32                     i++;
33                 }
34                 return true;
35             }
36         }
37         return false;
38     }
39 }

 

  同理,要实现Student类的信息比较,可以自己实现equals()和hashCode()方法。

  一个方法时,通过getStudentNo()方法取得对象的studentNo值并加以比较。

  例如:若studentNo的类型是String,代码如下:

 1 public class Student {
 2 
 3     @Override
 4     public boolean equals(Object obj) {
 5         if (this == obj) return true;  // 如果内存地址相等,返回true
 6         if (this.studentNo == null || !(obj instanceof Student)) return false;
 7         Student stu = (Student) obj;
 8         return this.studentNo.equals(stu.studentNo);
 9     }
10 
11     @Override
12     public int hashCode() {
13         return this.studentNo.hashCode();
14     }
15 
16 }

 

  通过studentNo来比较对象是否相等存在一些问题。

  因为当一个对象被new出来而还没有save()的时候,它并不会被赋予studentNo值,这是这个方法就不太适合了。

  通常使用的方法是根据对象中真正包含的属性值来做比较,例如:

 1 public class Student {
 2 
 3     @Override
 4     public boolean equals(Object obj) {
 5         if (this == obj) return true;  // 如果内存地址相等,返回true
 6         if(!(obj instanceof Student)) return false;
 7         Student stu = (Student)obj;
 8         if(!getStudentName().equals(stu.getStudentName())) return false;
 9         if(!getBornDate().equals(stu.getBornDate())) return false;
10         return true;
11     }
12 
13     // 通过一个简单的算法得到哈希码
14     @Override
15     public int hashCode() {
16         int result;
17         result = getStudentName().hashCode();
18         result = 29*result+getBornDate().hashCode();
19         return result;
20     }
21 
22 }

 

  上述例子不再简单地比较studentNo值了,而是根据学生姓名和生日对Student对象实例进行比较。因为基本上名字和生日就能确定一个人的身份了。

  当然,实现的方法还有很多,也可以使用其他的属性来比较Student的身份,这就要根据实际的需求来决定了。