1 equals方法

在 Object 类中,== 运算符和 equals 方法是等价的,都是比较两个对象的引用是否相等。
public boolean equals(Object obj) { return (this == obj); }
从另一方面来讲,如果两个对象的引用相等,那么这两个对象一定是相等的。对于我们自定义的一个对象,如果不重写 equals 方法,那么在比较对象的时候就是调用Object 类的 equals方法,也就是用==运算符比较两个对象。在 Java 规范中,对equals方法的使用必须遵循以下几个规则:

  • 自反性:对于任何非空引用值X,X.equals(X)都应返回true
  • 对称性:对于任何非空引用值X和Y,当且仅当Y.equals(X)返回true时,X.equals(Y)也应该返回true
  • 传递性:对于任何非空引用值X,Y,Z,如果X.equals(Y)返回true,并且Y.equals(Z)返回true,那么X.equals(Z)应返回true
  • 一致性:对于任何非空引用值X和Y,多次调用X.equals(Y)始终返回true或始终返回false

另外,为了避免空指针异常,最好让确定非空的对象调用equals对象。

2 ==与equals()的区别

== 作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址);
equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
  • 情况2:类覆盖了 equals()方法。覆盖equals()方法来判断两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法;对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

3 为什么重写 equals 方法就必须重写 hashcode 方法

当不重写 equals 方法时,判断两个对象是否相等,等同于利用 == 判断。当重写 equals 方法时,是根据内容判断两个对象是否相等。
HashSet 或 HashMap在存放元素之前,要根据hashcode方法计算得到的hash值来判断元素是否已经存在。不重写 hashcode 时,两个属性值完全相同的对象产生的hash值不同,但是当把这两个对象加入到 HashSet 或 HashMap 中则会产生异常。
就结果而言,两个内容意义上相等的不同对象加入 HashSet 中,容量应该为1,但是 HashSet 使用 hashcode 判断对象是否已存在或相等,产生了歧义,最差的结果会导致HashSet 的不正常运行。所以重写 equals 方法必须重写 hashcode 方法。

4 hashcode方法

hashcode() 是 Object 类中的函数,所有类都拥有该函数,用于返回该对象的hash值。hash值的通用约定如下:

  • 在 java 程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode 方法都会返回相同的整数值。对象的hash值没有必要在不同的程序中保持相同的值。
  • 如果 2 个对象使用 equals 方法进行比较并且相同的话,那么这 2 个对象的 hashCode 方法的值也必须相等
  • 根据 equals 方法,得到两个对象不相等,那么这 2 个对象的 hashCode 可以相同或不同。但是,不相等的对象的 hashCode 值不同的话可以提高哈希表的性能

在 JDK6 与 JDK7 中基于随机数作为 hashcode 的来源; JDK8 默认通过和当前线程油管的一个随机数+三个确定值,运用 Marsaglia’s xorshift scheme 随机数算法得到的一个随机数。

  1. static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  2. intptr_t value = 0 ;
  3. if (hashCode == 0) {
  4. // 根据Park-Miller伪随机数生成器生成的随机数
  5. value = os::random() ;
  6. } else
  7. if (hashCode == 1) {
  8. // 此类方案将对象的内存地址,做移位运算后与一个随机数进行异或得到结果
  9. intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
  10. value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  11. } else
  12. if (hashCode == 2) {
  13. value = 1 ; // 返回固定的1
  14. } else
  15. if (hashCode == 3) {
  16. value = ++GVars.hcSequence ; // 返回一个自增序列的当前值
  17. } else
  18. if (hashCode == 4) {
  19. value = cast_from_oop<intptr_t>(obj) ; // 对象地址
  20. } else {
  21. // 通过和当前线程有关的一个随机数+三个确定值
  22. unsigned t = Self->_hashStateX ;
  23. t ^= (t << 11) ;
  24. Self->_hashStateX = Self->_hashStateY ;
  25. Self->_hashStateY = Self->_hashStateZ ;
  26. Self->_hashStateZ = Self->_hashStateW ;
  27. unsigned v = Self->_hashStateW ;
  28. v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
  29. Self->_hashStateW = v ;
  30. value = v ;
  31. }
  32. value &= markOopDesc::hash_mask;
  33. if (value == 0) value = 0xBAD ;
  34. assert (value != markOopDesc::no_hash, "invariant") ;
  35. TEVENT (hashCode: GENERATE) ;
  36. return value;
  37. }

hashCode() 返回hash值,而 equals() 是用来判断两个对象是否等价。等价的两个对象hash值一定相同,但是hash值相同的两个对象不一定等价。
对于一些容器,尤其是 HashMap 来说,为了保持 Key 唯一性且降低比较次数,自然而然地引入了 hash 值。以 HashMap 的 put 为例:如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的 equals 方法与新元素进行比较,相同的话就不存了;不相同的话,也就是发生了 hash冲突的情况,那么就在这个发生hash冲突的地方产生一个链表,将所有产生相同 hash 值的对象放到这个单链表上去,串在一起(很少出现)。

5 clone方法

clone 方法是创建并且返回一个对象的复制之后的结果。复制的含义取决于对象的类定义。
clone 方法首先会判对象是否实现了 Cloneable 接口,若无则抛出 CloneNotSupportedException 异常,最后会调用internalClone 。
intervalClone 是一个 native 方法,一般来说 native 方法的执行效率高于非 native 方法。

6 浅拷贝和深拷贝的区别

在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。
而一般使用 = 号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

浅拷贝:对基本数据类型进行了拷贝,对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象。
深拷贝:对基本数据类型进行了拷贝,对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

浅拷贝和深拷贝,只是在拷贝对象的时候,对 类的实例对象 这种引用数据类型的不同操作而已。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型,数组或引用),拷贝的就是内存地址。因此如果其中一个对象改变了这个地址,就会影响到另一个对象。Object 的 clone() 方法,提供的是一种浅拷贝的机制。

浅拷贝的实现方式:

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。如果想要实现对对象的深拷贝,在不引入第三方jar包的情况下,可以使用如下办法:

  • 先对对象进行序列化,紧接着马上反序列化出。
  • 重写 clone 方法。与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象里面的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法。最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。
  • 拷贝构造函数。拷贝构造方法指的是该类的构造方法的参数为该类的对象。 ```java package org.example;

import java.io.*;

// 深拷贝之 序列化 public class CopyTest1 { public static void main(String[] args) throws IOException, ClassNotFoundException { // 创建一个对象 School school = new School(“china”); // 对象作为参数 Student student = new Student(“zhangsan”, 1, school); // 序列化对象 SerializeStudent(student); // 反序列化对象 Student deserializeStudent = DeserializeStudent(); // 修改序列化后的对象 deserializeStudent.setAge(999); // 输出结果 System.out.println(student.getAge()); System.out.println(deserializeStudent.getAge()); }

  1. // 序列化
  2. private static void SerializeStudent(Student student) throws IOException {
  3. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream
  4. (new File("G:/javaCode/demo_code/demo/src/org/example/student.txt")));
  5. oos.writeObject(student);
  6. System.out.println("student 序列化成功");
  7. oos.close();
  8. }
  9. // 反序列化
  10. private static Student DeserializeStudent() throws IOException, ClassNotFoundException {
  11. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File
  12. ("G:/javaCode/demo_code/demo/src/org/example/student.txt")));
  13. Student student = (Student) ois.readObject();
  14. System.out.println("student 反序列化成功");
  15. ois.close();
  16. return student;
  17. }
  18. private static class Student implements Serializable {
  19. private String name;
  20. private int age;
  21. private School school;
  22. // 省略了 get set constructor
  23. }
  24. private static class School implements Serializable {
  25. private String address;
  26. // 省略了 get set constructor
  27. }

}

  1. ```java
  2. package org.example;
  3. // 深拷贝之 重写clone方法
  4. public class CopyTest2 {
  5. public static void main(String[] args) throws CloneNotSupportedException {
  6. // 创建一个对象
  7. School school = new School("china");
  8. // 对象作为参数
  9. Student student = new Student("zhangsan", 1, school);
  10. // 调用clone方法进行深拷贝
  11. Student copyStudent = student.clone();
  12. // 修改源对象属性值
  13. student.setAge(999);
  14. // 展示结果
  15. System.out.println(student.getAge());
  16. System.out.println(copyStudent.getAge());
  17. }
  18. // 实现Cloneable接口
  19. private static class Student implements Cloneable {
  20. private String name;
  21. private int age;
  22. private School school;
  23. // 省略了 get set constructor
  24. // 重写clone方法
  25. // super.clone()其实是浅拷贝,所以对引用数据类型,需要再次调用其clone方法
  26. @Override
  27. protected Student clone() throws CloneNotSupportedException {
  28. // super.clone()其实是浅拷贝
  29. Student student = (Student) super.clone();
  30. // 其中的school是引用类型,需要再次调用school的clone方法。并且school的clone也必须被重写
  31. student.setSchool(this.school.clone());
  32. return student;
  33. }
  34. }
  35. // 实现Cloneable接口
  36. private static class School implements Cloneable {
  37. private String address;
  38. // 省略了 get set constructor
  39. // 重写clone方法
  40. @Override
  41. protected School clone() throws CloneNotSupportedException {
  42. return (School) super.clone();
  43. }
  44. }
  45. }
  1. package org.example;
  2. // 深拷贝之 拷贝构造函数
  3. public class CopyTest3 {
  4. public static void main(String[] args) {
  5. // 创建一个对象
  6. School school = new School("china");
  7. // 对象作为参数
  8. Student student = new Student("zhangsan", 1, school);
  9. // 调用构造函数进行深拷贝
  10. Student copyStudent = new Student(student.getName(), student.getAge(), new School(school.getAddress()));
  11. // 修改源对象的值
  12. student.setAge(999);
  13. // 输出结果
  14. System.out.println(student.getAge());
  15. System.out.println(copyStudent.getAge());
  16. // 浅拷贝
  17. Student student1 = student;
  18. student1.setName("lisi");
  19. System.out.println(student.getName());
  20. System.out.println(student1.getName());
  21. }
  22. private static class Student {
  23. private String name;
  24. private int age;
  25. private School school;
  26. // 省略了 get set constructor
  27. }
  28. private static class School {
  29. private String address;
  30. // 省略了 get set constructor
  31. }
  32. }

总结来说:
1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
006tKfTcly1fij5l5nx2mj30e304o3yn.jpg
2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
006tKfTcly1fij5l1dm3uj30fs05i74h.jpg
浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。
参考:深拷贝和浅拷贝的区别

7 数组的复制方法

  • for循环。
  • System.arraycopy() 和 Arrays.copyOf()。
    • Arrays.copyOf 本质上是调用 System.arraycopy 。之所以时间差距比较大,是因为很大一部分开销全花在了 Math.min 函数上了。所以,相比之下,System.arraycopy 效率要高一些。
    • 对于基本数据类型来说 System.arraycopy() 方法是深拷贝;对于引用数据类型来说 System.arraycopy() 方法是浅拷贝。
    • System.arraycopy 线程不安全。
    • 从参数上可以看出,System.arraycopy需要传入源数组、源数组的起始位置、目标数组,目标数组的起始位置、复制长度;而copyOf方法只需要传入源数组和复制长度,其内部会自行创建并返回一个数组。
  • clone() 。
    • 如果数组存储的类型是对象,它是浅拷贝,复制的是引用(地址值)。
    • 如果存储的是基本数据,一维数组是深拷贝,二维及以上数组是浅拷贝。
      1. public static byte[] copyOf(byte[] original, int newLength){
      2. byte[] copy = new byte[newLength];
      3. System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
      4. return copy;
      5. }
      6. public static void arraycopy(
      7. Object src, //源数组
      8. int srcPos, //源数组的起始位置
      9. Object dest, //目标数组
      10. int destPos, //目标数组的起始位置
      11. int length //复制长度
      12. )
  1. package org.example;
  2. // for循环复制数组
  3. public class ArrayCopyTest1 {
  4. public static void main(String[] args) {
  5. int a[] = {1, 2, 3};
  6. int b[] = new int[a.length];
  7. for (int i = 0; i < a.length; i++) {
  8. b[i] = a[i];
  9. }
  10. for (int i = 0; i < b.length; i++) {
  11. System.out.println(a[i]);
  12. System.out.println(b[i]);
  13. System.out.println("-----------");
  14. }
  15. }
  16. }
  1. package org.example;
  2. import java.util.Arrays;
  3. // System.arraycopy、Arrays.copyOf 复制数组
  4. public class ArrayCopyTest2 {
  5. public static void main(String[] args) {
  6. int a[] = {1, 2, 3};
  7. int b[] = new int[a.length];
  8. // System.arraycopy 复制基本类型数组
  9. Long begin1 = System.currentTimeMillis();
  10. System.arraycopy(a, 0, b, 0, a.length);
  11. Long end1 = System.currentTimeMillis();
  12. // Arrays.copyOf 复制基本类型数组
  13. Long begin2 = System.currentTimeMillis();
  14. int[] c = Arrays.copyOf(a, a.length);
  15. Long end2 = System.currentTimeMillis();
  16. // 这里数据较少,看不出时间差距
  17. System.out.println(begin1 - end1);
  18. System.out.println(begin2 - end2);
  19. System.out.println("==============");
  20. for (int i = 0; i < b.length; i++) {
  21. System.out.println(a[i]);
  22. System.out.println(b[i]);
  23. System.out.println(c[i]);
  24. System.out.println("-----------");
  25. }
  26. // 基本数据类型,System.arraycopy是深拷贝
  27. b[1] = 999;
  28. System.out.println(a[1]);
  29. System.out.println(b[1]);
  30. System.out.println(c[1]);
  31. System.out.println("===========");
  32. Student s1 = new Student("zhangsan");
  33. Student s2 = new Student("lisi");
  34. Student s3 = new Student("wangwu");
  35. Student s[] = {s1, s2, s3};
  36. Student copyS[] = new Student[s.length];
  37. // System.arraycopy 复制引用类型数组
  38. System.arraycopy(s, 0, copyS, 0, s.length);
  39. // Arrays.copyOf 复制引用类型数组
  40. Student[] copyS2 = Arrays.copyOf(s, s.length);
  41. // 没有重写toString方法,默认打印的是地址值,发现三个都是相同的地址,说明是浅拷贝
  42. for (int i = 0; i < s.length; i++) {
  43. // 引用类型,System.arraycopy是浅拷贝
  44. System.out.println(s[i]);
  45. System.out.println(copyS[i]);
  46. System.out.println(copyS2[i]);
  47. System.out.println("-----------");
  48. }
  49. }
  50. private static class Student {
  51. private String name;
  52. // 省略了 get set constructor
  53. }
  54. }
  1. package org.example;
  2. // clone 复制数组
  3. public class ArrayCopyTest3 {
  4. public static void main(String[] args) {
  5. int a[] = {1, 2, 3};
  6. int[] copyA = a.clone();
  7. // clone 复制基本类型数组,深拷贝
  8. for (int i = 0; i < a.length; i++) {
  9. System.out.println(a[i]);
  10. System.out.println(copyA[i]);
  11. System.out.println("-----------");
  12. }
  13. a[1] = 999;
  14. System.out.println(a[1]);
  15. System.out.println(copyA[1]);
  16. System.out.println("=========");
  17. Student s1 = new Student("zhangsan");
  18. Student s2 = new Student("lisi");
  19. Student s[] = {s1, s2};
  20. Student[] copyS = s.clone();
  21. // clone 复制引用类型数组是浅拷贝,复制的是引用
  22. for (int i = 0; i< s.length; i++) {
  23. // 打印的地址值相同,说明复制的是引用
  24. System.out.println(s[i]);
  25. System.out.println(copyS[i]);
  26. System.out.println("------------");
  27. }
  28. // 修改源数组内对象的属性,复制数组也发生了变化
  29. s[1].setName("wangwu");
  30. System.out.println(s[1].getName());
  31. System.out.println(copyS[1].getName());
  32. }
  33. private static class Student {
  34. private String name;
  35. // 省略了 get set constructor
  36. }
  37. }

参考