MTI TEK
  • Home
  • LLMs
  • Docker
  • Kubernetes
  • Java
  • All
Java | Inheritance
  1. Inheritance
  2. Variable / Reference / Object
  3. Casting (UpCasting + DownCasting)
  4. Method Invocation

  1. Inheritance
    Inheritance is a fundamental concept in object-oriented programming that allows a class to:
    • inherit properties and methods from a parent class (superclass),
    • override inherited methods to provide specialized behavior,
    • and implement abstract methods from abstract classes or interfaces.

    package com.mtitek.inheritance;
    
    public class SuperClass {
        protected Integer addOperation(Integer value1, Integer value2) {
            return value1 + value2;
        }
    
        protected Integer multiplyOperation(Integer value1, Integer value2) {
            return value1 * value2;
        }
    }
    package com.mtitek.inheritance;
    
    public class SubClass extends SuperClass {
        @Override
        public Integer multiplyOperation(Integer value1, Integer value2) {
            return (value1 * value2) * 10;
        }
    
        // new method: not defined in the parent class
        public Integer subtractOperation(Integer value1, Integer value2) {
            return (value1 - value2) * 10;
        }
    }
    In the example above, the class SubClass inherits from the class SuperClass.
    It inherits both methods from the SuperClass and can use them as if they were defined in SubClass.
    It can also override the behavior of the superclass methods by redefining them (e.g., multiplyOperation).

    Note: The @Override annotation is recommended for overridden methods to ensure compile-time checking.

    Here's an illustration of the effective structure of SubClass after inheritance:
    SubClass (effective methods):
        protected Integer [inherited from SuperClass].addOperation(Integer value1, Integer value2) { ... }
    
        protected Integer [inherited from SuperClass].multiplyOperation(Integer value1, Integer value2) { ... }
    
        public Integer [overridden in SubClass].multiplyOperation(Integer value1, Integer value2) { ... }
    
        public Integer [defined in SubClass].subtractOperation(Integer value1, Integer value2) { ... }
    Overridden methods from the superclass are still accessible using the super keyword from within the subclass.

    Method visibility and access constraints:
    When a method in the superclass is declared as protected and the subclass is in a different package, the inherited method can only be accessed within the subclass itself or its subclasses. It cannot be directly invoked on subclass instances from outside the inheritance hierarchy (e.g., subClassInstance.protectedMethod() from an unrelated class in a different package).

    Important: When overriding, you cannot reduce the visibility of the method (e.g., cannot override a public method as protected).
  2. Variable / Reference / Object
    Understanding the relationship between variables, references, and objects is crucial in Java inheritance.

    • Variable Declaration:
      Declaring a variable establishes the reference type, which determines what types of objects it can point to.
      The reference type of a variable is fixed at compile time and cannot be changed.
      public class TestClass {
          SuperClass superClassRef; // Can reference SuperClass or any of its subclasses
          SubClass subClassRef; // Can only reference SubClass or its subclasses
      }
      In the example above, superClassRef can reference instances of SuperClass or SubClass (polymorphism).
      However, subClassRef can only reference instances of SubClass or its subclasses.

    • Variable Initialization and Assignment:
      Initialization creates an object instance and assigns its reference to the variable.
      The assignment must be compatible: the actual object type must be the same as or a subtype of the variable's declared type.
      Java performs both compile-time (static) and runtime (dynamic) type checking.
      public class TestClass {
          public static void main(String[] args) {
              SuperClass superClass1 = new SuperClass(); // OK: Same type
              SuperClass superClass2 = new SubClass(); // OK: SubClass IS-A SuperClass
      
              SubClass subClass1 = new SubClass(); // OK: Same type
              SubClass subClass2 = new SuperClass(); // Compiler error: Type mismatch: cannot convert from SuperClass to SubClass
              SubClass subClass3 = (SubClass) new SuperClass(); // Runtime error: ClassCastException: class SuperClass cannot be cast to class SubClass
          }
      }
    Array Covariance and ArrayStoreException:
    Java arrays are covariant, meaning you can assign an array of a subclass to a variable of superclass array type. However, the runtime type of the array remains that of the subclass, leading to potential ArrayStoreException when storing incompatible objects.
    class Parent {
    }
    
    class Child extends Parent {
        public void childMethod() {
        }
    }
    Child[] childArray = new Child[1];
    Parent[] parentArray = childArray; // Legal assignment (covariance)
    parentArray[0] = new Parent(); // Runtime error: ArrayStoreException
    
    This exception is Java's runtime safety mechanism to prevent ClassCastException when accessing array elements.
    This prevents: childArray[0].childMethod() - calling a method that doesn't exist in the Parent class.
  3. Casting (UpCasting + DownCasting)
    Type casting in inheritance allows conversion between reference types in the inheritance hierarchy.
    In assignment statements (targetVar = sourceVar;), the compiler verifies that the target variable's type is the same as or a superclass of the source variable's type.
    package com.mtitek.inheritance;
    
    public class TestClass {
        public static void main(String[] args) {
            TestClass testClass = new TestClass();
    
            TestClass testClass1 = testClass; // OK: Same type
    
            Object obj = testClass; // OK: Object is superclass of TestClass (upcasting)
    
            TestClass testClass2 = obj; // Compiler error (implicit downcasting not allowed, requires explicit cast): Type mismatch: cannot convert from Object to TestClass
    
            String str = obj; // Compiler error: Type mismatch: cannot convert from Object to String
        }
    }
    • Upcasting (Widening):
      Converting a subclass reference to a superclass reference. Always safe and can be implicit.
      Object obj1 = new String("Hello"); // Implicit upcasting
      Object obj2 = (Object) new String("Java"); // Explicit upcasting - unnecessary but valid
      Upcasting is always safe because a subclass instance IS-A superclass instance. The compiler accepts it, and no runtime exceptions occur. However, you can only access methods and fields defined in the superclass through the upcasted reference.

    • Downcasting (Narrowing):
      Converting a superclass reference to a subclass reference. Potentially unsafe and requires explicit casting.
      Object obj = "Hello"; // String object referenced by Object variable
      String str1 = obj; // Compiler error (implicit downcasting not allowed): Type mismatch: cannot convert from Object to String
      String str2 = (String) obj; // OK (explicit downcasting): succeeds at runtime
      
      Object obj2 = new Integer(42);
      String str3 = (String) obj2; // Runtime error: ClassCastException: class Integer cannot be cast to class String
      Implicit downcasting is always rejected by the compiler for safety.
      Explicit downcasting is accepted by the compiler but checked at runtime. The JVM verifies that the actual object type is compatible with the target type; if not, it throws ClassCastException.

      Note: Use instanceof to check type compatibility before downcasting.
      if (obj instanceof String) {
          String str = (String) obj;
      }
  4. Method Invocation
    Method invocation in inheritance involves both compile-time and runtime resolution.

    Compile-time Checking (Static Binding):
    The compiler only considers the declared (reference) type of the variable, not the actual object type.
    It verifies that the method exists in the reference type or its supertypes (not subtypes).
    If the method is not found, a Compiler error occurs.
    For checked exceptions, the compiler enforces handling based on the method signature in the reference type, even if an overridden version in a subclass doesn't throw the exception.

    Runtime Execution (Dynamic Binding):
    The JVM uses the actual object type to determine which method implementation to execute.
    If the method is overridden in the object's class, that version executes (method overriding/polymorphism).
    public class TestClass {
        public static void main(String[] args) {
            SuperClass superRef1 = new SuperClass(); // Reference type: SuperClass, Object type: SuperClass
            SuperClass superRef2 = new SubClass(); // Reference type: SuperClass, Object type: SubClass
            SubClass subRef1 = new SubClass(); // Reference type: SubClass, Object type: SubClass
    
            // Runtime: Method resolution based on actual object type
            superRef1.multiplyOperation(1, 2); // Executes SuperClass.multiplyOperation()
            superRef2.multiplyOperation(1, 2); // Executes SubClass.multiplyOperation() (overridden)
            subRef1.multiplyOperation(1, 2); // Executes SubClass.multiplyOperation()
    
            // superRef2.subtractOperation(1, 2); // Compiler error: The method subtractOperation(int, int) is undefined for the type SuperClass
    
            subRef1.subtractOperation(1, 2); // OK: subtractOperation exists in SubClass and subRef1's reference type is SubClass
        }
    }
    Accessing Superclass Methods:
    To invoke the superclass version of an overridden method, use the super keyword. This is only available within the subclass code, not from external callers.
    public class SubClass extends SuperClass {
        @Override
        public Integer multiplyOperation(Integer value1, Integer value2) {
            Integer superResult = super.multiplyOperation(value1, value2);
            return superResult * 10;
        }
    }
© 2025 mtitek
About