Wednesday, January 19, 2022

Java Tutorial: Object and Objects Classes

Chapters

Overview

In this tutorial, we're gonna explore the Object and Objects classes. These two seem equivalent. However, their purpose is different.


Object Class

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class. I will demonstrate this class method except for wait(), notify() and notifyAll(). I explained this method in this article. I won't also demonstrate finalize() because it's deprecated since Java 9.

clone() Method

Method Form: protected Object clone()throws CloneNotSupportedException
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. More information can be read in the documentation. A class must implement Cloneable interface and override clone() method in order to make cloning work. There are two ways of copying objects: Shallow Copy and Deep Copy.


Shallow Copy

This copy mechanism copies primitive values and reference of objects without using the new keyword. This copy mechanism combined with clone() method improves performance compare to copy constructor.

This example demonstrates shallow copy mechanism.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = c1.clone();
    
    System.out.println("c1 | num1 | " + c1.getNum());
    System.out.println("c2 | num1 | " + c2.getNum());
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA implements Cloneable{
  private int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  public int getNum(){
    return num1;
  }
  
  //clone() method in Cloneable interface has protected
  //access modifier. It means that we can only access
  //that method in a class that implements Cloneable
  //
  //That's why we create this method to contain the 
  //clone() method. Take note that we didn't override
  //clone()  method of Cloneable interface
  public ClassA clone() throws CloneNotSupportedException{
    return (ClassA)super.clone();
  }
}

Result
c1 | num1 | 1
c2 | num1 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | AB
c1 | builder | AB
In the example above, we see that num1 value in c1 is copied to num1 in c2. We also see that builder reference in c1 is copied to builder reference in c2. Since we copied the reference of builder to c2, any changes made to builder of c2 will be reflected to c1.

Deep Copy

This copy mechanism is similar to Shallow Copy. However, in deep copy, new instances of objects in the object that we wanna copy are created. Making the copy independent to the original. We use deep copy if we want to create an independent copy of an object contained in the object that we wanna copy.

This example demonstrates Deep Copy.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = c1.clone();
    
    System.out.println("c1 | num1 | " + c1.num1);
    System.out.println("c2 | num1 | " + c2.num1);
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA implements Cloneable{
  int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  public ClassA clone() throws CloneNotSupportedException{
    
    //create a shallow copy of ClassA
    ClassA ca = (ClassA)super.clone();
    
    //create a deep copy of builder
    ca.builder = new StringBuilder(builder.toString());
    
    //return the shallow copy
    return ca;
  }
}

Result
c1 | num1 | 1
c2 | num2 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | A
c1 | builder | AB
Copy Constructor

We can set up a constructor to copy fields of another object with the same type. This type of constructor is called Copy Constructor.

This example demonstrates Copy Constructor.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = new ClassA(c1);
    
    System.out.println("c1 | num1 | " + c1.getNum());
    System.out.println("c2 | num1 | " + c2.getNum());
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA{
  private int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  //Copy Constructor
  ClassA(ClassA a){
    num1 = a.num1;
    builder = a.builder;
  }
  
  int getNum(){
    return num1;
  }
  
}

Result
c1 | num1 | 1
c2 | num2 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | AB
c1 | builder | AB
In the example above, the copy constructor copies primitives and produces a shallow copy of builder object. However, copy constructor doesn't utilize the mechanism of clone() method. In my personnal preference, I use clone() if an object is going to be copied a lot. Otherwise, copy constructor will suffice.

equals() Method

Method Form: public boolean equals(Object obj)
Indicates whether some other object is "equal to" this one. More information can be read in the documentation. This method is overriden by various classes like String class. In String class, equals() compares the characters of Strings. In Object class, equals() compares the hash code of objects.

This example demonstrates equals().
public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    ClassA c2 = c1;
    ClassA c3 = new ClassA();
    
    System.out.println("c1 == c2: "+ c1.equals(c2));
    System.out.println("c1 == c3: "+ c1.equals(c3));
    
    //equivalent to
    //System.out.println("c1 == c2: "
    //+(c1.hashCode() == c2.hashCode()));
    //System.out.println("c1 == c3: "
    //+(c1.hashCode() == c3.hashCode()));
    
  }
}

class ClassA{
}

Result
c1 == c2: true
c1 == c3: false

getClass() Method

Method Form: public final Class<?> getClass()
Returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class. You should be knowledgeable about Class<T> to understand the return type of this method. More information can be found in the documentation.

This example demonstrates getClass().
import java.lang.reflect.Field;

public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    Class<?> c = c1.getClass();
    
    for(Field f : c.getFields())
      System.out.println(f.getType());
  }
}

class ClassA{
  public int num = 1;
  public double decimal = 10.5;
}

Result
int
double

hashCode() Method

Method Form: public int hashCode()
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap. More information can be found in the documentation.

This example demonstrates hashCode().
public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    ClassA c2 = new ClassA();
    
    System.out.println("c1 hashCode(): "
    + c1.hashCode());
    System.out.println("c2 hashCode(): "
    + c2.hashCode());
    
  }
}

class ClassA{
}

Result(may vary)
c1 hashCode: 1175962212
c2 hashCode: 1706377736

toString() method

Method Form: public String toString()
Returns a string representation of the object. This method is overriden by various classes like String. In String, toString() return the string value of the String. In Object class, toString() returns this type of String:
getClass().getName() + '@' + Integer.toHexString(hashCode())
More information can be found in the documentation.

This example demonstrates toString().
public class SampleClass{

  public static void main(String[] args){
    
    ClassA c1 = new ClassA();
    System.out.println(c1.toString());
  }
}

class ClassA{
}

Result(may vary)
ClassA@4617c264
Tip: We don't need to call toString() if we wanna display value returned by toString() on the console using println(). If we put an object in println(), toString() is automatically called. Take a look at this example.
public class SampleClass{

  public static void main(String[] args){
    
    ClassA c1 = new ClassA();
    System.out.println(c1);
    
    String str = "Hello";
    System.out.println(str);
  }
}

Result(may vary)
ClassA@4617c264
Hello
Objects Class

Objects class consists of static utility methods for operating on objects, or checking certain conditions before operation. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, comparing two objects, and checking if indexes or sub-range values are out of bounds.

In this tutorial, I'm gonna demonstrate some methods. More methods can be found in the documentation.
checkFromIndexSize() Method

Method Form: public static int checkFromIndexSize(int fromIndex, int size, int length)
Method Form: public static long checkFromIndexSize(long fromIndex, long size, long length)
Checks if the sub-range from fromIndex (inclusive) to fromIndex + size (exclusive) is within the bounds of range from 0 (inclusive) to length (exclusive). More information can be found in the documentation.

This example demonstrates checkFromIndexSize().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int start = 0;
      int end = 4;
      start = Objects.checkFromIndexSize
      (1, end, pseudoBuffer.length);
      
      System.out.print("[ ");
      for(int i = start; i < start+end; i++)
        System.out.print(pseudoBuffer[i] + " ");
      System.out.print("]");
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Range [1, 1 + 4) out of bounds for length 4

Result(if end == 3)
[ 50 25 39 ]
checkFromToIndex() Method

Checks if the sub-range from fromIndex (inclusive) to toIndex (exclusive) is within the bounds of range from 0 (inclusive) to length (exclusive). This method has two forms. More information can be found in the documentation.

This example demonstrates int checkFromToIndex() method.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int start = 0;
      int end = 5;
      start = Objects.checkFromToIndex
      (1, end, pseudoBuffer.length);
      
      System.out.print("[ ");
      for(int i = start; i < end; i++)
        System.out.print(pseudoBuffer[i] + " ");
      System.out.print("]");
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Range [1, 5) out of bounds for length 4

Result(if end == 4)
[ 50 25 39 ]
checkIndex() Method

Checks if the index is within the bounds of the range from 0 (inclusive) to length (exclusive). This method has two forms. More information can be found in the documentation.

This example demonstrates long checkIndex() method.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int index = 0;
      index = Objects.checkIndex
      (4, pseudoBuffer.length);
      
      System.out.println("Content: " 
      + pseudoBuffer[index]);
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Index 4 out of bounds for length 4

Result(if index == 3)
Content: 39
compare() Method

Method Form: public static <T> int compare(T a, T b, Comparator<? super T> c)
Returns 0 if the arguments are identical and c.compare(a, b) otherwise. Consequently, if both arguments are null 0 is returned. Note that if one of the arguments is null, a NullPointerException may or may not be thrown depending on what ordering policy, if any, the Comparator chooses to have for null values.

This example demonstrates compare().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = "string";
    String s3 = "myString";
    String s4 = null;
    
    int result = 
    Objects.compare
    (s1, s2, String.CASE_INSENSITIVE_ORDER);
    System.out.println("1st result: " + result);
    
    result = Objects.compare
    (s1, s3, String.CASE_INSENSITIVE_ORDER);
    System.out.println("2nd result: " + result);
    
    result = Objects.compare
    (s4, s4, String.CASE_INSENSITIVE_ORDER);
    System.out.println("3rd result: " + result);
    
    result = Objects.compare
    (s1, s4, String.CASE_INSENSITIVE_ORDER);
    System.out.println("4th result: " + result);
  }
}

Result
1st result: 0
2nd result: 6
3rd result: 0
java.lang.NullPointerException...
deepEquals() method

Method Form: public static boolean deepEquals(Object a, Object b)
Returns true if the arguments are deeply equal to each other and false otherwise. Two null values are deeply equal. If both arguments are arrays, the algorithm in Arrays.deepEquals is used to determine equality. Otherwise, equality is determined by using the equals method of the first argument.

This example demonstrates deepEquals().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    int[][] ints1 = {{1,2,3},{4,5,6}};
    int[][] ints2 = {{1,2,3},{4,5,6}};
    int[][] ints3 = {{1,3},{4,5,6}};
    String s1 = "String";
    String s2 = "string";
    
    System.out.println("1st result: " +
    Objects.deepEquals(ints1, ints2));
    
    System.out.println("2nd result: " +
    Objects.deepEquals(ints1, ints3));
    
    System.out.println("3rd result: " +
    Objects.deepEquals(s1, s2));
  }
}

Result
1st result: true
2nd result: false
3rd result: false
equals() method

Method Form: public static boolean equals(Object a, Object b)
Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned. Otherwise, if the first argument is not null, equality is determined by calling the equals method of the first argument with the second argument of this method. Otherwise, false is returned.

This example demonstrates equals().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = "String";
    String s3 = "sTrInG";
    String s4 = null;
    
    System.out.println("1st result: " +
    Objects.equals(s1, s2));
    
    System.out.println("2nd result: " +
    Objects.equals(s1, s3));
    
    System.out.println("3rd result: " +
    Objects.equals(s1, s4));
  }
}

1st result: true
2nd result: false
3rd result: false
hash() method

Method Form: public static int hash(Object... values)
Generates a hash code for a sequence of input values. The hash code is generated as if all the input values were placed into an array, and that array were hashed by calling Arrays.hashCode(Object[]). More information can be found in the documentation.

This example demonstrates hash().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    StringBuilder sb = 
    new StringBuilder("Builder");
    int[] ints = {2,4,6};
    
    int hash = Objects.hash(s1, sb, ints);
    System.out.println("1st result: " + hash);
    
    hash = Objects.hash(s1);
    System.out.println("2nd result: " +
    (hash == s1.hashCode()));
  }
}

Result(may vary)
1st result: -1273781580
2nd result: false
hashCode() method

Method Form: public static int hashCode(Object o)
Returns the hash code of a non-null argument and 0 for a null argument. This example demonstrates hashCode().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = null;
    
    System.out.println("1st result: " + Objects.hashCode(s1));
    System.out.println("2nd result: " + Objects.hashCode(s2));
    //exception will occur here
    //System.out.println("2nd result: " + s2.hashCode());
  }
}
isNull() method

Method Form: public static boolean isNull(Object obj)
Returns true if the provided reference is null otherwise returns false. This method exists to be used as a Predicate, filter(Objects::isNull)

This example demonstrates isNull().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("str1");
    ar.add(null);
    ar.add("str2");
    ar.add(null);
    
    long nullCount = ar.stream()
      .filter(Objects::isNull)
      .count();
      
    System.out.println("null count: "+nullCount);
  }
}

Result
null count: 2
nonNull() method

Method Form: public static boolean nonNull(Object obj)
Returns true if the provided reference is non-null otherwise returns false. This method exists to be used as a Predicate, filter(Objects::nonNull).

This example demonstrates nonNull().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("str1");
    ar.add(null);
    ar.add("str2");
    ar.add(null);
    
    ar.stream()
      .filter(Objects::nonNull)
      .forEach(System.out::println);
      
  }
}

Result
str1
str2
requireNonNull() method

Checks that the specified object reference is not null. This method has three forms. More information can be found in the documentation. This example demonstrates requireNonNull(T obj, String message).
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String str = null;
    
    try{
      Object s = 
      Objects.requireNonNull(str, "str is null!");
      if(s instanceof String)
        System.out.println(s);
    }
    catch(NullPointerException e){
      System.err.println(e.getMessage());
    }
  }
}

Result
str is null!
requireNonNullElse() method

Method Form: public static <T> T requireNonNullElse(T obj, T defaultObj)
Returns the first argument if it is non-null and otherwise returns the non-null second argument. This example demonstrates requireNonNullElse().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(10);
    ar.add(null);
    ar.add(100);
    ar.add(null);
    
    ar.stream()
      .map((e) -> 
       Objects
       .requireNonNullElse(e,Integer.valueOf(0)))
      .forEach(System.out::println);
  }
}

Result
10
0
100
0
requireNonNullElseGet() method

Method Form: public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
Returns the first argument if it is non-null and otherwise returns the non-null value of supplier.get(). Thie example demonstrates requireNonNullElseGet().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Double> ar = 
    new ArrayList<>();
    
    ar.add(3.5);
    ar.add(null);
    ar.add(5.5);
    ar.add(null);
    
    ar.stream()
      .map((e) -> 
       Objects
       .requireNonNullElseGet(e,Math::random))
      .forEach(System.out::println);
  }
}

Result(may vary)
3.5
0.6468480442983721
5.5
0.762667953719096
toString() method

Returns the result of calling toString for a non-null argument or a particular message for a null argument. This method has two forms. This example demonstrates toString() and its forms.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String str = "String";
    String s = null;
    
    //public static String toString(Object o)
    System.out.println(Objects.toString(str));
    System.out.println(Objects.toString(s));
    
    //public static String toString(Object o,
    //String nullDefault)
    System.out.println
    (Objects.toString(str, "none"));
    System.out.println
    (Objects.toString(s, "none"));
  }
}

Result
String
null
String
none

No comments:

Post a Comment