Java OOPs concepts Crash Course

Java OOPs concepts Crash Course

Covered all possible topics for Java OOPs concepts.

·

32 min read

Introduction and basic Concepts - Class, Objects, Constructors, Keywords

Class and Objects

  • A class is the name for a type whose values are objects.
  • Class is basically like a named group of properties of functions and variables. It is also a template for an Object. For example,

    class Box {
      int width,height; // variables property
    
    // function property
    int area(){
      return width * height;
    }
    
    }
    
  • Objects are entities that store data and can take actions.

  • Object is an instance of a class. When you declare an object of a class, you are creating an instance of that class. Thus, a class is a logical construct ( template ). An object has physical reality. (That is, an object occupies space in memory.)
  • Objects are characterized by three essential properties: state, identity, and behavior. The state of an object is a value from its data type. The identity of an object distinguishes one object from another. It is useful to think of an object’s identity as the place where its value is stored in memory. The behavior of an object is the effect of data-type operations. Behavior also includes actions. The actions that an object can take are called methods.
  • For the creation of an object from a class, we use the 'new' keyword. The 'new' keyword dynamically allocates(that is, allocates at runtime ) memory for an object & returns a reference to it. This reference is, more or less, the address in memory of the object allocated by new. This reference is then stored in the variable. Thus, in Java, all class objects must be dynamically allocated.
  • Here is the syntax for object creation. Below, class-var is a variable of the class type being created. The class name is the name of the class that is being instantiated. The class name followed by parentheses specifies the constructor for the class. A constructor defines what occurs when an object of a class is created. You will see about contructor after this topic. so, do not worry!

    // New Keyword 
    classname class-var = new classname ( );
    
  • Let's take an example of object creation.

    class Student {
      String studentName; // variable
    
      String getName() {
        return studentName;
      }
    
    }
    
    // Syntax for creating object
    classname class-var = new classname ( );
    
    // now to create Object
    Student s = new Student();
    /* Here Left side portion (Student s) will initialize at compile-time and the right side portion with a new keyword means an object created in Heap memory in runtime (dynamically). */
    
    Student s2; // declare a reference to an object
    s2 = new Student(); // allocate a Student object to s2 reference
    
  • To use instance variables or functions, we use the dot operator. The dot operator links the name of the object with the name of an instance variable. Although commonly referred to as the dot operator, the formal specification for Java categorizes the . as a separator. For example,

    s.getName(); 
    // Here we used dot between the name of an object which is s and instance members which is getName method.
    

the new operator

  • The new operator is used to create an object for the class and associate the object with the variable, that names it.
  • You might be wondering why you do not need to use new for such things as integers or characters. The answer is that Java’s primitive types are not implemented as objects. Rather, they are implemented as “normal” variables. This is done in the interest of efficiency.
  • It is important to understand that new allocates memory for an object during run time. Take the below example Here, b1 and b2 will both refer to the same object. The assignment of b1 to b2 did not allocate any memory or copy any part of the original object. It simply makes b2 refer to the same object as does b1. Thus, any changes made to the object through b2 will affect the object to which b1 is referring since they are the same object. When you assign one object reference variable to another object reference variable, you are not creating a copy of the object, you are only making a copy of the reference.

    Box b1 = new Box();
    Box b2 = b1;
    
  • Let's talk about methods ( or functions ), commonly there are two types of methods of a class: methods that return (compute) some value and methods that perform an action other than returning a value. For a format, A parameter is a variable defined by a method that receives a value when the method is called. In the below example, in square( int i), i is a parameter. An argument is a value that is passed to a method when it is invoked. For example, square(100) passes 100 as an argument. Inside square( ), the parameter i receives that value.

    // Method Explanation
    int square(int i){
        return i * i;
    }
    
  • This is an important point so please remember this. For example, Bus bus = new Bus(); Left-hand side (reference i.e. bus) is looked by compiler and right-hand side (object i.e. new Bus()) is looked at by JVM.

Constructors

  • A constructor is a special variety of method that is used to initialize objects.
  • A constructor must have the same name as the class to which it belongs.
  • Once defined, the constructor is automatically called when the object is created, before the new operator completes. Constructors look a little strange because they have no return type, not even void. This is because the implicit return type of a class constructor is the class type itself.
  • For example,

    class Student {
      String name;
    
      Student(){
          name = "Meet";
      }
    
      String getName(){
          return name; 
      }
    }
    
    // now create an object and check
    Student s = new Student();
    s.getName(); // Meet
    
  • A constructor that takes no arguments is called a no-argument constructor or no-arg constructor. If you define a class and include absolutely no constructors of any kind, then a no-argument constructor is automatically created. However, If you include one or more constructors that each takes one or more arguments, but you do not include a no-argument constructor in your class definition, then there is not a no-argument constructor.

this keyword

  • Sometimes a method will need to refer to the object that invoked it. To allow this, Java defines this keyword. this can be used inside any method to refer to the current object. That is, this is always a reference to the object on which the method was invoked.

    class Student {
      String name;
    
      Student(){
          name = "Meet";
      }
    
      String setName(String name){
          this.name = name; 
      }
    
      String getName(){
          return name; 
       }
     }
    
    // now create an object and check
    Student s = new Student();
    s.setName("MSD"); // here this of this method points to s object and changes name
    s.getName(); // MSD
    

Wraper class

  • It is used to convert primitives into class types. So, it could use all methods and properties of the class.

    int a = 10 // primitive type, not wrapper class
    Integer a = 10; // Wrapper class
    

Final keyword

  • A field can be declared as final. Doing so prevents its contents from being modified, making it, essentially, a constant. This means that you must initialize a final field when it is declared.
  • It is a common coding convention to choose all uppercase identifiers for final fields: final int FILE_OPEN=true
  • Unfortunately, final guarantees immutability only when instance variables are primitive types, not reference types. If an instance variable of a reference type has the final modifier, the value of that instance variable (the reference to an object) will never change—it will always refer to the same object—but the value of the object itself can change.

    final int a = 10;
    a=20; // Gives error bcz of final keyword
    
    // if you passed ref variable of final object (Integer class has final variables but in swap it creates new object) and if you change value then it would not point to other value.
    void swap (Integer a,Integer b){
        Integer t = a;
        a=b;
        b=a;
    }
    
    Integer a = 20;
    Integer b = 30;
    
    swap(a,b)
    
    // Here it will not swap because Integer is a final class.
    

The finalize() Method

  • Sometimes an object will need to perform some action when it is destroyed. To handle such situations, Java provides a mechanism called finalization. By using finalization, you can define specific actions that will occur when an object is just about to be reclaimed by the garbage collector. To add a finalizer to a class, you simply define the finalize( ) method. The Java run time calls that method whenever it is about to recycle an object of that class. Right before an asset is freed, the Java run time calls the finalize( ) method on the object.

    protected void finalize(){
        // finalization code here
    }
    

Package, Static, Singleton class

Package

  • A package is Java’s way of forming a library of classes. You can make a package from a group of classes and then use the package of classes in any other class or program you write without the need to move the classes to the directory (folder) in which you are working. All you need to do is include an import statement that names the package.

  • Packages are containers for classes. They are used to keep the class namespace compartmentalized. For example, a package allows you to create a class named List, which you can store in your own package without concern that it will collide with some other class named List stored elsewhere. Packages are stored in a hierarchical manner and are explicitly imported into new class definitions.

  • The package is both a naming and a visibility control mechanism.
  • The following statement creates a package called MyPackage: package MyPackage;.
  • Java uses file system directories to store packages. For example, the .class files for any classes you declare to be part of MyPackage must be stored in a directory called MyPackage. Remember that case is significant, and the directory name must match the package name exactly.
  • A package hierarchy must be reflected in the file system of your Java development system. For example, a package declared as package java.awt.image; needs to be stored in java\awt\image in a Windows environment. Be sure to choose your package names carefully. You cannot rename a package without renaming the directory in which the classes are stored.
  • When you use import statement then remember that Subdirectories are not automatically imported. Suppose you have two packages, utilities.numericstuff and utilities. numericstuff.statistical. In this case, you know that utilities. numericstuff.statistical is in a subdirectory (subfolder) of the directory (folder) containing utilities.numericstuff. This leads some programmers to assume that the following import statement imports both packages:

    import utilities.numericstuff.*;
    
  • This is not true. When you import an entire package, you do not import subdirectory packages. To import all classes in both of these packages, you need below lines

    import utilities.numericstuff.*;
    import utilities.numericstuff.statistical.*;
    
  • How does the Java run-time system know where to look for packages that you create? The answer has three parts.
    1. By default, the Java run-time system uses the current working directory as its starting point. Thus, if your package is in a subdirectory of the current directory, it will be found.
    2. You can specify a directory path or paths by setting the CLASSPATH environmental variable.
    3. you can use the -classpath option with java and javac to specify the path to your classes.
  • When a package is imported, only those items within the package declared as public will be available to non-subclasses in the importing code.

Static keyword

  • When a member is declared static, it can be accessed before any objects of its class are created, and without reference to any object. You can declare both methods and variables to be static.
  • The most common example of a static member is main( ). main( ) is declared as static because it must be accessible for an application to run before any instantiation takes place. Static method in Java is a method that belongs to the class and not to the object.
  • A static method can access only static data. It cannot access non-static data (instance variables) A non-static member belongs to an instance. It's meaningless without somehow resolving which instance of a class you are talking about. In a static context, you don't have an instance, that's why you can't access a non-static member without explicitly mentioning an object reference.
  • In fact, you can access a non-static member in a static context by specifying the object reference explicitly.

    public class Human {
    
        String message = "Hello World";
    
        public static void display(Human human){
            System.out.println(human.message);
        }
    
        public static void main(String[] args) {
            Human kunal = new Human();
            kunal.message = "Kunal's message";
            Human.display(kunal);
        }
    
    }
    
  • A static method can call only other static methods and cannot call a non-static method from it.

  • A static method can be accessed directly by the class name and doesn’t need any object.
  • A static method cannot refer to "this" or "super" keywords in any way.
  • If you need to do the computation in order to initialize your static variables, you can declare a static block that gets executed exactly once, when the class is first loaded.
  • In the below example, first, static variables initialize and then static block run which means a would-be 3 and b would-be 12.
    // Demonstrate static variables, methods, and blocks.
         class UseStatic {
           static int a = 3;
           static int b;
           static void meth(int x) {
             System.out.println("x = " + x);
             System.out.println("a = " + a);
             System.out.println("b = " + b);
           }
           static {
             System.out.println("Static block initialized.");
             b = a * 4;
            }
           public static void main(String args[]) {
             meth(42);
        }
    }

    // Output
    // Static block initialized.
    // x = 42
    // a = 3
    // b = 12
  • For a class, an only nested class can be static. Moreover, static inner classes can have static variables and functions.
  • Static methods are class-level methods, so it is always resolved during compile time. As a result, you cannot override the inherited static methods as override take place at run time not compile time.
  • Static INTERFACE METHODS are not inherited by either an implementing class or a sub-interface.
  • Example
  public class StaticExp {


      static class Test{
          String name;

          public Test(String name) {
              this.name = name;
          }
      }

      public static void main(String[] args) {
          Test a = new Test("Kunal");

          Test b = new Test("Rahul");

          System.out.println(a.name); // Kunal
          System.out.println(b.name); // Rahul
      }
  }

Singleton Class

  • Singleton class allow only one instance to be created.
  • Here is the example,
     public class SingletonDemo {

      public static void main(String[] args) {

          Singleton obj1 = Singleton.getInstance();

          Singleton obj2 = Singleton.getInstance();

          Singleton obj3 = Singleton.getInstance();

          // All objects have the same instance

      }
  }

  class Singleton{

      // To only allow one object created, make constructor private
      private Singleton(){

      }

          // Make static so this only one instance could use via class name
      private static Singleton instance;


      public static Singleton getInstance(){
          // check whether 1 obj only created or not
          if(instance==null){
              instance = new Singleton();
          }

          return instance;
      }
  }

Core Principles - Inheritance, Polymorphism, Encapsulation, Abstraction

Encapsulation

  • First understand Information hiding. Information hiding means that you separate the description of how to use a class from the implementation details.
  • Information hiding is a way of avoiding information overloading.
  • Encapsulation means that the data and the actions are combined into a single item (in our case, a class object) and that the details of the implementation are hidden. The terms information hiding and encapsulation deal with the same general principle: If a class is well designed, a programmer who uses a class need not know all the details of the implementation of the class but need only know a much simpler description of how to use the class.
  • It means wrapping up the data members and methods in the class.
  • It basically hides all the data and operations (methods) into a single entity so that can be protected from the outside world. It is a subprocess of Abstraction.

Access Modifiers

  • It is used to achieve data hiding and encapsulation.
  • It is considered good programming practice to make all instance variables private.
  • Once you label an instance variable as private, there is then no way to change its value (nor to reference the instance variable in any other way) except by using one of the methods belonging to the class.
  • Normal good programming practices require that all instance variables be private and that typically, most methods be public.

Untitled.png

  • When to use which?
    • Private: It is used for sensitive data that you don’t want to provide direct access to. Access is via some public methods.
    • No modifier ( Default ): It is used when the user doesn’t want to provide direct access to the outside current package.
    • Protected: It is used when to provide direct access to an outside package if only its subclass. Here take note that in different packages it's only able to access it when the user accesses it via subclass object.
    • Public: It is used when users want to access it anywhere. It's very dangerous for members.

Inheritance

  • Inheritance is kind of like creating a parent-child relationship where the child class objects acquire (inherit) all properties and behaviors (methods) of the parent class object.
  • To inherit a class, you simply incorporate the definition of one class into another by using the extends keyword. For Example, class subclass-name (or child class name) extends superclass-name (or parent class name) { // body of class }
  • You can only specify one superclass for any subclass that you create. Java does not support the inheritance of multiple superclasses into a single subclass. You can, as stated, create a hierarchy of inheritance in which a subclass becomes a superclass of another subclass. However, no class can be a superclass in itself.
  • Although a subclass includes all of the members of its superclass, it cannot access those members of the superclass that have been declared as private.
  • Before we go ahead, just go through this example as it explains what is a reference and an actual object. As it plays a crucial role to understand parent and child objects and a reference relationship.

    Parent p = new ChildClass();
    // Here LHS = Parent P = Reference type
    // Here RHS = new ChildClass() = actual object that reference points to
    
  • A Superclass Variable Can Reference a Subclass Object. But, a Subclass variable cannot Reference Superclass Object.

    • It is important to understand that it is the type of the reference variable—not the type of the object that it refers to—that determines what members can be accessed.
    • When a reference to a subclass object is assigned to a superclass reference variable, you will have access only to those parts of the object defined by the superclass.
    plainbox      =  weightbox;
    (superclass)     (subclass)
    
    SUPERCLASS ref = new SUBCLASS();    // HERE ref can only access methods which are available in SUPERCLASS
    
  • Use of Super keyword

    • Whenever a subclass needs to refer to its immediate superclass, it can do so by use of the keyword super. super has two general forms. The first calls the superclass’ constructor. The second is used to access a member of the superclass that has been hidden by a member of a subclass.
    • super( ) always refers to the superclass immediately above the calling class. This is true even in a multileveled hierarchy.
    • In the below example, notice that super() is passed an object of type BoxWeight—not of type Box. This still invokes the constructor Box(Box ob). As a superclass variable can be used to reference any object derived from that class. Thus, we are able to pass a BoxWeight object to the Box constructor. Of course, Box only has knowledge of its own members.

      class Box {
           private double width;
           private double height;
           private double depth;
      
           // construct clone of an object
      
           Box(Box ob) { // pass object to constructor
             width = ob.width;
             height = ob.height;
             depth = ob.depth;
           }
      }
      
      class BoxWeight extends Box {
           double weight; // weight of box
      
           // construct clone of an object
      
           BoxWeight(BoxWeight ob) { // pass object to constructor
              super(ob);
              weight = ob.weight;
           }
      }
      
    • super( ) always refers to the constructor in the closest superclass. The super( ) in BoxPrice calls the constructor in BoxWeight. The super( ) in BoxWeight calls the constructor in Box. In a class hierarchy, if a superclass constructor requires parameters, then all subclasses must pass those parameters “up the line.” This is true whether or not a subclass needs parameters of its own.

    • If super( ) is not used in subclass' constructor, then the default or parameterless constructor of each superclass will be executed.
  • If you think about it, it makes sense that constructors complete their execution in order of derivation. Because a superclass has no knowledge of any subclass, any initialization it needs to perform is separate from and possibly prerequisite to any initialization performed by the subclass. Therefore, it must complete its execution first.
    • The second form of super acts somewhat like this, except that it always refers to the superclass of the subclass in which it is used.
    • For Example, super.member. Here, member can be either a method or an instance variable. This second form of super is most applicable to situations in which member names of a subclass hide members by the same name in the superclass.

Polymorphism

  • It means the act of representing the same thing in multiple ways.
  • Polymorphism does not apply to instance variables.
  • There are 2 types of Polymorphism.

  • Compile time / Static Polymorphism - Achieved via method overloading

    • It only happens in the same class, not in Inheritance.
    • In Java, it is possible to define two or more methods within the same class that share the same name, as long as their parameter declarations are different ( which is also called Method Signature).
    • While overloaded methods may have different return types, the return type alone is insufficient to distinguish two versions of a method. When Java encounters a call to an overloaded method, it simply executes the version of the method whose parameters match the arguments used in the call.
    • In some cases, Java’s automatic type conversions can play a role in overload resolution.
    • In the below example, As you can see, this version of OverloadDemo does not define test(int). Therefore, when test( ) is called with an integer argument inside Overload, no matching method is found. However, Java can automatically convert an integer into a double, and this conversion can be used to resolve the call. Therefore, after test(int) is not found, Java elevates I to double and then calls test(double). Of course, if test(int) had been defined, it would have been called instead. Java will employ its automatic type conversions only if no exact match is found.

      class OverloadDemo {
         void test(double a){
             System.out.println("Inside test(double) a: " + a);
         }
      }
      class Overload {
          public static void main(String args[]) {
              OverloadDemo ob = new OverloadDemo();
              int i = 88;
              ob.test(i);        // this will invoke test(double) bcz of automatic type conversion
              ob.test(123.2);    // this will invoke test(double)
          }
      }
      
    • Here is the example of returning an object. As you can see, each time incrByTen( ) is invoked, a new object is created, and a reference to it is returned to the calling routine. Since all objects are dynamically allocated using new, you don’t need to worry about an object going out of scope because the method in which it was created terminates. The object will continue to exist as long as there is a reference to it somewhere in your program. When there are no references to it, the object will be reclaimed the next time garbage collection takes place.

      // Returning an object.
      class Test {
        int a;
          Test(int i) {
              a = i;
         }
         Test incrByTen() {
             Test temp = new Test(a+10);
             return temp;
         }
      }
      class RetOb {
      public static void main(String args[]) {
        Test ob1 = new Test(2);
        Test ob2;
        ob2 = ob1.incrByTen();
        System.out.println("ob1.a: " + ob1.a);
        System.out.println("ob2.a: " + ob2.a);
      }
      }
      
      // Output:
      // ob1.a: 2
      // ob2.a: 12
      
  • Runtime / Dynamic Polymorphism - Achieved via method overriding

    • In a class hierarchy, when a method in a subclass has the same name and type signature as a method in its superclass, then the method in the subclass is said to override the method in the superclass. When an overridden method is called from within its subclass, it will always refer to the version of that method defined by the subclass. The version of the method defined by the superclass will be hidden.
    • Method overriding occurs only when the names and the type signatures of the two methods are identical. If they are not, then the two methods are simply overloaded.
    • Although static methods can be inherited, there is no point in overriding them in child classes because the method in the parent class will run always no matter from which object you call it. That is why static interface methods cannot be inherited because these methods will run from the parent interface and no matter if we were allowed to override them, they will always run the method in parent interface. That is why static interface method must have a body.
    • Now, let's take the below example where both parent and child contain the same method. Here, which method will be called depending on an assigned object (in this case child method will call). This is known as Upcasting.

      public class Main {
      
         public static void main(String[] args) {
             Shape s = new Circle();
             s.printMe(); // Hello from circle
         }
      }
      
      class Shape {
      
         public void printMe(){
             System.out.println("Hello from Shapes");
         }
      }
      
      class Circle extends Shape {
      
         public void printMe() {
             System.out.println("Hello from Circle");
         }
      }
      
    • Dynamic method dispatch - Resolves the method overriding

      • It is the mechanism by which a call to an overridden method is resolved at run time, rather than compile time. Dynamic method dispatch is important because this is how Java implements run-time polymorphism. Let’s begin by restating an important principle: a superclass reference variable can refer to a subclass object. When an overridden method is called through a superclass reference, Java determines which version of that method to execute based upon the type of the object being referred to at the time the call occurs. Thus, this determination is made at run time.
      • In other words, it is the type of the object being referred to (not the type of the reference variable) that determines which version of an overridden method will be executed.

Using final with Inheritance

  • It can be used to create the equivalent of a named constant.
  • Using final to Prevent Overriding of methods
    • To disallow a method from being overridden, specify final as a modifier at the start of its declaration. Methods declared as final cannot be overridden.
    • Methods declared as final can sometimes provide a performance enhancement: The compiler is free to inline calls to them because it “knows” they will not be overridden by a subclass. When a small final method is called, often the Java compiler can copy the bytecode for the subroutine directly in line with the compiled code of the calling method, thus eliminating the costly overhead associated with a method call. Inlining is an option only with final methods. Normally, Java resolves calls to methods dynamically, at run time. This is called late binding. However, since final methods cannot be overridden, a call to one can be resolved at compile time. This is called early binding.
  • Using final to Prevent Inheritance of class
    • Sometimes you will want to prevent a class from being inherited. To do this, precede the class declaration with the final.
    • Declaring a class as final implicitly declares all of its methods as final, too.
    • As you might expect, it is illegal to declare a class as both abstract and final since an abstract class is incomplete by itself & relies upon its subclasses to provide complete implementations.

Abstraction

  • It means hiding unnecessary details and showing valuable information.
  • Data hiding is achieved via abstraction.

Abstract Class

  • Sometimes you will want to create a superclass that only defines a generalized form that will be shared by all of its subclasses, leaving it to each subclass to fill in the details. Such a class determines the nature of the methods that the subclasses must implement.
  • You may have methods that must be overridden by the subclass in order for the subclass to have any meaning. In this case, you want some way to ensure that a subclass does, indeed, override all necessary methods. Java’s solution to this problem is the abstract method.
  • An abstract method serves as a placeholder for a method that will be fully defined in a descendent class. An abstract method has a complete method heading with the addition of the modifier abstract. It has no method body but does end with a semicolon in place of a method body. An abstract method cannot be private. For example abstract type name(parameter-list);
  • These methods are sometimes referred to as subclass's responsibility because they have no implementation specified in the superclass. Thus, a subclass must override them—it cannot simply use the version defined in the superclass.
  • A class that has at least one abstract method is called an abstract class and, in Java, must have the modifier abstract added to the class heading.
  • If a derived class of an abstract class does not give full definitions to all the abstract methods, or if the derived class adds an abstract method, then the derived class is also an abstract class and must include the modifier abstract in its heading.
  • In contrast with the term abstract class, a class with no abstract methods is called a concrete class.
  • Although abstract classes cannot be used to instantiate objects, they can be used to create object references, because Java’s approach to run-time polymorphism is implemented through the use of superclass references. For example,

      public class PracticeAbstract {
    
          public static void main(String[] args) {
    
              Circle c = new Circle(2);
              System.out.println(c.calculateArea());
    
              // Can use abstract class reference with subclass object
              Figure f = new Circle(2);
              System.out.println(f.calculateArea());
          }
      }
    
      abstract class Figure {
    
          public Figure()
          {
              System.out.println("Figure class constructor called");
          }
    
          public abstract int calculateArea();
      }
    
      class Circle extends Figure {
          int r;
    
          public Circle(int r)
          {
              this.r=r;
          }
    
          public Circle()
          {
    
          }
    
          public int calculateArea()
          {
              return (int)3.14 * r * r;
          }
      }
    
  • There can be no objects of an abstract class.

  • You cannot declare abstract constructors or abstract static methods.
  • You can declare static methods in abstract class. Because there can be no objects for abstract class. If they had allowed calling abstract static methods, it would mean we are calling an empty method (abstract) through class name because it is static.
  • Abstract classes can include as much implementation as they see fit i.e.there can be concrete methods(methods with the body) in abstract classes.
  • A public constructor on an abstract class doesn't make any sense because you can't instantiate an abstract class directly (can only instantiate through a derived type that itself is not marked as abstract).

Interface

  • An interface is something like the extreme case of an abstract class. An interface is not a class. It is, however, a type that can be satisfied by any class that implements the interface An interface is a property of a class that says what methods it must have.
  • By default functions are public and abstract without a body in interface and variables are final and static, public by default in interface. Variables must be initialized. Also, the type signature of the implementing method must match exactly the type signature specified in the interface definition.
  • Using the keyword interface, you can fully abstract a class interface from its implementation. That is, using interface, you can specify what a class must do, but not how it does it.
  • By providing the interface keyword, Java allows you to fully utilize the “one interface, multiple methods” aspect of polymorphism.
  • Key difference between a class and an interface: a class can maintain state information (especially through the use of instance variables), but an interface cannot.
  • Using interface, you can specify a set of methods that can be implemented by one or more classes. Although they are similar to abstract classes, interfaces have an additional capability: A class can implement more than one interface. By contrast, a class can only inherit a single superclass (abstract or otherwise).
  • A class can only implement more than one interface if only interfaces are consistent. For example

    interface Box1 {
        // default is static and final
        int no =5;
    
        public int getStuff();
    }
    
    interface Box2 {
        int no=6;
    
        public String getStuff();
    }
    
    class SubBox implements Box1,Box2 {
    
        public SubBox(){
            // Illegal
    //        System.out.println(no);
    
            // Legal
            System.out.println(Box1.no);
        }
    
        // Illegal - it clashes with Box2 and Box1 interface method
        public int getStuff(){
            return 1;
        }
    }
    
  • Interfaces are designed to support dynamic method resolution at run time. Normally, in order for a method to be called from one class to another, both classes need to be present at compile time so the Java compiler can check to ensure that the method signatures are compatible. This requirement by itself makes for a static and nonextensible classing environment. Inevitably in a system like this, functionality gets pushed up higher and higher in the class hierarchy so that the mechanisms will be available to more and more subclasses. Interfaces are designed to avoid this problem. They disconnect the definition of a method or set of methods from the inheritance hierarchy. Since interfaces are in a different hierarchy from classes, it is possible for classes that are unrelated in terms of the class hierarchy to implement the same interface. This is where the real power of interfaces is realized.

  • Beginning with JDK 8, it is possible to add a default implementation to an interface method. Thus, it is now possible for interface to specify some behavior.However, default methods constitute what is, in essence, a special-use feature, and the original intent behind interface still remains.
  • You can declare variables as object references that use an interface rather than a class type. This process is similar to using a superclass reference to access a subclass object. Any instance of any class that implements the declared interface can be referred to by such a variable. When you call a method through one of these references, the correct version will be called based on the actual instance of the interface being referred to. Called at run time by the type of object it refers to. The method to be executed is looked up dynamically at run time, allowing classes to be created later than the code which calls methods on them. The calling code can dispatch through an interface without having to know anything about the “callee.”

public class PracticeInterface {
    public static void main(String[] args) {

        SubBox sb = new SubBox();

        // this is using reference of interface and object of implemented class = Legal / 
        valid
        Box1 b = new SubBox();
        System.out.println(b.getStuff());

    }
}

interface Box1 {
    // default is static and final
    int no =5;

    int getStuff();
}


class SubBox implements Box1 {

    public SubBox(){

    }

    // Illegal - it clashes with Box2 and Box1 interface method
    public int getStuff(){
        return no;
    }
}
  • CAUTION: Because dynamic lookup of a method at run time incurs a significant overhead when compared with the normal method invocation in Java, you should be careful not to use interfaces casually in performance-critical code.
  • Nested Interfaces: An interface can be declared a member of a class or another interface. Such an interface is called a member interface or a nested interface. A nested interface can be declared as public, private, or protected. This differs from a top-level interface, which must either be declared as public or use the default access level.

    // This class contains a member interface.
    class A {
      // this is a nested interface
      public interface NestedIF {
        boolean isNotNegative(int x);
      }
    }
    // B implements the nested interface.
    class B implements A.NestedIF {
      public boolean isNotNegative(int x) {
        return x < 0 ? false: true;
      }
    }
    class NestedIFDemo {
      public static void main(String args[]) {
        // use a nested interface reference
        A.NestedIF nif = new B();
        if(nif.isNotNegative(10))
          System.out.println("10 is not negative");
        if(nif.isNotNegative(-12))
          System.out.println("this won't be displayed");
      }
    }
    
  • Interfaces Can Be Extended: One interface can inherit another by use of the keyword extends. The syntax is the same as for inheriting classes. Any class that implements an interface must implement all methods required by that interface, including any that are inherited from other interfaces.

  • Default Interface Methods (aka extension method): A primary motivation for the default method was to provide a means by which interfaces could be expanded without breaking existing code. i.e. suppose you add another method without body in an interface. Then you will have to provide the body of that method in all the classes that implement that interface. For example
interface Box {

  // Does not need to implement this method in class
  default String getInterfaceName(){
    return "Box"; 
 }

}

class Main implements Box {
  public Main(){
}
}

Box b = new Main();
System.out.println(b.getInterfaceName()); // Box
  • For example, you might have a class that implements two interfaces. If each of these interfaces provides default methods, then some behavior is inherited from both.

    • In all cases, a class implementation takes priority over an interface default implementation.
    • In cases in which a class implements two interfaces that both have the same default method, but the class does not override that method, then an error will result.
    • In cases in which one interface inherits another, with both defining a common default method, the inheriting interface’s version of the method takes precedence.
  • Note: static interface methods are not inherited by either an implementing class or a subinterface. i.e. static interface methods should have a body! They cannot be abstract.

  • Note: when overriding methods, the access modifier should be the same or better i.e. if in Parent Class it was protected, then overridden should be either protected or public.

Abstract vs Interface

abstraction vs Interface.png

Abstraction vs Encapsulation

Abstraction Vs Encapsulation.png

Inner Classes

  • Defining an inner class is straightforward; simply include the definition of the inner class within another class, as follows:

    public class OuterClass
    {
        private class InnerClass
        {
            Declarations_of_InnerClass_Instance_Variables
            Definitions_of_InnerClass_Methods
        }
        Declarations_of_OuterClass_Instance_Variables
        Definitions_of_OuterClass_Methods
    }
    
  • Inner class and outer class have access to each other's private members.

  • The inside class is called an inner class. A common and simple use of an inner class is to use it as a helper class for the outer class, in which case the inner class should be marked private.

    class Outer {
        private int n = 5;
    
        private class Inner {
    
            private int x=10;
    
            public Inner()
            {
                System.out.println("Inner constructor called");
            }
        }
    
        // Have to use object of inner to get access its private variables
        private Inner in;
    
        public Outer()
        {
            // initializing inner reference 
            in = new Inner();
        }
    
        public int getInnerX()
        {
            return in.x;
        }
    }
    
    Outer o = new Outer();
    System.out.println(o.getInnerX());
    
  • When you compile any class in Java, it produces a .class file. When you compile a class with an inner class, this compiles both the outer class and the inner class and produces two .class files.
  • A static inner class can have nonstatic instance variables and methods, but an object of a static inner class has no connection to an object of the outer class. You may encounter situations where you need an inner class to be static. For example, if you create an object of the inner class within a static method of the outer class, then the inner class must be static. This follows from the fact that a nonstatic inner class object must arise from an outer class object.
  • If an inner class is marked with the public modifier instead of the private modifier, then it can be used in all the ways we discussed so far, but it can also be used outside of the outer class.

    BankAccount account = new BankAccount();
    BankAccount.Money amount = account.new Money("41.99");
    
  • if a method is invoked in an inner class and the inner class has no such method, then it is assumed to be an invocation of the method by that name in the outer class.

  • OuterClass has an inner class named InnerClass. If you derive DerivedClass from OuterClass, then DerivedClass automatically has InnerClass as an inner class just as if it were defined within DerivedClass.
  • It is not possible to override the definition of an inner class when you define a derived class of the outer class.

Use of Inner Class

  • suppose you want to have a class with two base classes. This is not allowed in Java. However, you can have an outer class derived from one base class with an inner class derived from the other base each other’s instance variables and methods, this can often serve as if it were a class with two base classes.

References