OOPS Concept in Java

Overview

Most import aspects of OOPS
  • Data Hiding. Outside person can't access our internal data directly or our internal data should not go out directly, this OOP feature is nothing but Data Hiding. After validation or authentication outside person can access our internal data.
    • By declaring data member (variable) as private we can achieve Data Hiding. e.g.
 public class Person {
final private String name;
final private int age;

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

public String getName() {
// Validation
return name;
}

public int getAge() {
// Validation
return age;
}
}
    • The main advantage of Data Hiding is security. 
    • e.g. 
      • After providing proper username and password, we can able to access our Gmail inbox information.
      • Even though we are valid customer of the bank, we can able to access our account information but we can't access other's account information.
  • Abstraction: Hiding internal implementation and just highlight the set of services what we are offering is a concept of abstraction. 
    • e.g. Through Bank ATM GUI Screen, bank people are highlighting the set of services what they are offering without highlighting internal implementation. 
    • The main advantages of using Abstraction are
      • Security: We can achieve security because we are not highlighting our internal implementation. 
      • Enhancement: Without affecting outside person, we can able to perform any type of changes in our internal system and hence enhancement will become easy.
      • Maintainability:. It improves maintainability after implementing Abstraction. 
      • Improves Easiness: It improves easiness to use our system. 
    • By using Interfaces and Abstract classes, we can implement Abstraction.
  • Encapsulation: The process of binding data and the corresponding methods into a single unit is nothing but encapsulation. 
    • e.g. Every class in Java is an example of encapsulation.
 public class Person {
private String name;
private int age;

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
    • The main advantages of encapsulation are 
      • We can achieve security.
      • Enhancement will become easy.
      • It improves maintainability of the application.
    • The main advantage of encapsulation is we can achieve security but, the main disadvantage of encapsulation is, it increases rendering the code and slows the execution of our operation. 
  • Tightly Encapsulated Class: A class is said to be tightly encapsulated if and only if each and every variable is declared as private. Whether class contains corresponding getter and setter or not and whether these methods are declared as public or not, these things we are not required to check. 
     public abstract class Person {
    final private String name;
    private int age;

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

    public String getName() {
    return name;
    }

    public int getAge() {
    return age;
    }

    public boolean validateAge(int age) {
    return age > 3;
    }
    }
  • IS-A Relationship: IS-A Relationship is also known as Inheritance. The main advantage of IS-A Relationship is code reusability. By using extends keyword we can implement IS-A Relationship.
     public class Person {
    final private String name;
    final private int age;
    final private double salary;

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

    public String getName() {
    return name;
    }

    public int getAge() {
    return age;
    }

    public double getSalary() {
    return salary;
    }

    public void showPersonClassName() {
    System.out.println(this.hashCode());
    }
    }
     public class Doctor extends Person {
    final private String hospitalName;

    public Doctor(String name, int age, double salary, String hospitalName) {
    super(name, age, salary);
    this.hospitalName = hospitalName;
    }

    public String getHospitalName() {
    return hospitalName;
    }

    public void showDoctorClassName() {
    System.out.println(this.hashCode());
    }
    }
     public class InheritanceMain {
    public static void main(String args[]) {
    Person doctor1 = new Doctor("Joe Dane", 24, 56000.0, "Apollo Hospital");
    // This is an example of Polymorphism
    doctor1.showPersonClassName();
    /* This below code is invalid, it prompts compile time error. */
    /* Because reference of Parent class can create object of child class but
    can't access properties and methods of child class */
    // doctor1.showDoctorClassName();
    Doctor doctor2 = new Doctor("Jane Perry", 24, 65000, "Seven Hospital");
    /* This below code is valid */
    /* Because object of child class can access properties and methods of parent class */
    doctor2.showPersonClassName();
    doctor2.showDoctorClassName();
    /* This below code is invalid. Because reference of Child class can't be used to create object of Parent class */
    // Doctor doctor3 = new Person("Kate Upton", 26, 95000.0);
    }
    }
    • Whatever methods Parent has, are by default available to the Child. And hence with the child reference we can call both parent and child methods. 
    • Whatever methods child has, are by default not available to the parent. And hence with the parent reference we can't call child specific methods.
    • Parent reference can be used to hold Child object but using that reference we can't call Child specific methods. But we can call the methods present in Parent class.
    • Parent reference can be used to hold Child object but child reference cannot be used to hold Parent object. 
    • The most common methods which are applicable for any type of Child, we have to define in Parent class. The specific methods which are applicable for a particular Child, we have to define in Child class.
    • Total Java API is implemented based on Inheritance concept. The most common methods which are applicable for any Java Object are defined in Object class. And hence every class in Java is a Child class of Object either directly or indirectly, so that Object class methods by default available to every Java class without rewriting. Due to this, Object class acts as root to all Java classes.
    • Throwable class has the most common methods which are required for every exception and error classes. Hence this class acts as root for Java Exception Hierarchy. 
    • Multiple Inheritance: A Java class can't extend more than one class at a time. Hence, Java won't support multiple inheritance in classes.
    • Either directly or indirectly Java won't provide support for multiple inheritance with respect to classes because of ambiguity problem. i.e. Suppose we are inheriting two classes at a time in a Class and if the Object of the Class calls a method which is available in both the inheriting classes, then it is impossible to identify which method of the class is being called upon.
    • Java support multiple inheritance for Interfaces. Even though multiple method declarations are available but implementation is unique and hence there is no chance of ambiguity problem in Interfaces.
 public interface PersonInterface {
boolean validateName();

boolean validateAge();
}
 public interface TeacherInterface {
boolean validateName();

void validateTeacher();
}
 public interface ProgrammerInterface extends PersonInterface, TeacherInterface {
boolean validateProgrammer();
}
 public class Programmer extends Person implements ProgrammerInterface {

private String companyName;

public Programmer(String name, int age, double salary, String companyName) {
super(name, age, salary);
this.companyName = companyName;
}

public String getCompanyName() {
return companyName;
}

public void setCompanyName(String companyName) {
if (validateProgrammer()) {
this.companyName = companyName;
}
}

@Override
public boolean validateProgrammer() {
if (validateName() && validateAge()) {
System.out.println("The inputs are valid");
return true;
} else {
System.out.println("Please fill in the details carefully!!! The inputs are not valid.");
return false;
}
}

@Override
public boolean validateName() {
return !this.getName().equals("");
}

@Override
public boolean validateAge() {
return this.getAge() > 18;
}

@Override
public void validateTeacher() {

}

public void showProgrammerName() {
System.out.println(this.hashCode());
}
}
 public class InheritanceMain {
public static void main(String[] args) {
/* For Doctors*/
Person doctor1 = new Doctor("Joe Dane", 24, 56000.0, "Apollo Hospital");
// Doctor doctor3 = new Person("Kate Upton", 26, 95000.0);
/* This is an example of Polymorphism */
doctor1.showPersonClassName();
/* This below code is invalid, it prompts compile time error. */
/* Because reference of Parent class can create object of child class but
can't access properties and methods of child class */
// doctor1.showDoctorClassName();
Doctor doctor2 = new Doctor("Jane Perry", 24, 65000, "Seven Hospital");
/* This below code is valid */
/* Because object of child class can access properties and methods of parent class */
doctor2.showPersonClassName();
doctor2.showDoctorClassName();
/* This below code is invalid. Because reference of Child class can't be used to create object of Parent class */
// Doctor doctor3 = new Person("Kate Upton", 26, 95000.0);
/* For Teachers */
Person teacher1 = new Teacher("Sumit Vyas", 35, 56000.0, "K.B.C.H");
/* For Programmers */
Person programmer1 = new Programmer("Abhishek Tiwari", 28, 125000.0, "Kotak Mahindra");
Programmer programmer2 = new Programmer("Kiran Rathod", 17, 75000.0, "Shaw Info Solutions");

/* For Common People */
Person[] personArray = new Person[5];
personArray[0] = doctor1;
personArray[1] = doctor2;
personArray[2] = programmer1;
personArray[3] = teacher1;
/* As programmer2 is an Object of Programmer which extends Person,
that's why we can put object of Programmer into an array of Person */
personArray[4] = programmer2;
printDetails(personArray);
}

public static void printDetails(Person[] person) {
for (Person p : person) {
System.out.println("Name: " + p.getName());
System.out.println("Institution: " + p.getSalary());
}
}
}
  • HAS-A Relationship
    • It is also know as Composition or Aggregation. 
    • There is no specific keyword to implement HAS-A Relation but most of the times we are depending on new keyword.
    • The main advantage of HAS-A Relationship is reusability of the code. 
    • e.g. Person HAS-A name Reference.
      •  public class Person {
        final private String name;
        }
    • Difference between Composition and Aggregation
      • Composition: Without existing container object if there is no chance of existing contained objects, then container and contained objects are strongly associated and this strong association is nothing but Composition. e.g. University consists of several Departments. Without existing University there is no chance of existing Department. Hence University and Department are strongly associated and this strong association is nothing but Composition. 
      • Aggregation: Without existing container object if there is a chance of existing contained objects, then container and contained objects are weakly associated and this weak association is nothing but Aggregation. e.g. Department consists of several Professors. Without existing Department, there may be a chance of existing Professor objects. Hence Department and Professor objects are weakly associated and this weak association is nothing but Aggregation.


      • In Composition objects are strongly associated whereas in Aggregation, objects are weakly associated. 
    • IS-A Relationship versus HAS-A Relationship
      • If we want total functionality of a class automatically then we should go for IS-A Relationship. e.g. Data class. 
      • If we want part of the functionality of a class then we should go for HAS-A relationship. 
  • Method Signature: In Java Method Signature consists of Method name followed by Argument Types.
    • Return type is not part of Method Signature in Java.
    • In a class, two methods with the same signatures are not allowed even though their return types are different.
  • Overloading: Two methods are said to be Overloaded if and only if both methods having same name but different argument types.


    • In C Language, method overloading concept is not available, hence we can't declare multiple methods with the same name but different argument types. If there is a change in argument type compulsory we should go for new method name which increases complexity of programming.
    • In Java Language, we can declare multiple methods with the same name but different argument types. Such type of methods are called, Overloaded methods.
    • Having Overloading concept in Java, reduces complexity of programming. 
 public class Test {
public void m1(int i) {
System.out.println("Integer Arguments: " + i);
}

public void m1(float f) {
System.out.println("Floating Type Arguments: " + f);
}

public void m1(String s) {
System.out.println("String Type Arguments: " + s);
}
}
  public static void main(String[] args) {
Test t1 = new Test();
t1.m1(20);
t1.m1(20f);
t1.m1("String Argument");
}
    • Case 1: Automatic promotion in Overloading
      • While resolving Overloaded methods if exact matched method is not available then we won't get any compile time error immediately. First it will promote argument to the next level and check whether matched method is available or not. If matched method is available then it will be considered. And if the matched method is not available then compiler promotes argument once again to the next level. This process will be continued until all possible promotions. Still if the matched method is not available then we will get compile time error. 
      • The following are all possible promotions in overloading. This process is called Automatic promotion in Overloading.
        • byte >> short
        • short >> int
        • int >> long
        • long >> float
        • float >> double
        • char >> int
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
t1.m1(20);
t1.m1(20F);
t1.m1("String Argument");
/* The following code will invoke the method having Integer Type */
t1.m1('c');
/* The following code will invoke the method having Floating Type */
t1.m1(20L);
}
}
 Integer Arguments: 20
Floating Type Arguments: 20.0
String Type Arguments: String Argument
Integer Arguments: 99
Floating Type Arguments: 20.0
    • Case 2: While resolving overloaded methods compiler will always gives a precedence for Child type Argument and compare it with Parent type Argument. 
 public class Test {
public void m1(String s) {
System.out.println("String Type Argument " + s);
}

public void m1(Object obj) {
System.out.println("Object Type Argument" + obj);
}
}
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
t1.m1(new Object());
t1.m1("String Argument");
t1.m1(null);
}
}
 Object Type Argumentjava.lang.Object@2f7c7260
String Type Argument String Argument
String Type Argument null
    • Case 3:
 public class Test {
public void m1(String s) {
System.out.println("String Type Argument: " + s);
}

public void m1(StringBuffer sb) {
System.out.println("String Buffer Type Argument: " + sb.toString());
}
}
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
t1.m1("String Argument");
t1.m1(new StringBuffer("StringBuffer Argument"));
/* In this case this null argument will give an error because
both String and StringBuffer stands at the same level of hierarchy. */
// t1.m1(null);
}
}
    • Case 4:
 public class Test {
public void m1(int i, float f) {
System.out.println("Int - Float Type Argument");
}

public void m1(float f, int i) {
System.out.println("Float - Int Type Argument");
}
}
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
t1.m1(20F, 10);
t1.m1(20, 10F);
/* This below code will give compile time error because
* compiler is not able to identify which overloaded methods is called */
// t1.m1(10,10);
/* This below code will give compile time error because
* compiler is not able to identify which overloaded methods is called */
// t1.m1(10f, 10f);
}
}
    • Case 5: In general var-arg method will get least priority i.e. if no other method matched then only var-arg method will get the chance. It is exactly same as default case inside switch.
 public class Test {
public void m1(int i) {
System.out.println("Int Type Argument");
}

public void m1(int... i) {
System.out.println("Var-Arg Int Type Argument");
}
}
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
t1.m1();
t1.m1(10, 20);
t1.m1(10);
}
}
 Var-Arg Int Type Argument
Var-Arg Int Type Argument
Int Type Argument
    • Case 6: In case of method overloading, compiler chooses the method according to reference.
 class Animal {
}

class Monkey extends Animal {
}

public class Test {
public void m1(Animal a) {
System.out.println("Animal Type Argument");
}

public void m1(Monkey m) {
System.out.println("Monkey Type Argument");
}
}
 public class OverloadingMain {
public static void main(String[] args) {
Test t1 = new Test();
Animal a1 = new Animal();
t1.m1(a1);
Monkey m1 = new Monkey();
t1.m1(m1);
Animal am = new Monkey();
t1.m1(am);
}
}
 Animal Type Argument
Monkey Type Argument
Animal Type Argument
  • Overriding: Whatever methods Parent has by default available to the Child through Inheritance. If Child class not satisfied with the Parent class implementation then, Child is allowed to redefine that method based on its requirement. This process is called Overriding. 
    • The Parent class method which is overridden is called Overridden Method. 
    • The Child class method which is overriding is called Overriding Method.
 public class Parent {
public void property() {
System.out.println("Cash + Land + Gold");
}

public void marry() {
System.out.println("Marry Jane");
}
}
 public class Children extends Parent {
@Override
public void marry() {
System.out.println("Ivy Fernandez");
}
}
public class OverridingMain {
public static void main(String[] args) {
/* In the below code 'new Parent()' is a runtime method */
Parent p = new Parent();
p.marry();
/* In the below code 'new Children()' is a runtime method */
Children c = new Children();
c.marry();
/* In the below code 'new Children()' is a runtime method */
Parent p1 = new Children();
/* In overriding, method resolution is always been taken care of JVM and not by compiler.
* As this is a runtime method which is being handled by JVM, JVM ignores the reference rules and
* checks if a method is being overridden or not. If it is overridden then JVM calls the overriding
* method or else it will simply call the non-overridden method */
p1.marry();
}
}
    • In overriding method resolution always takes care by JVM based on runtime object and hence overriding is also considered as Runtime Polymorphism or Dynamic Polymorphism or Late Binding. 
    • Rules for Overriding
      • In Overriding method names and argument types must be matched i.e. method signatures must be same.
      • In Overriding return types must be same but, this rule is applicable until 1.4 version only. From 1.5 version onwards, we can take co-variant return types. According to this Child method return type need not be same as Parent method return type. It's Child type also allowed.
 public class Parent {
public Object property() {
System.out.println("Cash + Land + Gold");
return null;
}

public void marry() {
System.out.println("Marry Jane");
}
}
 public class Children extends Parent {
@Override
public void marry() {
System.
out.println("Ivy Fernandez");
}

@Override
public String property() {
System.
out.println("Co-Variant Return Type");
return null;
}
}
      • For overriding, if the return type of Parent method is primitive then the return type of Child method must be the same. For non-primitive return types for Parent and Child methods kindly refer the below image.
      • Parent class private methods not available to the Child class and hence overriding concept not applicable for private methods.
      • Based on our requirement we can define exactly same private method in Child class. It is valid but not overriding. 
 public class Parent {
private void marry() {
System.out.println("Marry Jane");
}
}
 public class Children extends Parent {
private void marry() {
System.out.println("Ivy Fernandez");
}
}
      • We can't override final methods of Parent class in Child class. If we are trying to override we will get compile time error. 
      • We must override Parent class abstract methods in the Child class to provide implementation. 
 public abstract class AbstractRelation {
public abstract void displayRelation();
}
 public class Parent extends AbstractRelation {
@Override
public void displayRelation() {
System.out.println("I am the Parent Class of Children Class");
}

public void property() {
System.out.println("Cash + Land + Gold");
}

public void marry() {
System.out.println("Marry Jane");
}
}
      • We can override non-abstract method as abstract method in the Child class. The main advantage of this approach is we can stop the availability of Parent method implementation to the next level child classes.
 public class Parent {
public void m1() {
System.out.println("Implement Parent Method");
}
}
 public abstract class Children extends Parent {
@Override
public abstract void m1();
}
      • In overriding the following modifiers won't keep any restriction 
        • synchronized
        • native
        • strictfp
      • While overriding we can't reduce the scope of access modifiers but we can increase the scope of access modifiers
      • private << default << protected << public (Note: private methods of Parent class can't be overridden)
 public class Parent {
void m1() {
System.out.println("Implement Parent Method");
}
}
 public class Children extends Parent {
@Override
public void m1() {
System.out.println("Implement Child Method");
}
}
 public class OverridingMain {
public static void main(String[] args) {
Parent p = new Parent();
p.m1();
Children c = new Children();
c.m1();
Parent p1 = new Children();
p1.m1();
}
}
 Implement Parent Method
 Implement Child Method
 Implement Child Method
      • If Child class method throws any Checked Exception compulsory Parent class method should throw the same Checked Exception are its Parent, otherwise we will get Compile time error but, there are no restrictions for Unchecked Exceptions.
      • In the below Exceptions chart, Errors are also considered as Unchecked Exceptions.
 public class Parent {
public void m1() throws Exception {
System.out.println("Parent Class Method that throws Exception");
}

public void m2() {
System.out.println("Parent Class Method");
}

public void m3() throws Exception {
System.out.println("Parent Class Method that throws Exception");
}

public void m4() throws IOException {
System.out.println("Parent Class Method that throws IOException");
}

public void m5() throws IOException {
System.out.println("Parent Class Method that throws IOException");
}

public void m6() throws IOException {
System.out.println("Parent Class Method that throws IOException");
}

public void m7() throws IOException {
System.out.println("Parent Class Method that throws IOException");
}
}
 public class Children extends Parent {
@Override
public void m1() {
System.out.println("Child Class Method");
}

/* Prompts Compile Time Exception */
/*
@Override
public void m2() throws Exception {
System.out.println("Child Class Method that throws Exception");
}
*/

@Override
public void m3() throws IOException {
System.out.println("Child Class Method that throws IOException");
}

/* Prompts Compile Time Exception */
/*
@Override
public void m4() throws Exception {
System.out.println("Child Class Method that throws Exception");
}
*/

@Override
public void m5() throws FileNotFoundException, EOFException {
System.out.println("Child Class Method that throws FileNotFoundException and EOFException");
}

/* Prompts Compile Time Exception */
/*
@Override
public void m6() throws EOFException, InterruptedException {
System.out.println("Child Class Method that throws EOFException");
}
*/

@Override
public void m7() throws ArithmeticException, NullPointerException, ClassCastException {
System.out.println("Child Class Method that throws, ArithmeticException, NullPointerException, ClassCastException");
}
}
public class OverridingMain {
public static void main(String[] args) {
/* For m1 */
Parent p11 = new Parent();
try {
p11.m1();
} catch (Exception e) {
e.printStackTrace();
}
Parent p12 = new Children();
try {
p12.m1();
} catch (Exception e) {
e.printStackTrace();
}
Children c1 = new Children();
c1.m1();
/* End */

/* For m2*/
Parent p21 = new Parent();
p21.m2();

Parent p22 = new Children();
p22.m2();

Children c21 = new Children();
c21.m2();
/* End */

/* For m3 */
Parent p31 = new Parent();
try {
p31.m3();
} catch (Exception e) {
e.printStackTrace();
}

Parent p32 = new Children();
try {
p32.m3();
} catch (Exception e) {
e.printStackTrace();
}

Children c31 = new Children();
try {
c31.m3();
} catch (IOException e) {
e.printStackTrace();
}
/* End */

/* For m4 */
Parent p41 = new Parent();
try {
p41.m4();
} catch (IOException e) {
e.printStackTrace();
}

Parent p42 = new Children();
try {
p42.m4();
} catch (IOException e) {
e.printStackTrace();
}

Children c41 = new Children();
try {
c41.m4();
} catch (IOException e) {
e.printStackTrace();
}
/* End */

/* For m5 */
Parent p51 = new Parent();
try {
p51.m5();
} catch (IOException e) {
e.printStackTrace();
}

Parent p52 = new Children();
try {
p52.m5();
} catch (IOException e) {
e.printStackTrace();
}

Children c51 = new Children();
try {
c51.m5();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (EOFException e) {
e.printStackTrace();
}
/* End */

/* For m6 */
Parent p61 = new Parent();
try {
p61.m6();
} catch (IOException e) {
e.printStackTrace();
}

Parent p62 = new Children();
try {
p62.m6();
} catch (IOException e) {
e.printStackTrace();
}
/* End */

/* For m7 */
Parent p71 = new Parent();
try {
p71.m7();
} catch (IOException e) {
e.printStackTrace();
}

Parent p72 = new Children();
try {
p72.m7();
} catch (IOException e) {
e.printStackTrace();
}

Children c71 = new Children();
c71.m7();
/* End */
}
}
 Parent Class Method that throws Exception
 Child Class Method
 Child Class Method
 Parent Class Method
 Parent Class Method
 Parent Class Method
 Parent Class Method that throws Exception
 Child Class Method that throws IOException
 Child Class Method that throws IOException
 Parent Class Method that throws IOException
 Parent Class Method that throws IOException
 Parent Class Method that throws IOException
 Parent Class Method that throws IOException
 Child Class Method that throws FileNotFoundException and EOFException
 Child Class Method that throws FileNotFoundException and EOFException
 Parent Class Method that throws IOException
 Parent Class Method that throws IOException
 Parent Class Method that throws IOException
 Child Class Method that throws, ArithmeticException, NullPointerException, ClassCastException
 Child Class Method that throws, ArithmeticException, NullPointerException, ClassCastException
      • Overriding with respect to static methods.
        • We can't override a static method as non-static otherwise we will get compile time error because static methods are Class level methods and non-static methods are Object level methods.
        • Similarly we can't override a non-static method as static in Child class. 
 public class Parent {
public static void m1() {
System.out.println("Parent Class Method with Static keyword");
}
}
 public class Children extends Parent {

/* You can't override a static method of Parent Class */
/*
@Override
public void m1() {
System.out.println("Child Class Method with Static keyword");
}
*/

/* You can't use static keyword on the method that you inherited from Parent Class */
/*
@Override
public static void m2() {
System.out.println("Child Class Method with Static Keyword");
}
*/
}
        • If both Parent class method and Child class method is static then it is a valid code we won't get any compile time error but it is not called overriding. This technique is used for Method Hiding.
 public class Parent {
/* This is a class level method */
public static void m1() {
System.out.println("Parent Class Method with Static Keyword");
}
}
 public class Children extends Parent {
/* In the technique of Method Hiding, you can't use the annotation @Override */
// @Override
/* This is a class level method */
public static void m1() {
System.out.println("Child Class Method with Static keyword");
}
}
 public class OverridingMain {
public static void main(String[] args) {
Parent.m1();
Children.m1();
}
}
        • Method Hiding is always takes care by Compiler based on reference type.
 public class Parent {
/* This is a class level method */
public static void m1() {
System.out.println("Parent Class Method with Static Keyword");
}
}
 public class Children extends Parent {
/* In the technique of Method Hiding, you can't use the annotation @Override */
// @Override
/* This is a class level method */
public static void m1() {
System.out.println("Child Class Method with Static keyword");
}
}
 public class OverridingMain {
public static void main(String[] args) {
Parent.m1();
Children.m1();

Parent p = new Parent();
p.m1();

Children c = new Children();
c.m1();

Parent p1 = new Children();
p1.m1();
}
}
 Parent Class Method with Static Keyword
 Child Class Method with Static keyword
 Parent Class Method with Static Keyword
 Child Class Method with Static keyword
 Parent Class Method with Static Keyword
        • All rules of Method Hiding are exactly same as Overriding except the following differences:
          • Both Parent and Child class method must be static in Method Hiding. Both Parent and Child class method must be non-static in Overriding.
          • Compiler is responsible for method resolution based on reference type in Method Hiding. JVM is always responsible for method resolution based on runtime object. It is also known as Compile Time Polymorphism or Static Polymorphism or Early Binding. Overriding are also known as Runtime Polymorphism or Dynamic Polymorphism or Late Binding. 
      • Overriding with respect to var-arg methods:
        • We can override var-arg method with another var-arg method only. If we are trying to override with normal method then it will become Overloading but not Overriding. 
 public class Parent {
/* This is an instance level method */
public void m1(int... x) {
System.out.println("Parent Class Method var-arg type arguments");
}
}
 public class Children extends Parent {
/* This is an example of Overloading but not Overriding */
/* This is an instance level method */
public void m1(int x) {
System.out.println("Child class method with int type argument.");
}
}
 public class OverridingMain {
public static void main(String[] args) {

Parent p = new Parent();
p.m1(10);

Children c = new Children();
c.m1(10);

Parent p1 = new Children();
p1.m1(10);
}
}
 Parent Class Method var-arg type arguments
 Child class method with int type argument
 Parent Class Method var-arg type arguments
        • In the above program if we replace child method with var-arg method then it will become Overriding.
      • Overriding with respect to variables
        • Variable resolution always takes care by Compiler based on reference type. Irrespective of whether the variable is static or non-static (Overriding concept applicable only for Methods but not for Variables).
 public class Parent {
String variable = "Parent-Variable";
}
 public class Children extends Parent {
String variable = "Children-Variable";
}
 public class OverridingMain {
public static void main(String[] args) {

Parent p = new Parent();
System.out.println(p.variable);

Children c = new Children();
System.out.println(c.variable);

Parent p1 = new Children();
System.out.println(p1.variable);
}
}
 Parent-Variable
 Children-Variable
 Parent-Variable
      • Differences between Overloading and Overriding
        • Method Names: It must be same for both Overloading and Overriding.
        • Argument Types: It must be different for Overloading (at least order) and it must be same for Overriding (including order).
        • Method Signatures: It must be different for Overloading and it must be same for Overriding. 
        • Return Types: No rules or restrictions for Overloading but it must be same until 1.4 version. From 1.5 version onwards, Co-Variant return types are allowed.
        • private, static, final methods: Methods with such access modifiers can be Overloaded but cannot be Overridden.
        • Access Modifiers: No restrictions in Overloading. The scope of access modifiers cannot be reduced but we can increase in Overriding.
        • Throws Class: No restrictions in Overloading. If Child class method throws any Checked Exception compulsory Parent class method should throw the same Checked Exception or its Parent. But no restrictions for Unchecked Exception.
        • Method Resolution: In Overloading, always takes care by Compiler based on reference type. In Overriding, always takes care by JVM based on runtime object.
        • Also Known As: Overloading is also known as Compile Time Polymorphism, Static Polymorphism or Early Binding. Overriding is also known as Runtime Polymorphism, Dynamic Polymorphism or Late Binding. 
      • Polymorphism
        • One name but multiple forms is the concept of Polymorphism. 
        • e.g.
          • Method name is the same but we can apply for different types of arguments (Overloading).
          • Method signature is the same but in Parent class one type of implementation and in the Child class another type of implementation (Overriding).
          • Usage of Parent reference to hold Child object is a concept of Polymorphism.
 List arrayList = new ArrayList();
List linkedList = new LinkedList();
List vector = new Vector();
List stack = new Stack();
        • Parent class reference can be used to hold Child object but by using that reference we can call only the methods available in Parent class and we can't call Child specific methods. 
        • But by using Child reference, we can call both Parent and Child class methods.
        • When we should go for Parent reference to hold Child object:
          • If we don't know exact runtime type of Object then we should go for Parent reference. 
          • e.g. 
            • The first element present in the ArrayList can be of any type, it may be Student Object / Customer Object / String Object / StringBuffer Object/ hence the return type of get method is Object, which can hold any Object. 
  • Static Control Flow: Whenever we are executing a Java class the following sequence of steps or activities will be executed as the part of Static Control Flow. 
    • Identification of Static Members from top to bottom.
    • Execution of Static Variable Assignments and Static Blocks from top to bottom.
    • Execution of Main Method.
 public class StaticControlFlowMain {
static int i = 10;

static {
m1();
System.out.println("First Static Block");
}

public static void main(String[] args) {
/* You can't declare a static variable inside a static method. */
// static int x = 20;
/* We cannot call a non-static method or variable inside a static method. */
m1();
System.out.println("Main Method");
}

public static void m1() {
/* We cannot call a non-static method or variable inside a static method. */
System.out.println("j: " + j);
}

static {
System.out.println("Second Static Block");
}

static int j = 20;
}
 j: 0
First Static Block
Second Static Block
j: 20
Main Method
    • RIWO (Read Indirectly & Write Only):
      • If we are trying to read a variable inside a Static Block then, that read operation is called Direct read.
      • If we are trying to read a variable inside a Main or Custom Method then, that read operation is called Indirect read.
      • If a variable is just identified by the JVM and the original value is not yet assigned then the variable is said to be in RIWO (Read Indirectly & Write Only) state.
      • If a variable is in RIWO (Read Indirectly & Write Only) state than we can’t perform Direct read i.e. read the variable from Static Block but we can perform Indirect Read.
      • If we are trying to read directly then we will get compile time error saying ‘Illegal forward reference’.
 public class DirectAndIndirectMain {
static int i = 10;

static {
/* This is a Direct Read because i is called within a Static Block */
System.out.println(i);
}

public static void main(String[] args) {
/* This is Indirect Read because i is called within a method (main or other) */
System.out.println(i);
}
}
    • R&W (Read and Write).
    • Static Control Flow in Parent and Child Class:
      • Whenever we are executing Child class the following sequence of events will be executed automatically as the part of Static Control Flow.
        • Identification of Static Members from Parent to Child.
        • Execution of Static Variable Assignments and Static Blocks from Parent to Child.
        • Execution of only Child class Main Method.
      • Whenever we are loading Child class automatically Parent class will be loaded but whenever we are loading Parent class, Child class won’t be loaded (because parent class members by default available to the Child class whereas, Child class members by default won’t available to the Parent).

    • Static Block
      • Static Block will be executed at the time of class loading. Hence at the time of class loading, if we want to perform any activity, we have to define that inside a Static Block.
        • e.g. At the time of Java class loading, the corresponding native libraries must be loaded hence we have to define this activity inside a Static Block.
        • e.g. After loading every database driver class, we have to register driver class with the driver manger but inside database driver class there is a static block to perform this activity and we are not responsible to register this process explicitly. 
        • e.g. Object, System class of Java
        • e.g. Within a class we can declare any number of static blocks but all this static blocks will be executed from top to bottom.
      • Without writing Main Method is it possible to print some statements to the console? 
        • Yes by using static block we can print.
 static {
System.out.println("I can print without main method.");
/* Use this method to stop JVM. */
System.exit(0);
}
      • Without writing Main Method and Static Block is it possible to print some statements to the console?
        • Yes off course, there are multiple ways. Below are some of the examples.
 public class PrintWithConstructor {
static PrintWithConstructor printWithConstructor = new PrintWithConstructor();

PrintWithConstructor() {
System.out.println("I can print");
System.exit(0);
}
}
 public class PrintWithInstanceVariable {
static PrintWithoutStaticBlockAndMain printWithoutStaticBlockAndMain = new PrintWithoutStaticBlockAndMain();

/* This is an Instance Variable. */

{
System.out.println("I can print");
System.exit(0);
}
}
 public class PrintWithoutStaticBlockAndMain {
static int i = m1();

static int m1() {
System.out.println("I can print");
/* Use this method to stop JVM */
System.exit(0);
return 10;
}
}
 public class StaticBlockMain {
static {
System.out.println("I can print without main method.");
/* Use this method to stop JVM. */
System.exit(0);
}
}
      • Note: From 1.7 version onwards Main Method is mandatory to start a program execution hence from 1.7 version onwards it is impossible to print some statements to the console without writing Main Method .
  • Instance Control Flow:
    • Whenever we are executing a Java class first Static Control Flow will be executed. In the Static Control Flow if we are creating an object then the following sequence of events will be executed as a part of Instance Control Flow.
      • Identification of Instance Members from top to bottom.
      • Execution of Instance Variable assignments and Instance Blocks from top to bottom.
      • Execution of Constructor.
      • If we comment the '1' mark in the above program then only the Main Method will be executed.
      • Note: Static Control Flow is one time activity which will be performed at the time of Class loading. But Instance Control Flow is not one time activity and it will be performed for every Object creation.
      • Object creation is the most costly operation. If there is no specific requirement then it is not recommended to create Object.
    • Instance Control Flow in Parent to Child Relationship:
      • Whenever we are creating Child class object the following sequence of events will be performed automatically as the part of Instance Control Flow.
        • Identification of Instance members from Parent to Child.
        • Execution of Instance variable assignments and Instance blocks only in Parent Class.
        • Execution of Parent Constructor.
        • Execution of Instance variable assignments and Instance blocks in Child class.

  • Constructors.
  • Coupling: The degree of dependency between the components is called Coupling. If dependency is more then it is considered as Tightly coupling and if dependency is less then it is considered as Loosely coupling.
    • e.g of Tightly Coupling.
 class D {
public static int m1() {
return 10;
}
}

class C {
static int k = D.m1();
}

class B {
static int j = C.k;
}

class A {
static int i = B.j;
}
 public class CouplingMain {
public static void main(String[] args) {
System.out.println(A.i);
}
}
      • The above components are said to be tightly coupled with each other because dependency between the components is more. 
      • Tightly coupling is not a good programming practice because it has several serious disadvantages. 
        • Without effecting remaining components we can't modify any component and hence it will become difficult.
        • It suppresses reusability.
        • It reduces maintainability of the application.
    • Hence we have to maintain dependency between the components as less as possible. i.e. Loosely Coupling is a good programming coupling. 
  • Cohesion: For every component a clear well defined functionality is defined then that component is said to be follow High Cohesion.
   Image Credit: www.javaiq.in
    • High Cohesion is always a good programming practice because it has several advantages.
      • Without effecting remaining components we can modify any component hence enhancement will become easy.
      • It promotes reusability of the code (wherever validation is required we can reuse the same validate servlet without rewriting).
      • It improves maintainability of the application.
  • Object Type-Casting
    • We can use parent reference to hold child object.
 Object o = new String("Raj Kanchan");
    • We can use Interface reference to hold implemented class object.
 Runnable r = new Thread();
      • Below is the rule for Object Type Casting.
        • Rule 1 (Compile Time Checking 1): The type of 'd' and 'C' must have some relation (Either Child to Parent or Parent to Child or same type) otherwise we will get compile time error saying inconvertible types found 'd' type, required 'C'. e.g.
 Object o = new String("Jane Doe");
StringBuffer sb = (StringBuffer) o;
 String s = new String("Rik Jerry");
StringBuffer sb1 = (StringBuffer) s; // This is invalid
        • Rule 2 (Compile Time Checking 2): 'C' must be either same or derived type of A. Otherwise, we will get compile time error saying incompatible types found 'C' required 'A'. e.g.
 Object o = new String("Jane Doe");
StringBuffer sb = (StringBuffer) o;
 Object o = new String("Jane Doe");
StringBuffer sb = (String) o; // This is invalid
        • Rule 3 (Runtime Checking 3): Runtime object type of 'd' must be either same or derived type of 'C'. Otherwise, we will get runtime exception saying 'ClassCastException'. e.g.
 Object o = new String("Jane Doe");
StringBuffer sb = (StringBuffer) o; // Will get Runtime Exception
  • Type Casting: Strictly speaking through Type Casting, we are not creating any new object. For the existing object we are providing another type of reference variable i.e. we are performing Type Casting but not Object Casting.
    • e.g. 1:
 String s = new String("Jane Doe");
Object obj = (Object) s;
 Integer i = new Integer(10);
Number n1 = (Number) i;
Object obj2 = (Object) n1;
System.out.println(i.equals(n1));
System.out.println(n1.equals(obj2));

/* The above code can be minimized in the following form */
Number n2 = new Integer(10); // First 2 lines
Object obj3 = new Integer(10); // First 3 lines
System.out.println(n2.equals(obj3));
 class Parent {
public void m1() {
System.out.println("Method of Parent Class");
}
}

class Child extends Parent {
public void m2() {
System.out.println("Method of Child Class");
}
}
 public class TypeCastingMain {
public static void main(String[] args) {

Child child = new Child();
child.m1();
child.m2();

((Parent) child).m1();
/* As this is a reference of Parent class, method of Child class is no longer available */
((Parent) child).m2(); // Prompts Compile Time Error.
}
}
      • In the above program, Parent reference can be used to hold Child object but by using that reference we can't call child specific methods. And we can call only the methods available in Parent class. 
    • e.g. 2:
 class A {
public void m1() {
System.out.println("Method of A Class");
}
}

class B extends A {
@Override
public void m1() {
System.out.println("Method of B Class");
}
}

class C extends B {
@Override
public void m1() {
System.out.println("Method of C Class");
}
}
 public class TypeCastingMain {
public static void main(String[] args) {
C c = new C();
((B) c).m1();
((A) ((B) c)).m1();
}
}
 Method of C Class
Method of C Class
 class StaticA {
public static void m1() {
System.out.println("Method of StaticA Class");
}
}

class StaticB extends StaticA {
public static void m1() {
System.out.println("Method of StaticB class");
}
}

class StaticC extends StaticB {
public static void m1() {
System.out.println("Method of StaticC class");
}
}
public class TypeCastingMain {
public static void main(String[] args) {
StaticC staticC = new StaticC();
staticC.m1();
((StaticB) staticC).m1();
((StaticA) ((StaticB) staticC)).m1();
}
}
 Method of StaticC class
Method of StaticB class
Method of StaticA Class
      • The above program shows Method Hiding and method resolution is always based on resolution type. 
 class A {
int x = 777;
}

class B extends A {
int x = 888;
}

class C extends B {
int x = 999;
}
 public class TypeCastingMain {
public static void main(String[] args) {
C c = new C();

System.out.println(c.x);
System.out.println(((B) c).x);
System.out.println(((A) ((B) c)).x);

}
}
 999
888
777
      • Variable resolution is always based on reference type but not based on runtime object.

Do's and Dont's

  • It is highly recommended to declare data member (variable) as private.
  • For security purpose you should use Interface and Abstract classes for Abstraction.
  • Encapsulation = Data Binding + Abstraction.
  • If a Parent class is not Tightly Encapsulated then no Child class is Tightly Encapsulated.
  • Parent reference can be used to hold child object but child reference cannot be used to hold parent object.
 Person doctor1 = new Doctor("Joe Dane", 24, 56000.0, "Apollo Hospital");
// Doctor doctor3 = new Person("Kate Upton", 26, 95000.0);
  • If our class doesn't extend any other class then only our class is direct Child class of Object and if our class extend any other class then our class is an indirect Child class of Object.
  • Strictly speaking through Interfaces we won't get any Multiple Inheritance. 
  • Cyclic Inheritance is not allowed in Java because it is not required.
  • In Composition objects are strongly associated whereas in Aggregation, objects are weakly associated. 
  • In Composition container object holds directly contained objects whereas in Aggregation, container object holds just references of contained objects. 
  • Do not declare two methods with the same signatures in a Class, even though their return types are different. 
  • In Overloading, method resolution (which methods has to execute) always takes care by Compiler based on reference type object. Hence Overloading is also considered as Compile Time Polymorphism or Static Polymorphism or Early Binding.
  • In Overriding, method resolution (which methods has to execute) always takes care by JVM based on runtime object. Hence Overriding is also considered as Runtime Polymorphism or Dynamic Polymorphism or Late Binding.
  • In Overloading, we have to check only method names (must be same) and argument types (must be different). We are not required to check remaining like return types, access modifiers etc.
  • But in Overriding, we have to check everything. Like methods names, argument types, return types, access modifiers, Throws Class, etc.
  • Loosely Coupling and High Cohesion are good programming practices.
  • We can't declare a static variable inside a static method.
  • We can't call a non-static method or variable inside a static method.
  • Static Control Flow is one time activity which will be performed at the time of Class loading. But Instance Control Flow is not one time activity and it will be performed for every Object creation. 

Reference

Comments