Comparing primitive and reference types (using equals and hascode)

-Primitive variables can be compared by using == operator
-Objects type variables can be compared by overriding equals() and hashcode()
methods (if we do not override equals() and hashcode() methods, the program will result into unexpected output)
-For objects to be equal, their equals() method should return true and hashcode() method should return same hashcode.
===========================================================
Example: To demonstrate how to compare primitive and reference data types. For reference data types such as Car in below example, the equals() and hashcode() methods are overridden so that "deep comparison" is possible.
import java.util.HashSet;
import java.util.Set;
public class TestCompare {
     public static void main(String[] args) {
          int age = 20;// primitive
          //compare with == for primitive data types
          //check equality
          if (age == 20) {
               System.out.println("You are 20");
          }
          //Object type variable
          Car a = new Car();
          Car b = new Car();
          a.setModel("Mazda");
          a.setPrice(1000);         
          b.setModel("Mazda");
          b.setPrice(1000);         
          if(a.equals(b)) {
               System.out.println("Equal Car");
          } else {
               System.out.println("Not equal Car");
          }         
       //adding two objects a and b with same states
       //to Set will results into one unique object
       //only. Set calls Objects' equals() and hashcode()
       //methods to compare their contents
       Set<Car> carSet = new HashSet<Car>();
       carSet.add(a);
       carSet.add(b);
       System.out.println("Car Set size:" + carSet.size());
     }
}
Output:
You are 20
Equal Car
Car Set size : 1
--------------------------------------------------
public class Car {
     private String model;
     private double price;
     public double getPrice() {
          return price;
     }
     public void setPrice(double price) {
          this.price = price;
     }
     public String getModel() {
          return model;
     }
     public void setModel(String model) {
          this.model = model;
     }
     //method to compare state of this and other object
     //which is passed as a parameter
     //if all states are same between two objects then
     //this method will result true, else false
     @Override
     public boolean equals(Object b) {
       boolean result = false;
       if (b instanceof Car) {
         Car c = (Car) b;
         result = c.getModel().equalsIgnoreCase(getModel()) && 
                  c.getPrice() == getPrice();
       }
       return result;
     }
     //Require to override when overriding equals() method
     //so that two equals objects will return same hashcode
     //which will avoid duplicate elements in Hash 
     //like collections, such as HashSet
     @Override
     public int hashCode() {
          return (int)(price/10000)*7;
     }
}

Passing primitive and reference types method parameters

-Parameters are pass-as-copy for primitive and reference data type
-Object will be passed as copy of reference  
-While passing primitive type as a method parameter, java passes copy of the primitive type to the calling method, so that the original value will be kept different from the copy, which results into two separate variables with separate memory locations.
Examples: int, long, double as a method parameters

-While passing reference type as a method parameter, java passes copy of reference to the calling method (copy of reference but not copy of Object). So original reference variable and its copy are pointing to the same Object memory location. which results into two reference variables pointing to the same Object memory location. With this you can modify Object states using either of the references (original or copy).
Fig. 1: Memory map for primitive and reference data types
-------------------------------------------------------------------------------------------------------------------------
Example: To demonstrate how java passes parameters for primitive and reference data types.
public class TestParamPass {
  public static void main(String[] args) {
  int age = 20; //primitive data type
  Employee emp = new Employee(); //reference data type
  System.out.println("Original age: " + age);
  System.out.println("Original salary: " + emp.getSalary());
  processAge(age, emp);
  System.out.println("Updated age: " + age);
  System.out.println("Updated Salary: " + emp.getSalary());
 }

  //Method to process passed parameters

  private static void processAge(int ageParam, Employee empParam) {
   empParam.setSalary(2000);
   ageParam++;
  }
}
Output:
Original age: 20
Original salary: 0.0
Updated age: 20
Updated Salary: 2000.0
------------------------------------------------------------------------
public class Employee {
     double salary;
     public double getSalary() {
          return salary;
     }
     public void setSalary(double salary) {
          this.salary = salary;
     }
}

Static Methods and Variables

-Can be accessed at class level
-Does not require Class instance (aka Object) 
-Static variable keeps only one value during entire program execution,
 unlike Instance variable, which maintains separate values (state) per instance (Object) 
======================================================
Example: To demonstrate Static and Instance variables and methods

public class AccountHandler {
     public static double MAX_DEPOSIT_LIMIT = 1000000;         
     //Instance method to deposit specified amount to specified customer Id
     public void doDeposit(String customerId, double amount) {
          if (amount > MAX_DEPOSIT_LIMIT) {
               System.out.println("Can not process, exceeding limits, Amount: " + amount);
               return; //return to caller
          }
          System.out.println("Depositing amount " + amount + " for Customer Id : " + customerId);
     }
     //Static method to show customer's ß for the specified customer Id
     public static void showPortfolio(String customerId) {
          System.out.println("Showing Portfolio for Customer Id : " + customerId);
     }
}
---------------------------------------------------------------------------
public class TestStatic {
    public static void main(String[] args) {
          //check account deposit limits by accessing static MAX_DEPOSIT_LIMIT
          System.out.println("Allowable deposit limit : " + AccountHandler.MAX_DEPOSIT_LIMIT);
          AccountHandler handler = new AccountHandler();
          //calling instance method by using dot operator on instance of AccountHandler class
          handler.doDeposit("a007", 10000);
          //calling static method by using dot operator on AccountHandler class
          AccountHandler.showPortfolio("a007");
     }
}

Instance variables, Local variables and Method Parameters


Instance variables:
-are mainly use to store objects's state 
-visibility can be controlled by using appropriate scope (public, private, protected,default)
-lifetime is limited to containing object's lifetime

Local variables and Method parameters:
-are local to specific method 
-visibility can not be controlled its only visible inside the defined method
-lifetime is temporary and only limited to method scope
======================================================
Example: To demonstrate different types of variables
public class Customer {   
     private String lastName; //instance variable
     private String firstName; //instance variable
    
     public String getLastName() {
          return lastName;
     }
     public void setLastName(String lName) { //parameter 
          lastName = lName;
     }
     public String getFirstName() {
          return firstName;
     }
     public void setFirstName(String fName) {
          firstName = fName;
     }
     //Returns full name by concatenating Last and First name
     public String getFullName() {
          String fullName; //local variable
          fullName = getLastName() + " " + getFirstName();
          return fullName;
     }
}

Polymorphism

- Sub-class can override superclass methods for any specific behavior implementation
- JVM dynamically calls appropriate method based on the inheritance hierarchy
- For the same method call the objects behave differently based on implementation
 
- Promotes reuse and design can be extended by adding more sub-classes without 
changing interface definition. Such as getLanguage() method in below example which takes Country as a parameter, so that it can be used for any Country type, such as Japan, America and Korea including any future new Countries. 
======================================================
Example: Polymorphism using method overriding

Description: Code to demonstrate polymorphic behavior in Java using inheritance and method overriding.


Country is superclass of all specific country sub-classes. The method getLanguage() can be overridden by sub-classes of Country class as shown below, where Japan and America are overriding default implementation of getLanguage() method from Country but Korea class is not overriding it. So when caller program (TestPoly) calls getLanguage() on the Country type the JVM will dynamically call appropriate 
getLanguage() method from sub-class, if method is not overridden in sub-class then JVM will call super class's method i.e. Method from Country in case of Korea.


Design: Class Diagram






Source: 

public class TestPoly {
    public static void main(String[] args) {
         Country[] countries = new Country[3];
         countries[0] = new Japan();
         countries[1] = new America();
         countries[2] = new Korea();    
         for (Country country : countries) {
              getCountryInfo(country);
         }
    }
    //method to print country specific information
    public static void getCountryInfo(Country country) {
         System.out.println(country.getLanguage());
    }
}
Output
Japan's National Language is Japanese
America's National Language is English
UN-KNOWN
--------------------------------------------------------
public class Country {   
     //default implementation
     protected String getLanguage() {
          return "UN-KNOWN";
     }
}
--------------------------------------------------------
public class Japan extends Country {   
     @Override
     public String getLanguage() {
          return "Japan's National Language is Japanese";
     }
}
--------------------------------------------------------
public class America extends Country {
     @Override
     public String getLanguage() {
          return "America's National Language is English";
      }
}
--------------------------------------------------------
public class Korea extends Country {


}