Guava experience: common methods of copying objects

In Java, the Object class is the parent class of all classes. There are several methods that need override, such as equals,hashCode and toString. Every time I write these methods, I have to make a lot of repetitive judgments. Many class libraries provide tool classes to override these methods, and Guava also provides a similar way. Let's take a look at the simple use of these methods in Guava.

equals method:

Equals is a method that often needs to be overridden. You can view the equals method annotation of an Object. There are several requirements for equals:

  1. reflexive: any non empty reference x, x.equals(x) returns true;
  2. Symmetry: any non empty references X and y, x.equals(y) returns true if and only if y.equals(x) returns true;
  3. transitive: any non empty references X and Y. if x.equals(y) returns true and y.equals(z) returns true, x.equals(z) returns true;
  4. Consistency consistency: multiple calls of two non empty references X and y, x.equals(y) should be consistent (provided that the relevant information of X and y used for comparison is not modified between multiple comparisons);
  5. x.equals(null) returns false for all non null values X. (if you want to use null.equals(x) Or, NullPointerException will be reported).

When some values in the class we want to override may be null, we need to do a lot of judgment and branching on null. Using Guava's Objects.equal Method can avoid this problem and make the overwriting of equals method more easy, readable, concise and elegant.

import org.junit.Test;
import com.google.common.base.Objects;

public class ObjectTest {
    
    @Test
    public void equalTest() {
        System.out.println(Objects.equal("a", "a"));
        System.out.println(Objects.equal(null, "a")); 
        System.out.println(Objects.equal("a", null)); 
        System.out.println(Objects.equal(null, null));
    }
    
    @Test
    public void equalPersonTest() {
        System.out.println(Objects.equal(new Person("peida",23), new Person("peida",23)));
        Person person=new Person("peida",23);
        System.out.println(Objects.equal(person,person));
    }
}

class Person {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Operation output:

true
false
false
true
false
true

hashCode method:

After overriding the equals() method, you must also override the hashCode() method, and vice versa. This method returns an integer value (hash code value). If two objects are judged equal by the equals() method, they should have the same hash code. The hashCode() method of the Object class returns different values for different objects. The hashCode value of the Object class represents the address of the Object.

The general contract of hashCode (conditions to be met) is as follows:

  1. In one execution of Java application, if the information of the object used for equals comparison is not modified, the hashCode() method called by the same object multiple times should return the same integer value. In multiple applications, this value does not need to be consistent, that is, each execution keeps its own different value.
  2. If equals() determines that two objects are equal, their hashCode() method should return the same value.
  3. There is no mandatory requirement that if equals() judges that two objects are not equal, their hashCode() method should return different values. That is, two objects return false by comparing with the equals() method, and their hashcodes can be the same or different. However, it should be realized that generating two different hashcodes for two unequal objects can improve the performance of hash tables.

It's not hard to write a hashcode, but Guava gives us a simpler way-- Objects.hashCode(Object...), this is a variable parameter method. The parameter list can be any number, so it can be used like this Objects.hashCode(field1, field2, ..., fieldn). Very convenient and simple.

import org.junit.Test;
import com.google.common.base.Objects;

public class ObjectTest {    
    @Test
    public void hashcodeTest() {
        System.out.println(Objects.hashCode("a"));
        System.out.println(Objects.hashCode("a"));
        System.out.println(Objects.hashCode("a","b"));
        System.out.println(Objects.hashCode("b","a"));
        System.out.println(Objects.hashCode("a","b","c"));
        
        Person person=new Person("peida",23);
        System.out.println(Objects.hashCode(person));
        System.out.println(Objects.hashCode(person));
    }
}

class Person {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
128
4066
4096
126145
19313256
19313256

toString() method:

Because each class inherits directly or indirectly from Object, each class has a toString() method. This method is used the most and overwritten the most. A good toString method is very important for debugging, but it's really unpleasant to write. Guava also provides the toString() method.

import org.junit.Test;
import com.google.common.base.Objects;

public class ObjectTest {
    
    @Test
    public void toStringTest() {
        System.out.println(Objects.toStringHelper(this).add("x", 1).toString());
        System.out.println(Objects.toStringHelper(Person.class).add("x", 1).toString());
        
        Person person=new Person("peida",23);
        String result = Objects.toStringHelper(Person.class)
        .add("name", person.name)
        .add("age", person.age).toString();      
        System.out.print(result);
    }
}

class Person {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

//============Output===============
ObjectTest{x=1}
Person{x=1}
Person{name=peida, age=23}

compare/compareTo method:

  • **CompareTo: * * compareTo(Object o) method is java.lang.Comparable <T> Methods in the interface. When the objects of a class need to be sorted and the class needs to implement the compatible < T > interface, the public int compareTo(T o) method must be overridden. Java stipulates that if a and B are two objects, when a.compareto (b) > 0, a is greater than B, when a.compareto (b) < 0, a < B, that is, the rule of specifying the comparative size of objects;
  • Compare: compare(Object o1,Object o2) method is java.util.Comparator <T> The method of the interface, in the compare method, the size of the object is mainly determined by the rules of the object size relationship defined by the compareTo.

The general convention of compareTo method is similar to equals: compare the object with the specified object. If the object is less than, equal to, or greater than the specified object, the separation returns a positive number, zero, or positive number. If the specified object type cannot be compared with this object stop, ClassCastException will be run out.

  • **Symmetry: * * implementer must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and y. This also implies that x.compareTo(y) throws an exception when and only when y.compareTo(x) throws an exception.
  • **Transitivity: * the implementer must ensure that the analogy relationship is transitive. If x.compareto (y) > 0 and y.compareto (z) > 0, x.compareto (z) > 0. The implementer must ensure that x.compareTo(y)==0 implies that all Z has (x.compareTo(z)) == (y.compareTo(z)).
  • **Although not mandatory, it is strongly recommended (x.compareTo(y) == 0) == (x.equals(y)). **Generally speaking, if any class that implements compatible violates this Convention, it should be clear. It is recommended to say: "note: this class has a natural order, but it is inconsistent with equals".

The first article points out that if the order of the two objects is reversed, the following will happen: if the first object is smaller than the second object, the second object must be larger than the first object; if the first object is equal to the second object, the second object must also be equal to the first object; if the first object is larger than the second object, the second object is smaller In the first object.

The second indicates that if the first object is larger than the second and the second is larger than the third, the first is larger than the third.

Article 3 states that for two objects of proportionality, they should have the same result as any other object.

One result of these three conventions is that the equivalence test of compareTo method must satisfy the same constraints as that of equals method: reflexivity, symmetry and transitivity. So there are similar constraints: you cannot extend an instantiatable class and add a new value component, and at the same time guarantee the compareTo convention, unless you are willing to give up the advantages of object-oriented abstraction. You can use the same evasion as equals: if you want to add a value component to a class that implements the compatible interface, do not extend it; you should write an unrelated class, including an instance of the first class. Then a view method is provided to return the instance. In this way, you can implement any compareTo method on the second class, and allow customers to regard the second class as an instance of the first class when they need to.

The last paragraph of the compareTo convention is a strong recommendation rather than a real convention, that is, the equivalence test of the compareTo method must be the same as the result of the equals method. If this is followed, the order imposed by the compareTo method is said to be the same as equals; otherwise, it is said to be inconsistent with equals. Of course, the compareTo method, which is inconsistent with equals, can still work. However, if an ordered Collection includes elements of this class, the Collection may not follow the general convention of the response Collection interface (Collection, Set, Map). This is because the general conventions for these interfaces are based on the equals method, but ordered collections are executed using compareTo instead of equals.

Let's simply implement the compareTo method of a class:

import org.junit.Test;

public class ObjectTest {
    
    
    @Test
    public void compareTest(){
        Person person=new Person("peida",23);
        Person person1=new Person("aida",25);
        Person person2=new Person("aida",25);
        Person person3=new Person("aida",26);
        Person person4=new Person("peida",26);
        
        System.out.println(person.compareTo(person1));
        System.out.println(person1.compareTo(person2));
        System.out.println(person1.compareTo(person3));
        System.out.println(person.compareTo(person4));
        System.out.println(person4.compareTo(person));    
    }
}

class Person implements Comparable<Person>{
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public int compareTo(Person other) {
        int cmpName = name.compareTo(other.name);
        if (cmpName != 0) {
            return cmpName;
        }
        if(age>other.age){
            return 1;
        }
        else if(age<other.age){
            return -1;
        }
        return 0;  
    }
}
//========Output===========
15
0
-1
-1
1

In the compareTo method above, the code does not look very elegant. If there are many entity properties and rich data types, the code readability will be poor. In guava, a comparison tool function is provided for all primitive types to avoid this trouble. For example, for Integer, you can use the Ints.compare(). Using guava's original type of compare, we simplify the above method to implement the compare method:

class PersonComparator implements Comparator<Person> {  
    @Override 
    public int compare(Person p1, Person p2) {  
      int result = p1.name.compareTo(p2.name);  
      if (result != 0) {  
        return result;  
      }  
      return Ints.compare(p1.age, p2.age);  
    }  
  }

The above code looks a little simpler, but it is not so elegant and simple. For this, guava has a rather smart solution, which provides a comparison chain:

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public int score;    
    
    Student(String name, int age,int score) {
        this.name = name;
        this.age = age;
        this.score=score;
    }
    
    @Override
    public int compareTo(Student other) {
        return ComparisonChain.start()
        .compare(name, other.name)
        .compare(age, other.age)
        .compare(score, other.score, Ordering.natural().nullsLast())
        .result();
    }
}

class StudentComparator implements Comparator<Student> {  
    @Override public int compare(Student s1, Student s2) {  
      return ComparisonChain.start()  
          .compare(s1.name, s2.name)  
          .compare(s1.age, s2.age)  
          .compare(s1.score, s2.score)  
          .result();  
    }  
  }  
}

ComparisonChain is a lazy comparison process. When the comparison result is 0, that is, when it is equal, the comparison will continue. When there is a non-zero situation, the latter comparison will be ignored. compare and compareTo implemented by ComparisonChain have greatly improved code readability and performance.

Here is a comprehensive application example:

import java.util.Comparator;

import org.junit.Test;

import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;

public class ObjectTest {

    
    @Test
    public void StudentTest(){
        
        Student student=new Student("peida",23,80);
        Student student1=new Student("aida",23,36);
        Student student2=new Student("jerry",24,90);
        Student student3=new Student("peida",23,80);
        
        System.out.println("==========equals===========");
        System.out.println(student.equals(student2));
        System.out.println(student.equals(student1));
        System.out.println(student.equals(student3));
        
        System.out.println("==========hashCode===========");
        System.out.println(student.hashCode());
        System.out.println(student1.hashCode());
        System.out.println(student3.hashCode());
        System.out.println(student2.hashCode());
        
        System.out.println("==========toString===========");
        System.out.println(student.toString());
        System.out.println(student1.toString());
        System.out.println(student2.toString());
        System.out.println(student3.toString());
        
        System.out.println("==========compareTo===========");
        System.out.println(student.compareTo(student1));
        System.out.println(student.compareTo(student2));
        System.out.println(student2.compareTo(student1));
        System.out.println(student2.compareTo(student));
        
    }

}

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public int score;
    
    
    Student(String name, int age,int score) {
        this.name = name;
        this.age = age;
        this.score=score;
    }
    
    @Override
    public int hashCode() {
        return Objects.hashCode(name, age);
    }
    
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student) {
            Student that = (Student) obj;
            return Objects.equal(name, that.name)
                    && Objects.equal(age, that.age)
                    && Objects.equal(score, that.score);
        }
        return false;
    }
    
    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .addValue(name)
                .addValue(age)
                .addValue(score)
                .toString();
    }
    
    
    @Override
    public int compareTo(Student other) {
        return ComparisonChain.start()
        .compare(name, other.name)
        .compare(age, other.age)
        .compare(score, other.score, Ordering.natural().nullsLast())
        .result();
    }
}



class StudentComparator implements Comparator<Student> {  
    @Override public int compare(Student s1, Student s2) {  
      return ComparisonChain.start()  
          .compare(s1.name, s2.name)  
          .compare(s1.age, s2.age)  
          .compare(s1.score, s2.score)  
          .result();  
    }  
  }  

//=============Operation output===========================
==========equals===========
false
false
true
==========hashCode===========
-991998617
92809683
-991998617
-1163491205
==========toString===========
Student{peida, 23, 80}
Student{aida, 23, 36}
Student{jerry, 24, 90}
Student{peida, 23, 80}
==========compareTo===========
1
1
1
-1

 

Tags: Java Google Junit less

Posted on Sun, 07 Jun 2020 03:34:59 -0700 by culprit