Skip to content

RamanarayanRansingh/OOPS_NOTES

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 

Repository files navigation

Classes and Objects in Java

  • Class: A class is a template or blueprint for an object. It defines a new data type that can be used to create objects.
  • Object: An object is an instance of a class and represents physical reality (occupies space in memory).

Key Properties of Objects

  1. State: Represents the data (attributes) of the object.
  2. Identity: Uniquely distinguishes one object from another (usually where it's stored in memory).
  3. Behavior: Describes what operations (methods) can be performed on the object.

Declaration and Instantiation

  • Declaration:

    Box mybox; // Declare reference to object

    Here, mybox is a reference to an object of type Box. It does not yet refer to an actual object.

  • Instantiation:

    mybox = new Box(); // Allocate a Box object

    This line allocates memory for a Box object using the new keyword and assigns a reference to mybox.

Memory Allocation with new

  • The new keyword allocates memory dynamically (at runtime) for an object and returns a reference to it.

  • The reference is stored in a variable (in this case, mybox), which holds the memory address of the object.

    classname class-var = new classname();
    • class-var: A variable of the class type being created.
    • classname(): Constructor of the class, invoked when an object is instantiated.

Primitive Types vs Objects

  • Primitive Types: Such as int and char are not objects in Java. They are implemented as normal variables for efficiency and do not require new for instantiation.

Object References and Assignment

  • Example:
    Box b1 = new Box();
    Box b2 = b1;
    • b1 and b2 both refer to the same object. No new memory is allocated for b2, and changes made through b2 will affect the object referred to by b1.

Method Parameters

  • Parameter: A variable defined in a method that receives a value during method invocation.

    Example:

    int square(int i) {
        return i * i;
    }
    • i is a parameter in the square(int i) method.
  • Argument: The actual value passed when invoking the method. In square(100), 100 is the argument.


Note on Object Creation

  • When creating an object:
    Bus bus = new Bus();
    • LHS (Left-hand side): bus (the reference) is checked by the compiler.
    • RHS (Right-hand side): new Bus() (the object creation) is handled by the JVM.

This structure captures the essential concepts of classes, objects, memory allocation, and method parameters in Java.


The this Keyword

  • Definition:
    • The this keyword is used within a method or constructor to refer to the current object, i.e., the object on which the method or constructor was invoked.

    • Example:

      class Box {
          int width, height;
      
          Box(int width, int height) {
              this.width = width;  // Refers to the instance variable
              this.height = height; // Refers to the instance variable
          }
      
          void displayDimensions() {
              System.out.println("Width: " + this.width + ", Height: " + this.height);
          }
      }

The final Keyword

  • Purpose:

    • Used to declare constants in Java. A field declared as final cannot be modified after it is initialized.
  • Usage Example:

    final int FILE_OPEN = 2;  // Constant field with all uppercase naming convention
  • Immutability for Primitive vs Reference Types:

    • For primitive types: The value cannot be changed once initialized.

    • For reference types: The reference to the object cannot be changed, but the object’s internal state can still be modified.

    • Example for reference type:

      final Box box = new Box(5, 10);  // The reference 'box' cannot point to another Box
      box.width = 20;  // But you can modify the internal state of the Box object

The finalize() Method

  • Purpose:

    • The finalize() method is used to perform cleanup actions before an object is reclaimed by the garbage collector.
  • Example:

    protected void finalize() {
        System.out.println("Object is being garbage collected");
    }
  • Note: This method is rarely used in modern Java programs due to improvements in garbage collection and better alternatives for resource management like try-with-resources.


Constructors in Java

  • Definition:

    • A constructor is a special method that is automatically called when an object is created. It has no return type and is used to initialize objects.
  • Example:

    class Box {
        int width, height;
    
        // Constructor
        Box() {
            width = 10;
            height = 20;
            System.out.println("Box Constructor Called");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Box mybox = new Box();  // Calls the Box() constructor
        }
    }

Inheritance and Constructors in Java

  • Key Points:

    • In Java, the constructor of a base class (superclass) is automatically called when an object of the derived class (subclass) is created.
    • If the base class has no argument constructor, it gets invoked by default.
    • If the base class has a parameterized constructor, the derived class must explicitly call it.
  • Example:

    class Base {
        Base() {
            System.out.println("Base Class Constructor Called");
        }
    }
    
    class Derived extends Base {
        Derived() {
            // Automatically calls the Base() constructor
            System.out.println("Derived Class Constructor Called");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Derived d = new Derived();  // Creates an instance of Derived, calls both constructors
        }
    }

    Output:

    Base Class Constructor Called
    Derived Class Constructor Called
    
  • Parameterized Constructor in Base Class:

    class Base {
        int value;
    
        // Parameterized constructor
        Base(int value) {
            this.value = value;
            System.out.println("Base Class Constructor Called with value: " + value);
        }
    }
    
    class Derived extends Base {
        // Must explicitly call the Base class parameterized constructor
        Derived(int value) {
            super(value);  // Call to Base class constructor
            System.out.println("Derived Class Constructor Called");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Derived d = new Derived(100);  // Calls parameterized constructor of Base
        }
    }

    Output:

    Base Class Constructor Called with value: 100
    Derived Class Constructor Called
    

This structure provides a comprehensive overview of the this, final, finalize() methods, and the role of constructors and inheritance in Java with code examples to clarify the concepts.

Here's a detailed and structured note on Java packages, including examples and important details:


Java Packages

  • Definition:

    • Packages are containers for classes that help in compartmentalizing the class namespace. They prevent name collisions and organize related classes into hierarchical structures.
  • Purpose:

    • Naming: Avoids naming conflicts by allowing multiple classes with the same name in different packages.
    • Visibility Control: Determines which classes and members are accessible from other classes.
  • Creating a Package:

    • To create a package, include the package statement at the beginning of your Java source file.
    package MyPackage; // Declares that the class belongs to MyPackage
  • File System Structure:

    • Java uses directories to represent packages. The directory structure must mirror the package hierarchy.
    • For example, a class in the java.awt.image package must be stored in java/awt/image directory.
  • Directory Naming:

    • The directory name must exactly match the package name, including case sensitivity.
  • Finding Packages:

    • Default Behavior: The Java runtime system starts searching for packages in the current working directory.
    • Classpath: You can specify additional directories or JAR files using the CLASSPATH environment variable.
    • Command Line: Use the -classpath (or -cp) option with java and javac commands to specify paths to your classes.
  • Importing Packages:

    • When importing a package, only the public members of the classes within that package are accessible. Non-public members are not accessible outside their package.

    • Example:

      // In MyPackage/Person.java
      package MyPackage;
      
      public class Person {
          public String name;
          private int age;
      
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public void display() {
              System.out.println("Name: " + name + ", Age: " + age);
          }
      }
      // In another package or directory
      import MyPackage.Person;
      
      public class TestPerson {
          public static void main(String[] args) {
              Person p = new Person("Alice", 30);
              p.display();  // Accessible as display() is public
              // p.age; // Not accessible, as age is private
          }
      }
  • Classpath Example:

    • Setting CLASSPATH (environment variable): Add the directory where your .class files are located.

    • Using Command Line:

      javac -classpath /path/to/classes MyPackage/Person.java
      java -classpath /path/to/classes TestPerson

This note captures the essential aspects of Java packages, including their purpose, file system structure, and how to manage and import them, along with practical examples.

Here's a detailed and well-organized note on the static keyword in Java, including explanations and code examples:


Understanding static in Java

  • Definition:

    • The static keyword in Java is used for creating class-level methods and variables. Static members belong to the class rather than to instances of the class.
  • Characteristics of Static Members:

    • Access: Static members can be accessed before any objects of the class are created and without reference to any specific object.
    • Static Methods:
      • Belong to the class, not to any instance.
      • Can only access other static methods and static variables directly.
      • Cannot access instance variables or methods.
      • Cannot use this or super keywords.
      • Can be accessed using the class name directly.
  • Example of Static Methods:

    public class Human {
        String message = "Hello World";
    
        public static void display(Human human) {
            System.out.println(human.message); // Accessing non-static variable through object reference
        }
    
        public static void main(String[] args) {
            Human kunal = new Human();
            kunal.message = "Kunal's message";
            Human.display(kunal);
        }
    }
  • Static Variables:

    • Shared among all instances of a class.
    • Initialized when the class is first loaded.
  • Static Blocks:

    • Used for static initialization. Executes once when the class is first loaded.
    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
      
  • Static Inner Classes:

    • Only nested classes can be static.
    • Static nested classes do not have access to instance variables of the outer class.
    public class Static {
        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
        }
    }
  • Key Points:

    • Static methods cannot be overridden because method resolution is done at compile-time for static methods.
    • Static interface methods are not inherited by implementing classes or sub-interfaces.
    • Static inner classes are not associated with an instance of the outer class and do not have access to its instance members.
  • Note on Static Classes:

    • Only nested classes can be static. Static nested classes can have static variables and methods.
    public class StaticDemo {
        // Static inner class
        static class InnerStatic {
            static int staticVar = 10;
    
            static void staticMethod() {
                System.out.println("Static Method in InnerStatic");
            }
        }
    
        public static void main(String[] args) {
            InnerStatic.staticMethod(); // Access static method directly
            System.out.println("Static Variable: " + InnerStatic.staticVar); // Access static variable directly
        }
    }

This note provides a comprehensive overview of static members in Java, including how they function, examples, and specific rules regarding their usage.

Singleton Design Pattern in Java

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to that instance. Here's a detailed breakdown of how the Singleton pattern is implemented in Java, using the provided code as an example:


Singleton Class Implementation

package com.kunal.singleton;

import com.kunal.access.A;

public class Singleton {
    // Private constructor to prevent instantiation from outside
    private Singleton() {
    }

    // Private static variable to hold the single instance
    private static Singleton instance;

    // Public static method to provide access to the instance
    public static Singleton getInstance() {
        // Create the instance only if it does not exist
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Key Components

  1. Private Constructor:

    • The constructor private Singleton() ensures that the class cannot be instantiated from outside. This is crucial for maintaining a single instance of the class.
  2. Private Static Variable:

    • private static Singleton instance; is a static variable that holds the single instance of the class. This variable is private to prevent direct access from outside the class.
  3. Public Static Method:

    • public static Singleton getInstance() is a static method that provides the global access point to the single instance of the class.
    • Inside this method, a check is performed to see if the instance is null. If it is null, a new instance is created and assigned to the instance variable.
    • If the instance already exists, the existing instance is returned.

Usage

  • Accessing the Singleton Instance:
    • To get the single instance of the Singleton class, you call Singleton.getInstance().
    • This method ensures that only one instance of the class is created and reused throughout the application.

Advantages

  • Controlled Access: Ensures controlled access to the single instance of the class.
  • Reduced Memory Usage: Since only one instance is created, it helps in reducing memory consumption.
  • Global Access Point: Provides a global point of access to the instance.

Considerations

  • Thread Safety: The given implementation is not thread-safe. In a multi-threaded environment, multiple threads might simultaneously pass the null check and create multiple instances. To make it thread-safe, consider using synchronization or the double-checked locking principle.

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
  • Lazy Initialization: The instance is created only when getInstance() is called for the first time, which is known as lazy initialization.

Real-World Example

  • Configuration Manager: Often used in applications where a single configuration manager is needed to handle configuration settings.

Note

  • Package Import: The import com.kunal.access.A; statement indicates that this class might be using or extending functionality from another class or package. Ensure that A is appropriately defined if needed.

This note provides a clear explanation of the Singleton design pattern and how it is implemented in Java, along with the provided code example.

Inheritance in Java

Inheritance is a fundamental concept in Java that allows one class to inherit the properties and behaviors (fields and methods) of another class. Here's a breakdown of key concepts related to inheritance, including the use of extends, super, and final.


Basic Inheritance Syntax

In Java, inheritance is achieved using the extends keyword:

class Subclass extends Superclass {
    // body of class
}
  • Subclass: The class that inherits from another class.
  • Superclass: The class being inherited from.
  • A subclass can inherit only one superclass (Java does not support multiple inheritance directly).

Superclasses and Subclass Access

  • While a subclass inherits all the fields and methods of a superclass, it cannot access private members of the superclass directly.

  • Superclass Reference to Subclass Object: You can assign a subclass object to a superclass reference, but the reference can only access methods and fields defined in the superclass.

    Superclass ref = new Subclass(); // ref can only access superclass methods.

Using super Keyword

super is used to:

  1. Call the superclass constructor.
  2. Access a superclass method or field that is hidden by the subclass.
  • Calling a Superclass Constructor: If a subclass needs to initialize fields from the superclass, it can call the superclass constructor using super.

    class BoxWeight extends Box {
        double weight;
    
        BoxWeight(double w, double h, double d, double m) {
            super(w, h, d); // Call superclass constructor
            weight = m;
        }
    }
  • Accessing Superclass Methods or Fields: When a subclass hides a superclass member with the same name, you can use super to refer to the superclass version.

    super.member; // Access the superclass method or field.

Multilevel Hierarchy

In a multilevel class hierarchy, super() always refers to the constructor of the immediate superclass. Each constructor in the chain must call the constructor of its superclass.

class Box {
    private double width, height, depth;

    Box(double w, double h, double d) {
        width = w;
        height = h;
        depth = d;
    }
}

class BoxWeight extends Box {
    double weight;

    BoxWeight(double w, double h, double d, double m) {
        super(w, h, d); // Call Box constructor
        weight = m;
    }
}

Key Points About super()

  • super() must be the first statement in the subclass constructor if used.
  • If a superclass constructor requires parameters, the subclass must pass them via super().

final Keyword

The final keyword can be used with methods and classes to prevent further inheritance or modification.

  1. Prevent Overriding: Marking a method as final prevents subclasses from overriding it.

    final void show() {
        // cannot be overridden
    }
  2. Prevent Inheritance: Marking a class as final prevents it from being extended.

    final class MyClass {
        // no other class can extend MyClass
    }
  3. Performance: Declaring methods as final allows for early binding (method calls resolved at compile time), improving performance through inlining.

  4. Note on Static Methods: Static methods are class-level and are not subject to overriding. Thus, polymorphism does not apply to static methods.

Important Notes

  • Polymorphism: In Java, polymorphism applies to methods, not fields. Thus, instance variables are not subject to polymorphism.
  • Default Constructor: If super() is not used, the default or parameterless constructor of each superclass is called automatically.

Example

class Box {
    private double width, height, depth;

    Box(Box ob) { // Constructor with object parameter
        width = ob.width;
        height = ob.height;
        depth = ob.depth;
    }
}

class BoxWeight extends Box {
    double weight;

    BoxWeight(BoxWeight ob) {
        super(ob); // Call superclass constructor
        weight = ob.weight;
    }
}

In the above code, super(ob) calls the Box constructor, passing a BoxWeight object, which invokes the constructor of the superclass.


By understanding these core concepts, you can effectively utilize inheritance, super, and final in Java to build efficient and scalable object-oriented programs.

Method Overloading in Java

Method overloading allows multiple methods in the same class to have the same name but different parameter lists. Here's a detailed explanation:


Key Concepts of Method Overloading:

  1. Same Method Name, Different Parameters: You can define multiple methods with the same name in a class as long as their parameters (number, types, or order) differ.

  2. Different Return Types: While overloaded methods can have different return types, the return type alone does not distinguish overloaded methods.

  3. Method Resolution: When a method is called, Java determines which method to invoke based on the argument list provided. It selects the best match according to the method's parameter types.

  4. Automatic Type Conversion: If no exact match is found, Java will attempt to convert the arguments using its automatic type conversion rules (e.g., int to double).


Example: Automatic Type Conversion in Overloading

In this example, the method test(double) is invoked even though test(int) is not defined. This happens because Java automatically converts the int argument to double.

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;

        // Automatic type conversion from int to double
        ob.test(i);        // Calls test(double) with i converted to double
        ob.test(123.2);    // Calls test(double) with a double argument
    }
}

Explanation:

  • The test(int) method is not defined in OverloadDemo.
  • When ob.test(i) is called with an int argument, Java automatically converts i to a double and calls test(double).

Returning Objects from Methods

In Java, a method can return an object. This is useful when you want a method to return complex data or objects with multiple fields.


Key Concepts of Returning Objects:

  1. Returning Object References: When a method returns an object, it returns a reference to the object created within the method.

  2. Dynamic Object Allocation: Since objects are dynamically allocated using new, they remain in memory as long as there are references to them, even after the method terminates.

  3. Garbage Collection: Once there are no references to an object, it becomes eligible for garbage collection, meaning the memory is freed.


Example: Method Returning an Object

In the following example, the incrByTen() method returns a new Test object with its a field incremented by 10:

class Test {
    int a;

    // Constructor to initialize the 'a' field
    Test(int i) {
        a = i;
    }

    // Method that creates and returns a new object with 'a' incremented by 10
    Test incrByTen() {
        Test temp = new Test(a + 10);
        return temp;  // Return the new object
    }
}

class RetOb {
    public static void main(String args[]) {
        Test ob1 = new Test(2);  // Create a new object with 'a' initialized to 2
        Test ob2;

        // Call incrByTen() which returns a new Test object
        ob2 = ob1.incrByTen();

        System.out.println("ob1.a: " + ob1.a);  // Output: 2
        System.out.println("ob2.a: " + ob2.a);  // Output: 12
    }
}

Explanation:

  • Object Creation: The incrByTen() method creates a new Test object with a incremented by 10.
  • Returning the Object: The method returns the reference to the newly created object. This reference is assigned to ob2.
  • Output:
    • ob1.a remains 2 because ob1 is unchanged.
    • ob2.a becomes 12 as it references the new object with a incremented.

Key Points:

  • Automatic Type Conversion: If no exact match is found during method overloading, Java attempts to convert the argument type (e.g., int to double).
  • Returning Objects: When an object is returned from a method, it continues to exist as long as there is a reference to it, even after the method terminates.
  • Garbage Collection: When no references to an object remain, it is eligible for garbage collection, and its memory is freed.

These concepts are crucial for writing efficient, flexible, and reusable code in Java.

Method Overriding in Java

Method overriding occurs when a subclass defines a method with the same name, return type, and parameters as a method in its superclass. The overridden method in the subclass hides the method in the superclass. When the method is called using a subclass object, the subclass version is executed, even if the reference is of the superclass type.


Key Concepts of Method Overriding:

  1. Same Name, Return Type, and Parameters: The method in the subclass must have the exact same name, return type, and parameters as the method in the superclass.

  2. Method Resolution at Runtime: Overridden methods are resolved at runtime using dynamic method dispatch, which allows Java to achieve runtime polymorphism.

  3. Superclass Reference to Subclass Object: A reference variable of the superclass can point to an object of the subclass. The actual method executed depends on the object type, not the reference type.

  4. Return Type in Overriding: In overriding, the return type must be the same or a covariant return type (a subclass of the return type of the overridden method).


Example: Method Overriding in Java

Let's look at an example where a method in a subclass overrides a method in its superclass:

// Superclass
class A {
    // Method to be overridden
    void display() {
        System.out.println("Display from class A");
    }
}

// Subclass
class B extends A {
    // Overriding the display method
    @Override
    void display() {
        System.out.println("Display from class B");
    }
}

class TestOverride {
    public static void main(String[] args) {
        A a = new A();     // Reference and object of class A
        B b = new B();     // Reference and object of class B
        A ref;             // Reference variable of type A

        ref = a;           // ref refers to object of class A
        ref.display();     // Calls A's display method

        ref = b;           // ref refers to object of class B (upcasting)
        ref.display();     // Calls B's display method (overridden)
    }
}

Explanation:

  • The display() method in class B overrides the display() method in class A.
  • When the reference ref of type A refers to an object of class B, the overridden display() method in B is called.

Output:

Display from class A
Display from class B

Dynamic Method Dispatch

Dynamic method dispatch is the process through which Java resolves calls to overridden methods at runtime. The decision on which method to execute depends on the actual object type that the reference points to, rather than the reference type itself.


Key Concepts of Dynamic Method Dispatch:

  1. Runtime Polymorphism: Dynamic method dispatch allows for polymorphic behavior, meaning the same method call can invoke different method versions depending on the actual object type at runtime.

  2. Superclass Reference: A superclass reference can refer to any object of a subclass. When a method is called using this reference, the method of the actual object's class is invoked.


Example: Dynamic Method Dispatch in Java

// Superclass
class A {
    // Method to be overridden
    void display() {
        System.out.println("Display from class A");
    }
}

// Subclass
class B extends A {
    // Overriding the display method
    @Override
    void display() {
        System.out.println("Display from class B");
    }
}

// Another Subclass
class C extends A {
    // Overriding the display method
    @Override
    void display() {
        System.out.println("Display from class C");
    }
}

class TestDynamicDispatch {
    public static void main(String[] args) {
        A ref;  // Superclass reference

        B objB = new B();
        C objC = new C();

        // Dynamic method dispatch
        ref = objB;  // ref refers to an object of type B
        ref.display();  // Calls B's display method

        ref = objC;  // ref refers to an object of type C
        ref.display();  // Calls C's display method
    }
}

Explanation:

  • A superclass reference (ref) is used to point to objects of different subclasses (B and C).
  • The display() method that gets invoked depends on the object (objB or objC) that the reference points to at runtime.

Output:

Display from class B
Display from class C

Overriding with Covariant Return Types

In method overriding, the return type of the overridden method in the subclass can be a subclass of the return type in the superclass. This is called a covariant return type.


Example: Covariant Return Type in Overriding

// Superclass
class A {
    A display() {
        System.out.println("Display from class A");
        return this;
    }
}

// Subclass
class B extends A {
    @Override
    B display() {
        System.out.println("Display from class B");
        return this;
    }
}

class TestCovariantReturn {
    public static void main(String[] args) {
        B b = new B();
        b.display();  // Calls B's display method
    }
}

Explanation:

  • In class A, the display() method returns an object of type A.
  • In class B, the overridden display() method returns an object of type B, which is allowed because B is a subclass of A.

Output:

Display from class B

Summary:

  • Method Overriding: When a subclass provides a specific implementation of a method that is already defined in its superclass.
  • Dynamic Method Dispatch: The process through which the overridden method to be executed is determined at runtime based on the object's type.
  • Covariant Return Types: In method overriding, the return type can be a subclass of the return type declared in the superclass method.

This enables Java's powerful runtime polymorphism, allowing methods to behave differently based on the actual object type at runtime.

Access Control in Java

Access control in Java is determined by access modifiers that specify the visibility of class members (fields and methods) across different contexts (classes, packages, and subclasses). The four main access levels are public, protected, private, and default (no modifier).


Access Modifier Levels:

Here’s a breakdown of access levels across different contexts:

Modifier Class Package Subclass (same pkg) Subclass (diff pkg) World
public + + + + +
protected + + + +
no modifier (default) + + +
private +
  • + means accessible
  • Blank means not accessible

Understanding Access Modifiers:

  1. public:

    • Accessible from anywhere (within the same class, package, subclass, or any other class in different packages).
  2. protected:

    • Accessible within the same class, package, and by subclasses, even if they are in different packages. However, the member can only be accessed by subclass objects, not by instances of the superclass from a different package.
  3. default (no modifier):

    • Accessible only within the same package (package-private). Not accessible from outside the package, even by subclasses.
  4. private:

    • Accessible only within the same class. Not visible to other classes, subclasses, or outside packages.

Example of Access Control:

Here’s a practical example of how protected access works in the context of different packages and inheritance.

// packageOne/Base.java
package packageOne;

public class Base {
    protected void display() {
        System.out.println("in Base");
    }
}

// packageTwo/Derived.java
package packageTwo;
import packageOne.Base;

public class Derived extends Base {
    public void show() {
        // this is allowed, because we're in the subclass
        display(); // Directly calling the inherited protected method

        // this is allowed, because the method is being called on a Derived object
        new Derived().display();

        // this is NOT allowed, because we're creating an instance of Base (superclass),
        // and outside of packageOne, the protected method cannot be called using a superclass reference
        // new Base().display(); // Compile-time error!
    }
}

Explanation:

  • display() (direct call): This works because it is being called from within the subclass (Derived), and the protected method is inherited.
  • new Derived().display(): This works because the method is called on an instance of Derived, and Derived has access to the protected method of its superclass.
  • new Base().display(): This does not work because you are trying to access a protected method through a Base instance outside the packageOne. Even though Derived extends Base, the protected access does not apply to superclass instances from outside the package.

Protected Access with Subclass Objects:

Protected members can be accessed from a subclass, but only through an instance of the subclass. The access to protected members of an instance of the superclass from outside the package is not allowed.

Example:

// packageOne/C.java
package packageOne;

public class C {
    protected String message = "Protected message from class C";
}

// packageTwo/S.java
package packageTwo;
import packageOne.C;

public class S extends C {
    public void accessProtected(C obj) {
        // This works because 'message' is inherited by the S class
        System.out.println(this.message); // Accessing inherited protected member

        // This DOES NOT work because 'obj' is of type C, and we cannot access protected members through
        // superclass instances outside the package.
        // System.out.println(obj.message); // Compile-time error!
    }

    public static void main(String[] args) {
        S sObj = new S();
        C cObj = new C();

        sObj.accessProtected(cObj);  // Direct access to protected member via inheritance
    }
}

Explanation:

  • The message field in class C is protected, so it can be accessed by subclasses of C, even if they are in different packages.
  • Direct Access (this.message): This works because S inherits the message field.
  • Access via C instance (obj.message): This does not work because obj is of type C, and S cannot access the protected member of an instance of the superclass from a different package.

Dynamic Access Control Rule:

Java enforces a rule that if an object obj is an instance of a superclass C and you are accessing a protected member, the static type of obj must be exactly the subclass type (S). If obj is of type C or another subclass S2, accessing the protected member is not allowed.

Example:

// packageOne/C.java
package packageOne;

public class C {
    protected void method() {
        System.out.println("C's protected method");
    }
}

// packageTwo/S.java
package packageTwo;
import packageOne.C;

public class S extends C {
    public void callMethod(C obj) {
        // This works, because we're calling the inherited method
        this.method(); // Allowed

        // This works, because obj is of type S (the current subclass)
        if (obj instanceof S) {
            ((S)obj).method(); // Allowed due to explicit casting to subclass type
        }

        // This doesn't work, because obj is of type C (superclass)
        // obj.method(); // Compile-time error!
    }
}

Explanation:

  • this.method(): Works because the subclass S inherits the protected method method() from class C.
  • ((S)obj).method(): Works because obj is explicitly cast to S, so the static type becomes S.
  • obj.method(): Does not work if obj is of type C, because access to protected methods is not allowed through superclass references from outside the package.

Conclusion:

  • Protected access allows subclass inheritance and access within the same package but restricts access through superclass instances from outside the package.
  • The rule for protected access is that it’s only allowed when the object reference is of the subclass type or the class itself. Accessing protected members via superclass instances from a different package is not permitted.

In Java, abstract classes and interfaces are essential tools for defining the structure and behavior of classes in an object-oriented manner. Here's a detailed explanation of how abstract classes, methods, and interfaces work, highlighting their differences and practical use cases.

Abstract Classes and Abstract Methods

An abstract class in Java provides a base class that cannot be instantiated. It defines a generalized form to be shared by all its subclasses, leaving the implementation details to the subclasses. This allows you to define a template where certain behaviors must be implemented by subclasses.

Key Features:

  • Abstract Methods: These methods have no implementation in the abstract class and must be overridden by any subclass.
    abstract void methodName();  // No implementation
  • Concrete Methods: Abstract classes can also include methods with complete implementations, allowing for partial implementation.
  • Object Instantiation: You cannot instantiate an abstract class directly. However, you can declare references of the abstract class type.
  • Constructor: Abstract classes can have constructors, but they are not used to create objects directly. Instead, they are used for initialization when a subclass is instantiated.

Example:

abstract class Animal {
    abstract void makeSound();  // Abstract method
    void sleep() {
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Bark");
    }
}

Rules for Abstract Classes:

  • Mandatory Override: If a subclass does not implement all abstract methods, it must also be declared abstract.
  • Static Methods: Abstract classes can have static methods, but static methods cannot be abstract.
  • Visibility: Abstract classes can have private, protected, or public members.

Interfaces

An interface in Java is a completely abstract class that defines a contract for what a class can do without specifying how it does it. All methods in an interface are implicitly abstract until Java 8, where interfaces can also include default and static methods.

Key Features:

  • Abstract Methods: All methods in an interface are abstract by default (prior to Java 8). Subclasses must implement these methods.
  • Default Methods: From Java 8 onwards, interfaces can also have default methods, which provide a method body that can be overridden.
  • Static Methods: Interfaces can also contain static methods, which must have implementations.
  • Final Variables: All variables in an interface are implicitly public, static, and final.

Example:

interface Animal {
    void makeSound();  // Abstract method
}

class Cat implements Animal {
    public void makeSound() {
        System.out.println("Meow");
    }
}

Differences Between Abstract Class and Interface

Feature Abstract Class Interface
Methods Can have both abstract and concrete methods. Only abstract methods (until Java 8). Java 8+ allows default and static methods.
Final Variables Can have non-final and final variables. All variables are public, static, and final by default.
Implementation Can implement methods of an interface. Cannot implement methods of an abstract class.
Inheritance Can extend one class and implement multiple interfaces. Can extend multiple interfaces.
Access Modifiers Can have private, protected, or public methods and variables. All members are public by default.
Object Instantiation Cannot be instantiated directly but can have constructors. Cannot be instantiated and does not have constructors.

Multiple Inheritance

In Java, a class can only inherit from one superclass but can implement multiple interfaces, allowing multiple inheritance of behavior. Interfaces provide a way to define contracts for classes to follow, while abstract classes allow for shared code and partial implementation.

Example Scenario: Abstract Class vs Interface

If you're designing a set of behaviors that all subclasses must follow, and some methods can have a common implementation, an abstract class is appropriate.

abstract class Bird {
    abstract void fly();
    void sleep() {
        System.out.println("Bird is sleeping...");
    }
}

class Sparrow extends Bird {
    void fly() {
        System.out.println("Sparrow flies");
    }
}

If you want to define a contract for a class but don't care about how the behavior is implemented, an interface is more suitable.

interface Flyable {
    void fly();
}

class Airplane implements Flyable {
    public void fly() {
        System.out.println("Airplane is flying");
    }
}

Conclusion

  • Use an abstract class when you want to provide a base class with some common behavior that other classes can inherit and possibly override.
  • Use an interface when you want to specify that a class must implement certain behaviors, without enforcing how those behaviors are implemented.

These tools are essential for creating flexible, reusable, and maintainable code in Java.

In Java, multiple inheritance is not allowed for classes, meaning a class cannot directly inherit from more than one class. This is to avoid issues such as the "diamond problem," where two parent classes might provide different implementations of the same method, leading to ambiguity for the subclass.

Java's Solution: Interfaces

To achieve multiple inheritance of behavior, Java uses interfaces. Unlike classes, interfaces allow a class to "inherit" abstract method signatures from multiple sources. Here's how interfaces work:

  • Interface Basics: An interface is a reference type, similar to a class, but it can only contain method signatures (abstract methods) and constants (static final variables). All methods in an interface are implicitly public and abstract, and all variables are public, static, and final.

  • Differences from Classes:

    • A class can have state (through instance variables), but an interface cannot.
    • A class can implement multiple interfaces, allowing for multiple inheritance of types.

Key Points About Interfaces:

  1. Abstract Methods: All methods in an interface are abstract by default (without implementation), but starting from Java 8, interfaces can include default methods and static methods with actual implementations.

  2. Final and Static Variables: Variables declared in an interface are implicitly final and static, meaning they are constants and shared across all instances of implementing classes.

  3. Class vs Interface:

    • A class can maintain state and define both abstract and concrete methods.
    • An interface defines behavior without specifying how that behavior is implemented. Classes that implement an interface are responsible for providing the method bodies.
  4. Multiple Inheritance with Interfaces:

    • A class can only extend one other class, but it can implement multiple interfaces. This allows a class to inherit behaviors from multiple sources while avoiding ambiguity found in multiple class inheritance.
  5. Default Methods (Java 8+): An interface can provide a default implementation of a method using the default keyword. This allows interfaces to evolve by adding new methods without breaking existing code that implements the interface.

  6. Static Methods in Interfaces: Starting with Java 8, interfaces can also have static methods with bodies, but these methods are not inherited by implementing classes or subinterfaces.

Example: Interface in Action

interface A {
    // abstract method
    void doSomething();
    
    // default method
    default String getDefault() {
        return "Default implementation";
    }
    
    // static method
    static String getStatic() {
        return "Static method in interface";
    }
}

class B implements A {
    // overriding the abstract method
    public void doSomething() {
        System.out.println("Doing something in class B");
    }
    
    // class B can call the default method if not overridden
}

public class InterfaceDemo {
    public static void main(String[] args) {
        B b = new B();
        b.doSomething(); // Calls B's implementation
        System.out.println(b.getDefault()); // Calls default implementation from interface A
        System.out.println(A.getStatic()); // Calls static method from interface A
    }
}

Nested Interfaces:

  • Nested Interface: You can define an interface inside a class or another interface. Nested interfaces can have any access modifier (public, private, etc.), unlike top-level interfaces, which must either be public or use the default package access.

Example:

class OuterClass {
    // Nested interface
    public interface NestedInterface {
        boolean isPositive(int x);
    }
}

class InnerClass implements OuterClass.NestedInterface {
    public boolean isPositive(int x) {
        return x > 0;
    }
}

public class NestedInterfaceDemo {
    public static void main(String[] args) {
        OuterClass.NestedInterface obj = new InnerClass();
        System.out.println(obj.isPositive(10));  // Outputs: true
    }
}

Multiple Inheritance Conflict:

If a class implements two interfaces that have the same default method, the implementing class must override that method to avoid ambiguity.

interface X {
    default void method() {
        System.out.println("Interface X");
    }
}

interface Y {
    default void method() {
        System.out.println("Interface Y");
    }
}

class Z implements X, Y {
    // Must override the conflicting method
    public void method() {
        System.out.println("Class Z overrides method");
    }
}

public class MultipleInheritanceDemo {
    public static void main(String[] args) {
        Z obj = new Z();
        obj.method();  // Output: Class Z overrides method
    }
}

Summary of Abstract Class vs Interface:

Feature Abstract Class Interface
Methods Can have both abstract and concrete methods Can have only abstract methods (Java 8+: default and static methods allowed)
Variables Can have instance variables, static and non-static variables Only static and final variables
Inheritance Can extend one class Can implement multiple interfaces
Constructors Can have constructors Cannot have constructors
Multiple Inheritance Not supported Supported via multiple interfaces

Interfaces provide a flexible mechanism for defining shared behavior across different classes, while avoiding the complexity of multiple inheritance.


In Java, enumerations (enums) represent a list of named constants, but unlike in other languages, enums in Java are much more powerful because they define a class type. This capability allows enums to have additional functionality such as fields, methods, and constructors. Here’s a breakdown of important concepts and capabilities of enums in Java:

Basic Enum Declaration

Enums are defined using the enum keyword and can exist either inside or outside a class but not within methods.

enum Color {
    RED, BLUE, GREEN;
}

public class EnumExample {
    public static void main(String[] args) {
        Color c1 = Color.RED;
        System.out.println(c1); // Output: RED
    }
}

How Enums Work Internally

Internally, enums are implemented as a class, where each constant is an instance of that class.

// Equivalent internal representation of enum Color
class Color {
    public static final Color RED = new Color();
    public static final Color BLUE = new Color();
    public static final Color GREEN = new Color();
}

Enum and Inheritance

  • Enums implicitly extend java.lang.Enum, which means they cannot extend any other class. Java does not support multiple inheritance, so enums cannot be a superclass either.
  • However, enums can implement interfaces, which allows them to behave polymorphically.

Enum Methods: values(), ordinal(), valueOf()

  1. values(): This method returns an array of all enum constants.
    for (Color color : Color.values()) {
        System.out.println(color);
    }
  2. ordinal(): This returns the position (index) of the enum constant, starting from zero.
    Color c1 = Color.RED;
    System.out.println(c1.ordinal()); // Output: 0
  3. valueOf(String name): This method returns the enum constant matching the string provided.
    Color c2 = Color.valueOf("BLUE");
    System.out.println(c2); // Output: BLUE

Enum Constructors

Enums can have constructors, but they are implicitly private or package-private. You cannot create new instances of enum outside the enum definition.

enum Fruit {
    APPLE(100), BANANA(80), ORANGE(90);

    private int price;

    // Enum constructor
    Fruit(int price) {
        this.price = price;
    }

    public int getPrice() {
        return price;
    }
}

public class EnumConstructorExample {
    public static void main(String[] args) {
        System.out.println(Fruit.APPLE.getPrice()); // Output: 100
    }
}
  • Why are enum constructors private?: To prevent creating new instances, since enum constants are single instances and represent a fixed set of values.

Enum Methods and toString()

Enums can contain concrete methods but no abstract methods. The toString() method in java.lang.Enum is overridden to return the constant name.

enum Status {
    STARTED, IN_PROGRESS, COMPLETED;

    public void display() {
        System.out.println("Current Status: " + this);
    }
}

public class EnumMethodExample {
    public static void main(String[] args) {
        Status s1 = Status.IN_PROGRESS;
        s1.display(); // Output: Current Status: IN_PROGRESS
    }
}

Enum Comparison

  • You can compare two enum constants using the == operator, as they are single instances (singletons).

    if (Color.RED == Color.BLUE) {
        System.out.println("Different colors");
    } else {
        System.out.println("Same color");
    }
  • equals(): You can also compare two enum constants using equals(), but they will only be considered equal if they belong to the same enum and refer to the same constant.

Enum with main() Method

You can have a main() method within an enum and invoke it directly from the command line.

enum TestEnum {
    RED, BLUE;

    public static void main(String[] args) {
        System.out.println("Enum main method invoked");
        for (TestEnum t : TestEnum.values()) {
            System.out.println(t);
        }
    }
}

To run:

javac TestEnum.java
java TestEnum

Nested Enums

You can declare an enum inside a class or even inside another enum, known as nested enums. However, enums declared within a class can have any access modifier (public, private, protected), but top-level enums can only be public or package-private.

class OuterClass {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY;
    }
}

public class NestedEnumExample {
    public static void main(String[] args) {
        OuterClass.Day day = OuterClass.Day.MONDAY;
        System.out.println(day);  // Output: MONDAY
    }
}

Enum Implementing Interfaces

Enums can implement interfaces, allowing them to define behavior across enum constants.

interface Printable {
    void print();
}

enum Shape implements Printable {
    CIRCLE, SQUARE;

    @Override
    public void print() {
        System.out.println(this + " Shape");
    }
}

public class EnumWithInterfaceExample {
    public static void main(String[] args) {
        Shape.CIRCLE.print(); // Output: CIRCLE Shape
    }
}

Default Enum Method Priority

If a class implements two interfaces that define the same default method, the class must override it. However, in case one interface extends another, the most derived version of the default method takes precedence.

Static Methods in Enums

Static methods in enums must have a body, and they are not inherited by implementing classes or subinterfaces.

Summary of Enums in Java:

  • Enums in Java are more powerful than basic enumerations found in other languages, as they can contain fields, methods, and constructors.
  • They implicitly extend java.lang.Enum, meaning they cannot extend other classes.
  • They can implement interfaces, and each enum constant can hold specific behavior.
  • Enum methods include values(), ordinal(), and valueOf().
  • Enums provide strong type safety and are ideal for representing a fixed set of constants.

Enums are not just constants; they allow the representation of behaviors alongside constant values, giving more flexibility to how we use them in Java programs.


Here are a few additional points you can add to your cheat sheet to cover some common interview topics that could be beneficial:

1. Getters and Setters:

  • Definition: Getters and setters are methods used to access and modify private fields of a class.
  • Usage:
    • Getter: Returns the value of a private attribute.
      public String getName() {
          return this.name;
      }
    • Setter: Sets the value of a private attribute.
      public void setName(String name) {
          this.name = name;
      }
  • Encapsulation: These methods support encapsulation, by allowing controlled access to class attributes.

2. Polymorphism in Practice:

  • Method Overloading: Multiple methods with the same name but different parameters (compile-time polymorphism).
  • Method Overriding: Subclass provides a specific implementation for a method that is already defined in its superclass (runtime polymorphism).

3. "IS-A" vs "HAS-A" Relationships:

  • IS-A: Inheritance; example: a Dog IS-A Animal.
  • HAS-A: Composition; example: a Car HAS-A Engine.

4. SOLID Principles:

  • Single Responsibility Principle: A class should have one, and only one, reason to change.
  • Open/Closed Principle: Software entities should be open for extension but closed for modification.
  • Liskov Substitution Principle: Subtypes should be substitutable for their base types without affecting correctness.
  • Interface Segregation Principle: No client should be forced to depend on methods it does not use.
  • Dependency Inversion Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions.

5. Object Cloning:

  • Shallow Copy: Copies the fields of an object but does not copy objects referenced by those fields.
  • Deep Copy: Copies the fields of an object and all objects referenced by those fields, creating an independent object.

6. Exception Handling:

  • Checked Exceptions: Must be handled at compile-time.
  • Unchecked Exceptions: Errors that occur at runtime.
  • Commonly Asked: Explain how exceptions like NullPointerException, IndexOutOfBoundsException, and IOException work.

7. Common Design Patterns:

  • Singleton: Ensures a class has only one instance.
  • Factory: Creates objects without exposing the instantiation logic.
  • Observer: Defines a one-to-many dependency between objects.
  • Decorator: Dynamically adds functionality to an object without affecting other objects.

8. Final Keyword:

  • Final Class: Cannot be subclassed.
  • Final Method: Cannot be overridden by subclasses.
  • Final Variables: Its value cannot be changed once initialized.

9. Static Methods and Variables:

  • Static Variables: Shared across all instances of a class.
  • Static Methods: Can be called without creating an instance of the class.

10. String vs StringBuilder vs StringBuffer:

  • String: Immutable, meaning any change creates a new string object.
  • StringBuilder: Mutable, used when modifications are needed, not thread-safe.
  • StringBuffer: Mutable, thread-safe, used in multi-threaded environments.

These are a few important topics to enhance your cheat sheet. Let me know if you'd like more examples or detailed notes on any of these!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published