Common Java classes I

Posted by Fluf on Sun, 31 Oct 2021 03:58:59 +0100

Object

The Object class is the root class of the class hierarchy. Each class uses Object as its superclass. Each class directly or indirectly inherits from the Object class.

Common methods in Object include:

public int hashCode() //Returns the hash code value of the object.
// Note: the hash value is a value calculated according to the hash algorithm. This value is related to the address value, but not the actual address value.

public final Class getClass() //Returns the runtime class of this Object

public String toString() //Returns a string representation of the object.

protected Object clone() //Create and return a copy of this object. This method can be overridden

protected void finalize() 
// This method is called by the object's garbage collector when the garbage collector determines that there are no more references to the object. Used for garbage collection, but when is uncertain.

equals() method

1. Equivalence relation

I reflexivity

x.equals(x); // true

Ⅱ symmetry

x.equals(y) == y.equals(x); // true

III transitivity

if (x.equals(y) && y.equals(z))
    x.equals(z); // true;

Ⅳ consistency

The equals() method is called multiple times, and the result remains unchanged

x.equals(y) == x.equals(y); // true

V comparison with null

Calling x.equals(null) on any object x that is not null will result in false

x.equals(null); // false;

2. Equivalence and equality

For primitive types, = = determines whether two values are equal. Primitive types have no equals() method.
For reference types, = = determines whether two variables refer to the same object, while equals() determines whether the referenced object is equivalent.

Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false

3. Realization

Check whether it is a reference to the same object. If yes, return true directly;
Check whether it is the same type. If not, return false directly;
Transform the Object object;
Determine whether each key field is equal.

public class EqualExample {

    private int x;
    private int y;
    private int z;

    public EqualExample(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;  //Check whether it is a reference to the same object. If yes, return true directly;
        if (o == null || getClass() != o.getClass()){
            //Check whether it is the same type. If not, return false directly
            return false;
        }

        // Transform Object object
        EqualExample that = (EqualExample) o;

        // Determine whether each key field is equal.
        if (x != that.x) return false;
        if (y != that.y) return false;
        return z == that.z;
    }
}

hashCode() method

hashCode() returns a hash value, and equals() is used to determine whether two objects are equivalent. Two equivalent objects must have the same hash value, but two objects with the same hash value are not necessarily equivalent.

When overriding the equals() method, you should always override the hashCode() method to ensure that the hash values of the two equivalent objects are also equal.

In the following code, two equivalent objects are created and added to the HashSet. We want to treat the two objects as the same and add only one object to the collection. However, because EqualExample does not implement the hasCode() method, the hash values of the two objects are different, resulting in the addition of two equivalent objects to the collection.

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size());   // 2

The ideal hash function should be uniform, that is, unequal objects should be evenly distributed over all possible hash values. This requires the hash function to take into account the values of all fields. You can treat each field as a bit in r-ary, and then form an r-ary integer. R is generally 31 because it is an odd prime number. If it is an even number, when multiplication overflow occurs, the information will be lost, because multiplying with 2 is equivalent to moving one bit to the left.

A number multiplied by 31 can be converted into shift and subtraction: 31 * x = = (x < < 5) - x, and the compiler will automatically optimize it.

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + x;
    result = 31 * result + y;
    result = 31 * result + z;
    return result;
}

toString() method

Default return ToStringExample@4554617c This form, where the value after @ is the unsigned hexadecimal representation of the hash code.

public class ToStringExample {

    private int number;

    public ToStringExample(int number) {
        this.number = number;
    }
}
Copy to clipboardErrorCopied
ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
Copy to clipboardErrorCopied
ToStringExample@4554617c

clone() method

  1. Cloneable

clone() is the protected method of Object. It is not public. If a class does not explicitly override clone(), other classes cannot directly call the clone() method of the class instance.

public class CloneExample {
    private int a;
    private int b;
}
Copy to clipboardErrorCopied
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); 
// 'clone()' has protected access in 'java.lang.Object'

Overriding clone() yields the following implementation:

public class CloneExample {
    private int a;
    private int b;

    // CloneExample inherits Object by default
    @Override
    public CloneExample clone() throws CloneNotSupportedException {
        return (CloneExample)super.clone();
    }
}
Copy to clipboardErrorCopied
CloneExample e1 = new CloneExample();
try {
    CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
Copy to clipboardErrorCopied
java.lang.CloneNotSupportedException: CloneExample

CloneNotSupportedException is thrown above because CloneExample does not implement the clonable interface.

It should be noted that the clone() method is not a method of the clonable interface, but a protected method of Object.

The clonable interface only stipulates that if a class does not implement the clonable interface and calls the clone() method, it will throw a clonnotsupportedexception.

public class CloneExample implements Cloneable {
    private int a;
    private int b;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. Shallow copy

The reference type of the copy object and the original object refer to the same object.

public class ShallowCloneExample implements Cloneable {

    private int[] arr;

    public ShallowCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected ShallowCloneExample clone() throws CloneNotSupportedException {
        return (ShallowCloneExample) super.clone();
    }
}
Copy to clipboardErrorCopied
// The reference type of the copy object and the original object refer to the same object.
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e1.get(2)); // 222
System.out.println(e2.get(2)); // 222
  1. Deep copy

The reference types of the copy object and the original object refer to different objects.

public class DeepCloneExample implements Cloneable {

    private int[] arr;

    public DeepCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample result = (DeepCloneExample) super.clone();
        // create new object
        result.arr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result.arr[i] = arr[i];
        }
        return result;
    }
}
Copy to clipboardErrorCopied
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e1.get(2)); // 222
System.out.println(e2.get(2)); // 2
  1. Alternative to clone()

Using the clone() method to copy an object is complex and risky. It will throw exceptions and require type conversion. As mentioned in the Effective Java book, it is better not to use clone(). You can use a copy constructor or a copy factory to copy an object.

public class CloneConstructorExample {

    private int[] arr;

    public CloneConstructorExample() { //Constructor
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public CloneConstructorExample(CloneConstructorExample original) { // copy constructor 
        arr = new int[original.arr.length];
        for (int i = 0; i < original.arr.length; i++) {
            arr[i] = original.arr[i];
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }
}
Copy to clipboardErrorCopied
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e1.get(2)); // 222
System.out.println(e2.get(2)); // 2

Topics: Java Back-end