Contents

OOPS n Java

Object Oriented Programming (OOPs) Concept in Java

Object-Oriented Programming (OOP) in Java refers to a programming approach where objects are used to represent and execute the code’s functionality. Objects are the core components, performing specific tasks as defined by the programmer.

OOP is designed to model real-world entities, such as inheritance, encapsulation, polymorphism, etc., in code. The primary objective is to bind data and the functions that operate on the data, ensuring that no other part of the program can directly access this data except through those functions.

Key OOP Concepts in Java

Java is built on the foundation of Object-Oriented Programming. Understanding key OOP concepts such as inheritance, encapsulation, abstraction, and polymorphism is critical to writing scalable and maintainable Java code.

Prerequisites

Before diving into the pillars of OOP, let’s review the essentials of method declaration and message passing in Java.

Method Declaration

A method in Java consists of six main components:

1. Access Modifier: Defines the accessibility of the method within the application. The common types are:

  • public: Accessible in all classes.
  • protected: Accessible within the package and in subclasses.
  • private: Accessible only within the class.
  • Default (no modifier): Accessible only within the same package.

2. Return Type: Specifies the data type returned by the method, or void if no value is returned.
3. Method Name: Follows the same naming conventions as fields, but typically starts with a lowercase letter.
4. Parameter List: The input parameters (if any), defined as a comma-separated list within parentheses.
5. Exception List: Specifies any exceptions that the method might throw.
6. Method Body: Contains the block of code that is executed when the method is called.

Message Passing

Objects in Java communicate with one another by sending messages, which involve calling methods. A message consists of the object’s name, the method to be executed, and any required information.

Java OOP Concepts

OOP in Java is centered around several key concepts:

1. Class: A class is a blueprint or prototype that defines the properties and behaviors common to all objects of a specific type. A class typically consists of fields (attributes) and methods (behaviors). In Java, a class can be defined as:

				
					public class Employee {
    // Fields
    String name;
    int salary;

    // Methods
    void setDetails(String n, int s) {
        name = n;
        salary = s;
    }

    void displayDetails() {
        System.out.println("Name: " + name);
        System.out.println("Salary: " + salary);
    }
}

				
			

2. Object: An object is an instance of a class. It represents a specific entity in a Java program, with its own state and behavior.

				
					public class Main {
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setDetails("John Doe", 50000);
        emp.displayDetails();
    }
}

				
			

Output:

				
					Name: John Doe
Salary: 50000

				
			
The Four Pillars of OOP

1. Abstraction: Abstraction involves hiding unnecessary details and showing only the essential features of an object. In Java, abstraction is achieved using abstract classes and interfaces.

Example of an abstract class:

				
					abstract class Vehicle {
    abstract void start();
}

class Car extends Vehicle {
    void start() {
        System.out.println("Car is starting");
    }
}

				
			

2. Encapsulation: Encapsulation is the practice of wrapping data and methods into a single unit (class). It restricts direct access to the data by making the fields private and providing public getter and setter methods to modify and access them.

				
					class Person {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

				
			

3. Inheritance: Inheritance allows a class to inherit properties and methods from another class. It promotes reusability and a hierarchical class structure. Inheritance is achieved using the extends keyword.

				
					class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    void sound() {
        System.out.println("Dog barks");
    }
}

				
			

4. Polymorphism: Polymorphism allows methods or objects to behave in different ways based on the context. It is of two types: compile-time (method overloading) and runtime (method overriding).

Method Overloading (Compile-time Polymorphism):

				
					class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

				
			
Advantages of OOP over Procedural Programming
  • Code Reusability: By using classes and objects, you can reuse code efficiently without repetition.
  • Code Organization: Object-oriented code is better structured, making it easier to understand and maintain.
  • DRY Principle: OOP promotes the “Don’t Repeat Yourself” principle by allowing common code to be reused.
  • Faster Development: OOP speeds up development by encouraging modular components that can be reused across projects.

Java Classes and Objects

In Java, classes and objects are core concepts of Object-Oriented Programming (OOP), used to represent real-world entities and behaviors. A class acts as a blueprint for creating objects, while an object is an instance of a class. For example, “Dog” can be considered a class, while a specific dog named “Bruno” would be an object of that class.

In this article, we’ll explore Java classes, objects, and how to implement them in your programs.

Java Classes

A class in Java is a collection of objects with common properties and behaviors. It is essentially a user-defined prototype from which objects are created. For instance, “Car” is a class, while “BMW” is an object.

Properties of Java Classes

  • A class is not a physical entity but a blueprint used to create objects.
  • A class itself does not occupy memory; only objects do.
  • It groups variables (attributes) and methods (behaviors) under a common structure.

A class in Java can include the following components:

  • Data members (variables)
  • Methods
  • Constructors
  • Nested Classes
  • Interfaces

Class Declaration in Java

				
					access_modifier class ClassName {
    // variables (data members)
    // methods
    // constructors
    // nested classes
    // interfaces
}

				
			

In this example, the Student class has two data members, id and name, with default values 0 and null, respectively.

Components of Java Classes

A class declaration typically contains the following:

  • Modifiers: A class can be public or have default access.
  • Class keyword: Used to declare a class.
  • Class name: Starts with an uppercase letter by convention.
  • Superclass: The parent class (if any), specified with the extends keyword.
  • Interfaces: Any implemented interfaces, separated by commas and declared with the implements keyword.
  • Body: The class body is enclosed in curly braces {}.
Constructors

Constructors initialize new objects. They have the same name as the class and no return type. Fields (data members) define the state of objects, while methods define the behavior.

Java Objects

An object in Java is an instance of a class, representing a specific entity or concept. Each object has:

  • State: Represented by the attributes (data members) of the object.
  • Behavior: Represented by the methods of the object.
  • Identity: A unique identifier that allows interaction between different objects.

For example, in a shopping system, objects could include “ShoppingCart”, “Product”, and “Customer”.

Declaring and Initializing Java Objects

When an object is created, the class is instantiated. The values of the object’s attributes (state) are unique for each instance.

				
					Dog myDog; // Declaration
myDog = new Dog(); // Instantiation

				
			

Example of an Object Initialization

				
					public class Dog {
    // Instance variables
    String name;
    String breed;
    int age;
    String color;

    // Constructor
    public Dog(String name, String breed, int age, String color) {
        this.name = name;
        this.breed = breed;
        this.age = age;
        this.color = color;
    }

    @Override
    public String toString() {
        return "My dog's name is " + name + ", breed: " + breed + ", age: " + age + ", color: " + color;
    }

    public static void main(String[] args) {
        Dog myDog = new Dog("Buddy", "Golden Retriever", 3, "Golden");
        System.out.println(myDog.toString());
    }
}

				
			

Output:

				
					My dog's name is Buddy, breed: Golden Retriever, age: 3, color: Golden

				
			
Memory Allocation in Java Objects

When an object is created, it is allocated memory on the heap. All classes have at least one constructor; if none is explicitly defined, Java provides a default constructor.

Methods to Create Objects

There are several ways to create objects in Java:

1. Using the new keyword:

				
					Car myCar = new Car();

				
			

2. Using Class.forName(String className):

				
					Car obj = (Car)Class.forName("com.example.Car").newInstance();

				
			

3. Using clone() method:

				
					Car car1 = new Car();
Car car2 = (Car)car1.clone();

				
			

4. Using Deserialization:

				
					ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Car car = (Car)in.readObject();

				
			
Anonymous Objects

Anonymous objects are objects created without a reference and are often used for immediate method calls. These objects are destroyed after the method call completes.

				
					new Car().drive();

				
			
Difference Between Class and Object
ClassObject
A blueprint for creating objects.An instance of the class.
Does not occupy memory.Occupies memory when instantiated.
Logical entity.Physical entity.
Declared only once.Multiple objects can be created from the same class.

Java Naming Conventions

In software development, especially in Java, writing clean and readable code is essential. One key aspect of this is following proper naming conventions. This practice ensures that your code is easy to read and understand, both for yourself and for other developers who may work on the same project. While at a smaller scale these conventions may not seem critical, they become increasingly important in large, industrial-level projects where maintaining clean, organized code can significantly reduce development time and confusion.

For example, when naming a variable meant to store displacement, a meaningful name like “displacement” should be used, rather than something unclear like “x” or “d”. As the project grows, meaningful names enhance readability and reduce errors.

Key Naming Guidelines
  • Classes: When naming a class, it should be a noun, reflecting the purpose it serves in the program. The name should describe what the class is about without needing to read its contents. Examples of good class names include AddNumbers, ReverseString, etc. Poor names would be generic or meaningless like A1 or Programming.
  • Interfaces: Interfaces usually describe capabilities and are often named with adjectives. For example, common Java interfaces like Runnable and Serializable describe behavior. It’s also a good practice to use the suffix “able” (e.g., Readable), though it’s not mandatory.
  • Methods: Methods, being actions, should be named as verbs. Their names should clearly describe what they do. For example, the main() method in Java signifies the starting point of the program and is a good method name conventionally.
  • Constants: Constants are fixed values and should be named in uppercase with words separated by underscores (_). Examples include PI, MAX_VALUE, MIN_VALUE.
Java Naming Conventions Overview

In Java, it is considered best practice to name classes, variables, and methods according to their intended functionality. This approach enhances the readability and maintainability of the code. Java follows the CamelCase convention for naming classes, methods, and variables, where words are written together without spaces, and each word after the first starts with an uppercase letter. For example, firstName or calculateArea.

However, there are a few exceptions:

  • Packages: Package names should be written in all lowercase, even if they contain multiple words.
  • Constants: Constants should be written in uppercase, with words separated by underscores.
Naming Conventions for Specific Java Elements

1. Classes and Interfaces

  • Classes: Class names should be nouns, using CamelCase, with each internal word starting with a capital letter. Avoid abbreviations or acronyms, and instead use meaningful words.Example: Student, Integer, DataProcessor
  • Interfaces: Interfaces should follow the same convention as classes, but should often reflect actions or behaviors, typically in the form of adjectives.Example: Runnable, Serializable

2. Methods

  • Method Names: Methods should be verbs, following the camelCase convention. The first word is in lowercase, and each subsequent word starts with an uppercase letter.Example: calculateTotal, findMaximum, printDetails

3. Variables

  • Variable Names: Variables should have meaningful names and be short but descriptive. They should not start with an underscore (_) or dollar sign ($), even though Java permits it. Single-character variable names (like i, j, k) should generally be reserved for temporary variables in loops or similar short-term uses.Example: int score, double interestRate, String username

4. Constants

  • Constant Names: Constants are named in all uppercase letters, with words separated by underscores (_).Example: MAX_LIMIT, PI, DEFAULT_TIMEOUT
5. Packages
  • Package Names: Package names should always be written in lowercase, with each part separated by dots (.). The first part of the package name should usually be the top-level domain name (e.g., comorg), followed by the company’s domain and additional parts.Example: com.example.projectnameorg.utilities.math
Importance of Naming Conventions

Adopting proper naming conventions improves code clarity, making it easier for other developers to understand the purpose of each class, method, variable, and constant without having to examine the underlying implementation. Following these guidelines also helps avoid confusion and errors in larger codebases. Furthermore, adhering to established naming conventions is a sign of professionalism and helps teams maintain a consistent coding style across projects.

These conventions, though simple, are critical in producing clean, readable, and maintainable Java code—allowing teams to work more effectively and reducing the chances of misinterpretation or mistakes during development.

Methods in Java

A method in Java is a collection of statements designed to perform a specific task and, optionally, return a result. Methods can also execute tasks without returning any value. By using methods, Java allows code reuse, eliminating the need to rewrite similar logic multiple times. Unlike languages such as C, C++, or Python, every method in Java must belong to a class.

Key Points about Methods:
  • A method is similar to a function and is used to describe the behavior of an object.
  • It is a block of code that performs a particular task.

Syntax of a Method:

				
					<access_modifier> <return_type> <method_name>(list_of_parameters) {
    // body of the method
}

				
			
Advantages of Methods:
  • Code Reusability: Methods allow code to be written once and reused as needed, saving time and effort.
  • Code Optimization: Methods simplify complex operations and improve the structure of the code.
Method Declaration

A method declaration generally has six components:

1. Modifier: Defines the access level of the method (e.g., public, private, protected, default).

  • public: Accessible in all classes.
  • protected: Accessible within the class and its subclasses.
  • private: Accessible only within the defining class.
  • default: Accessible within the same class and package, with no modifier.

2. Return Type: Specifies the data type of the value returned by the method or void if no value is returned.
3. Method Name: The name given to the method, following the same rules as variable names but typically being a verb. It is required.
4. Parameter List: A comma-separated list of input parameters (preceded by their data types). If no parameters are needed, empty parentheses are used. This is optional.
5. Exception List: Specifies any exceptions the method can throw. This is optional.
Method Body: The actual code executed by the method, enclosed in braces. This is also optional.

Types of Methods in Java

1. Predefined Methods: These are methods already provided by Java libraries, also known as standard or built-in methods. We can directly call these methods within our program.

2. User-defined Methods: Methods written by the programmer, tailored to specific tasks according to the program’s needs.

Ways to Create Methods in Java

1. Instance Methods: Access instance data using the object name. These methods are declared within a class.

				
					void methodName() {
    // method body
}

				
			

2. Static Methods: Access static data using the class name. These methods are declared with the static keyword.

				
					static void methodName() {
    // method body
}

				
			
Method Signature

A method signature consists of the method name and its parameter list (the number, type, and order of parameters). The return type and exceptions are not part of the method signature.

Example:

				
					int max(int x, int y)  // Method signature

				
			

In this example, the method max has two parameters of type int.

Naming a Method

The convention for naming methods in Java is to use verbs in lowercase or multi-word names starting with a verb. If the name contains multiple words, the first letter of each word after the first should be capitalized (CamelCase).

Rules:

  • Method names should be verbs and start with a lowercase letter.
  • If the method name contains more than one word, the first word must be a verb, followed by an adjective or noun.
  • In multi-word names, capitalize the first letter of each subsequent word (e.g., findMax, computeSum).
Method Calling

Methods need to be called to execute. They can be called in three situations:

  • The method completes all its statements.
  • The method reaches a return statement.
  • The method throws an exception.

Example:

				
					class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        int result = calc.add(5, 10);
        System.out.println("Sum: " + result);
    }
}

				
			

Output:

				
					Sum: 15

				
			
Passing Parameters to Methods

Parameters can be passed in two ways:

1. Passing Arrays: Arrays can be passed as arguments to methods.

2. Varargs (Variable Arguments): When the number of parameters is unknown, we can use the varargs feature by specifying the parameter type followed by an ellipsis (...).

Method Overloading

Method overloading allows multiple methods with the same name but different parameter lists to coexist in the same class. This enables methods to handle various types of inputs.

Memory Allocation for Methods

Method calls are implemented using a stack. When a method is invoked, a stack frame is created to store its arguments, local variables, and return value. Once the method completes, its stack frame is removed from the stack.

Example:

				
					class Example {
    private int number;
    private String name;

    // Getter and Setter methods
    public int getNumber() { return number; }
    public String getName() { return name; }

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

    // Other methods
    public void printDetails() {
        System.out.println("Number: " + number);
        System.out.println("Name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Example example = new Example();
        example.setNumber(42);
        example.setName("Java Example");
        example.printDetails();
    }
}

				
			

Output:

				
					Number: 42
Name: Java Example

				
			

Access Modifiers in Java

Access modifiers in Java are essential for controlling the visibility and accessibility of classes, constructors, methods, variables, and data members. They provide a way to enforce security and manage how components of a program can interact with each other based on the access modifier used. In this section, we’ll explore the types of access modifiers available in Java, along with examples that demonstrate their use.

Types of Access Modifiers in Java

There are four access modifiers in Java:

1. Default (No keyword required)
2. Private
3. Protected
4. Public

1. Default Access Modifier

If no access modifier is specified, the default modifier is used. Members with the default access modifier are accessible only within the same package.

Example:

				
					// Java program demonstrating default access modifier
package package1;

class ExampleClass {
    void showMessage() {
        System.out.println("This is a default access modifier example.");
    }
}

				
			
				
					// Attempting to access a default class from another package
package package2;

import package1.*;

public class TestClass {
    public static void main(String[] args) {
        ExampleClass example = new ExampleClass(); // Compile-time error
        example.showMessage();
    }
}

				
			

Output:

				
					Compile-time error

				
			

2. Private Access Modifier

The private keyword is used to restrict access to class members. Members declared as private are accessible only within the class where they are defined.

Example:

				
					// Java program illustrating private access modifier
package package1;

class ExampleClass {
    private void displayMessage() {
        System.out.println("This is a private method.");
    }
}

public class TestClass {
    public static void main(String[] args) {
        ExampleClass example = new ExampleClass();
        example.displayMessage();  // Compile-time error
    }
}

				
			

Output:

				
					error: displayMessage() has private access in ExampleClass
        example.displayMessage();

				
			

3. Protected Access Modifier

The protected keyword allows access to members within the same package and also in subclasses, even if they are in different packages.

Example:

				
					// Package 1 - Parent class with protected method
package package1;

public class ParentClass {
    protected void displayMessage() {
        System.out.println("This is a protected method.");
    }
}

				
			
				
					// Package 2 - Subclass inheriting from ParentClass
package package2;

import package1.ParentClass;

public class ChildClass extends ParentClass {
    public static void main(String[] args) {
        ChildClass child = new ChildClass();
        child.displayMessage();
    }
}

				
			

Output:

				
					This is a protected method.

				
			

4. Public Access Modifier

The public keyword gives the widest access. Members declared as public can be accessed from any class, in any package.

Example:

				
					// Java program demonstrating public access modifier
package package1;

public class ExampleClass {
    public void showMessage() {
        System.out.println("This is a public method.");
    }
}

				
			
				
					// Accessing a public method from another package
package package2;

import package1.ExampleClass;

public class TestClass {
    public static void main(String[] args) {
        ExampleClass example = new ExampleClass();
        example.showMessage();
    }
}

				
			

Output:

				
					This is a public method.

				
			
Key Points:
  • Default access is package-private, meaning it’s accessible only within the same package.
  • Private access is the most restrictive, limiting access only to the class in which the member is declared.
  • Protected access allows visibility in the same package and in subclasses across different packages.
  • Public access provides unrestricted access throughout the program.
Best Practices for Using Access Modifiers
  • Always prefer the most restrictive access level for class members. For example, use private unless there’s a specific reason to expose a member.
  • Avoid using public fields, as this breaks encapsulation. Instead, use getter and setter methods to control access to fields.
Algorithm for Using Access Modifiers in Java

1. Define the class: Create a class representing your object or functionality.
2. Declare instance variables: Define the data members that represent the object’s state.
3. Choose access modifiers:

  • Use private for variables that should not be accessible outside the class.
  • Use protected for variables that can be accessed by subclasses, even in different packages.
  • Use public for variables that need to be accessed from any part of the program.
    Implement getter and setter methods: Even for public variables, it’s good practice to use accessors and mutators to control how values are accessed and modified.

4. Test visibility: Ensure that your chosen access levels make sense for the structure and interaction of your classes.

Java Constructors

In Java, a constructor is a special type of method used to initialize an object. Constructors are called when an object of a class is created, allowing developers to set initial values for the object’s attributes.

Whenever you create an object using the new keyword, a constructor is invoked to allocate memory for the object and initialize it.

Understanding constructors can greatly enhance your Java programming skills, particularly when working with large applications. Mastery of constructors is crucial for developing scalable, maintainable code, serving as a stepping stone toward more advanced programming concepts.

Example of a Java Constructor

Below is a simple demonstration of how constructors work in Java:

				
					// Java Program to demonstrate
// Constructor usage
public class MyClass {

    // Constructor
    MyClass() {
        System.out.println("Constructor called");
    }

    public static void main(String[] args) {
        // Creating an object which calls the constructor
        MyClass obj = new MyClass();
    }
}

				
			

Output:

				
					Constructor called

				
			
Differences Between Constructors and Methods
  • Name: A constructor must have the same name as the class, whereas methods can have any name.
  • Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
  • Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.

Syntax Example of a constructor:

				
					class MyClass {
    // A constructor
    MyClass() {
        // Initialization code here
    }
}

// Creating an object, which invokes the constructor
MyClass obj = new MyClass();

				
			

Output:

				
					Constructor called

				
			
Differences Between Constructors and Methods
  • Name: A constructor must have the same name as the class, whereas methods can have any name.
  • Return Type: Constructors do not have a return type, while methods can return values (or be void if they return nothing).
  • Invocation: Constructors are called only once when an object is created, whereas methods can be called multiple times.

Syntax example of a constructor:

				
					class MyClass {
    // A constructor
    MyClass() {
        // Initialization code here
    }
}

// Creating an object, which invokes the constructor
MyClass obj = new MyClass();

				
			
Why Do We Need Constructors?

Constructors are used to initialize objects at the time of creation. For example, if we think of a class that represents a box, it might have fields like length, breadth, and height. When an object of this class is created, it wouldn’t make sense for it to have undefined dimensions, so constructors ensure these values are set.

When Is a Constructor Called?

A constructor is called whenever an object is instantiated using the new keyword. Some rules for writing constructors include:

  • The constructor must have the same name as the class.
  • A constructor cannot be abstract, final, static, or synchronized.
  • Access modifiers can be used with constructors to control which classes can instantiate objects.
Types of Constructors in Java

There are three types of constructors in Java:

1. Default Constructor
2. Parameterized Constructor
3. Copy Constructor

1. Default Constructor : A default constructor is one without parameters. If no constructor is defined explicitly, Java automatically provides a default constructor.

Example:

				
					// Java Program to demonstrate Default Constructor
public class MyClass {
    
    // Default Constructor
    MyClass() {
        System.out.println("Default constructor called");
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
    }
}

				
			

Output:

				
					Default constructor called

				
			

2. Parameterized Constructor : A parameterized constructor takes arguments, allowing you to set object properties with specific values at the time of creation.

Example:

				
					// Java Program to demonstrate Parameterized Constructor
public class MyClass {
    String name;
    int id;

    // Parameterized Constructor
    MyClass(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass("John", 101);
        System.out.println("Name: " + obj.name + ", ID: " + obj.id);
    }
}

				
			

Output:

				
					Name: John, ID: 101

				
			

3. Copy Constructor : A copy constructor is used to create a new object as a copy of an existing object. While Java does not provide a built-in copy constructor, you can create one manually by passing an object of the same class to the constructor.

Example:

				
					// Java Program to demonstrate Copy Constructor
public class MyClass {
    String name;
    int id;

    // Parameterized Constructor
    MyClass(String name, int id) {
        this.name = name;
        this.id = id;
    }

    // Copy Constructor
    MyClass(MyClass obj) {
        this.name = obj.name;
        this.id = obj.id;
    }

    public static void main(String[] args) {
        // Creating an object using a parameterized constructor
        MyClass obj1 = new MyClass("Alice", 202);

        // Creating a new object using the copy constructor
        MyClass obj2 = new MyClass(obj1);

        System.out.println("Name: " + obj2.name + ", ID: " + obj2.id);
    }
}

				
			

Output:

				
					Name: Alice, ID: 202
				
			
Constructor Overloading

Just like methods, constructors can also be overloaded by defining multiple constructors with different parameter lists. This allows for flexibility when creating objects with varying initial values.

Example:

				
					// Java Program to demonstrate Constructor Overloading
public class MyClass {

    // Constructor with one parameter
    MyClass(String name) {
        System.out.println("Name: " + name);
    }

    // Constructor with two parameters
    MyClass(String name, int age) {
        System.out.println("Name: " + name + ", Age: " + age);
    }

    public static void main(String[] args) {
        // Calling different constructors
        MyClass obj1 = new MyClass("Alice");
        MyClass obj2 = new MyClass("Bob", 25);
    }
}

				
			

Output:

				
					Name: Alice
Name: Bob, Age: 25

				
			

Inheritance in Java

Java Inheritance Overview

Inheritance is one of the core concepts in Object-Oriented Programming (OOP), allowing a class to inherit properties and behaviors (fields and methods) from another class. In Java, this mechanism enables the creation of new classes that reuse, extend, and modify the functionality of existing ones. A class that inherits from another class can access the fields and methods of the parent class, while also adding its own methods and fields.

Why Do We Need Java Inheritance?

Inheritance offers several benefits, such as promoting code reusability, method overriding (which helps in achieving runtime polymorphism), and abstraction, allowing us to work with classes without needing to know the full details of their implementations. By leveraging inheritance, developers can create flexible and easily maintainable code.

Key Benefits:
  • Code Reusability: Common code in the superclass can be reused in the child classes, reducing redundancy.
  • Method Overriding: Enables the child class to provide specific implementations for methods defined in the parent class, facilitating runtime polymorphism.
  • Abstraction: Hides complex details from the user and only shows relevant functionalities.
Important Terms
  • Class: A blueprint for creating objects that share common attributes and behaviors.
  • Super Class (Parent Class): The class whose properties are inherited by another class.
  • Sub Class (Child Class): The class that inherits from another class. It can have additional fields and methods specific to itself.
  • Reusability: One of the major advantages of inheritance, where existing fields and methods from a parent class can be used in the child class without duplicating code.
How to Implement Inheritance in Java?

Inheritance in Java is implemented using the extends keyword. This keyword allows the subclass to extend the functionality of the superclass.

Syntax:

				
					class SubClass extends SuperClass {  
   // fields and methods  
}

				
			

Example of Java Inheritance

In this example, we have a base class Vehicle and a derived class Car that extends the Vehicle class. The TestDrive class is used to demonstrate the usage of inheritance.

				
					// Base class
class Vehicle {
    public int speed;
    public int fuel;

    // Constructor
    public Vehicle(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    // Methods
    public void accelerate(int increment) {
        speed += increment;
    }

    public void brake(int decrement) {
        speed -= decrement;
    }

    @Override
    public String toString() {
        return "Speed: " + speed + " km/h, Fuel: " + fuel + " L";
    }
}

// Derived class
class Car extends Vehicle {
    public int numberOfDoors;

    // Constructor
    public Car(int speed, int fuel, int numberOfDoors) {
        super(speed, fuel); // Calling the constructor of the superclass
        this.numberOfDoors = numberOfDoors;
    }

    // Additional method for the Car class
    public void lockDoors() {
        System.out.println("Doors locked.");
    }

    @Override
    public String toString() {
        return super.toString() + ", Doors: " + numberOfDoors;
    }
}

// Driver class
public class TestDrive {
    public static void main(String[] args) {
        Car myCar = new Car(80, 50, 4);
        System.out.println(myCar.toString());
    }
}

				
			

Output:

				
					Speed: 80 km/h, Fuel: 50 L, Doors: 4

				
			

In this example, when an object of Car is created, the fields and methods from the Vehicle class are inherited, allowing the car to access properties like speed and fuel. The toString() method is overridden in the Car class to display additional information about the car.

Types of Inheritance in Java

Java supports several types of inheritance, each with its own structure and use cases:

1. Single Inheritance: A subclass inherits from one superclass.
2. Multilevel Inheritance: A chain of classes where a subclass inherits from a superclass, and another class inherits from that subclass.
3. Hierarchical Inheritance: One superclass is inherited by multiple subclasses.
4. Multiple Inheritance (Through Interfaces): In Java, a class can implement multiple interfaces, which allows it to inherit features from multiple sources. Java doesn’t support multiple inheritance with classes directly to avoid complexity, but interfaces provide a way to achieve this.
5. Hybrid Inheritance: A combination of multiple and other forms of inheritance. Java supports this through interfaces and combinations of single and hierarchical inheritance.
Example:

In the following example, Dog is a subclass that inherits from the Animal superclass.

				
					// Superclass
class Animal {
    public void sound() {
        System.out.println("This is an animal sound.");
    }
}

// Subclass
class Dog extends Animal {
    public void bark() {
        System.out.println("The dog barks.");
    }
}

// Driver class
public class TestAnimal {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.sound();  // Inherited from Animal
        myDog.bark();   // Defined in Dog
    }
}

				
			

Output:

				
					This is an animal sound.
The dog barks.

				
			

Abstraction in Java

Abstraction in Java refers to the practice of showing only the necessary details and functionalities to the user while hiding the underlying implementation. By doing so, unnecessary complexities are kept out of sight, making it easier to work with the code.

Real-World Example of Abstraction:

A remote control for a TV is a great example of abstraction. You press buttons to change channels or adjust the volume, without needing to understand the inner workings of the TV or how the remote communicates with it.

What is Abstraction in Java?

In Java, abstraction is implemented using abstract classes and interfaces. If you want complete (100%) abstraction, interfaces are the tool of choice.

Data Abstraction is the process of focusing on the important characteristics of an object while ignoring irrelevant details. It helps in defining the key behaviors and properties of an object, distinguishing it from others in a meaningful way.

Real-Life Example of Abstraction:

Imagine driving a car. You know pressing the accelerator increases the speed, and pressing the brakes slows it down, but you don’t need to understand the detailed mechanism behind it. This is how abstraction simplifies interaction.

Java Abstract Classes and Abstract Methods
  • An abstract class is defined with the abstract keyword.
  • An abstract method is declared without any implementation.
  • An abstract class can have both abstract methods and regular methods.
  • If a class contains abstract methods, it must be declared as abstract.
  • Abstract classes cannot be instantiated directly; they must be extended.
  • Abstract classes can have constructors, both parameterized and default.
How to Implement Abstraction in Java

1. Define an abstract class or interface to represent the abstraction.
2. Declare common behaviors and properties as abstract methods within the class or interface.
3. Extend the abstract class or implement the interface in concrete classes.
4. Provide the specific implementations for the abstract methods in the concrete classes.
5. Use these concrete classes to achieve the desired functionality in your program.

When to Use Abstract Classes and Methods?

Abstract classes are useful when you want to define a template that outlines the structure of an abstraction but leave some details to be implemented by subclasses. A common example is the Shape hierarchy in design or simulation software, where shapes like circles, squares, and triangles share some characteristics but differ in how they calculate their areas.

Java Abstraction Example

Example 1:

				
					// Abstract class for Shape
abstract class Shape {
    String color;

    // Abstract methods
    abstract double area();
    public abstract String toString();

    // Constructor for Shape
    public Shape(String color) {
        this.color = color;
    }

    // Concrete method
    public String getColor() {
        return color;
    }
}

// Circle class extending Shape
class Circle extends Shape {
    double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * Math.pow(radius, 2);
    }

    @Override
    public String toString() {
        return "Circle color is " + getColor() + " and area is: " + area();
    }
}

// Rectangle class extending Shape
class Rectangle extends Shape {
    double length, width;

    public Rectangle(String color, double length, double width) {
        super(color);
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }

    @Override
    public String toString() {
        return "Rectangle color is " + getColor() + " and area is: " + area();
    }
}

// Main class
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle("Red", 5.0);
        Shape rectangle = new Rectangle("Blue", 4.0, 6.0);

        System.out.println(circle.toString());
        System.out.println(rectangle.toString());
    }
}

				
			

Output:

				
					Circle color is Red and area is: 78.53981633974483
Rectangle color is Blue and area is: 24.0

				
			
Using Interfaces for Abstraction

Interfaces provide a way to achieve complete abstraction in Java. They contain method declarations but no method implementations.

Example:

				
					// Shape interface
interface Shape {
    double calculateArea();
}

// Circle class implementing Shape interface
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

// Rectangle class implementing Shape interface
class Rectangle implements Shape {
    private double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double calculateArea() {
        return length * width;
    }
}

// Main class
public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rectangle = new Rectangle(4.0, 6.0);

        System.out.println("Area of Circle: " + circle.calculateArea());
        System.out.println("Area of Rectangle: " + rectangle.calculateArea());
    }
}

				
			

Output:

				
					Area of Circle: 78.53981633974483
Area of Rectangle: 24.0

				
			
Advantages of Abstraction

1. Simplifies Code: Reduces complexity by hiding implementation details.
2. Increases Reusability: Encourages code reuse and avoids duplication.
3. Enhances Security: Provides essential details while hiding the rest.
4. Improves Maintainability: Easier to update and maintain.
5. Supports Modularity: Breaks down complex systems into smaller parts.

Disadvantages of Abstraction

1. Increased Complexity: May add unnecessary layers if not used properly.
2. Debugging Challenges: Can make debugging more difficult due to hidden layers.
3. Potential Performance Overhead: Additional abstraction layers might affect performance.

Encapsulation in Java

Encapsulation is a core principle of object-oriented programming (OOP) in Java. It refers to the bundling of data (variables) and methods (functions) that operate on that data into a single unit, typically a class. Encapsulation ensures that the internal implementation of a class is hidden from outside access, only exposing a public interface that allows interaction with the class through methods. This technique enhances security and data integrity, as the internal state of an object can only be modified in controlled ways.

In Java, encapsulation is achieved by declaring instance variables as private, which means they cannot be accessed directly from outside the class. To provide access to these variables, public getter and setter methods are used, which enable controlled access and modification of the private variables.

Key Aspects of Encapsulation
  • Data hiding: By using private variables, you ensure that the internal details of a class are hidden.
  • Access through methods: Public getter and setter methods allow controlled access to the variables.
  • Modularity: It promotes better design by keeping data and methods together in a single unit.
  • Security and validation: Setters can include validation logic to prevent invalid data from being assigned to variables.

Java Encapsulation Example

				
					// Employee Class demonstrating encapsulation
class Employee {
    // Private fields, not accessible outside the class
    private String name;
    private int id;

    // Getter method for name
    public String getName() {
        return name;
    }

    // Setter method for name
    public void setName(String name) {
        this.name = name;
    }

    // Getter method for id
    public int getId() {
        return id;
    }

    // Setter method for id
    public void setId(int id) {
        this.id = id;
    }
}

// Main class to test encapsulation
public class Main {
    public static void main(String[] args) {
        // Creating an object of the Employee class
        Employee emp = new Employee();
        
        // Using setter methods to assign values
        emp.setName("Alice");
        emp.setId(101);

        // Using getter methods to retrieve and display values
        System.out.println("Employee Name: " + emp.getName());
        System.out.println("Employee ID: " + emp.getId());
    }
}

				
			

Output:

				
					Employee Name: Alice
Employee ID: 101

				
			
Advantages of Encapsulation

Data Hiding: Encapsulation prevents direct access to sensitive data, making it more secure.

1. Improved Flexibility: You can make instance variables read-only or write-only by providing only a getter or setter method, depending on the requirement.
2. Reusability: Encapsulated code is easier to maintain, reuse, and extend because it minimizes interdependencies.
3. Easy Testing: Since encapsulated code separates data and functions, it is easier to test individual components.
4. Adaptability: The internal implementation can change without affecting code that interacts with the class.

Disadvantages of Encapsulation
  • Increased Complexity: Encapsulation may lead to more boilerplate code, such as writing getter and setter methods for each private variable.
  • Difficult to Understand: Excessive use of encapsulation can make code more difficult to follow, especially if it adds layers of abstraction.
  • Limited Flexibility: Over-encapsulation can sometimes restrict the flexibility of your design, making it harder to quickly implement new features.
Java Encapsulation Example (Area Calculation)

Here’s another example that demonstrates encapsulation in a different context, where it is used to calculate the area of a rectangle:

				
					// Rectangle Class to calculate area using encapsulation
class Rectangle {
    // Private fields for length and breadth
    private int length;
    private int breadth;

    // Constructor to initialize length and breadth
    public Rectangle(int length, int breadth) {
        this.length = length;
        this.breadth = breadth;
    }

    // Getter for length
    public int getLength() {
        return length;
    }

    // Setter for length
    public void setLength(int length) {
        this.length = length;
    }

    // Getter for breadth
    public int getBreadth() {
        return breadth;
    }

    // Setter for breadth
    public void setBreadth(int breadth) {
        this.breadth = breadth;
    }

    // Method to calculate and print area
    public void calculateArea() {
        int area = length * breadth;
        System.out.println("Area of the Rectangle: " + area);
    }
}

// Main class to test encapsulation in rectangle class
public class Main {
    public static void main(String[] args) {
        // Creating a rectangle object
        Rectangle rect = new Rectangle(5, 10);
        
        // Accessing and modifying private fields via getters and setters
        rect.setLength(8);
        rect.setBreadth(12);
        
        // Calculating and printing the area
        rect.calculateArea();
    }
}

				
			

Output:

				
					Area of the Rectangle: 96

				
			

What are Interfaces in Java?

In Java, an interface is an abstract type used to specify a set of behaviors that a class must implement. It provides a blueprint for a class, defining static constants and abstract methods (methods without a body). An interface is primarily used to achieve abstraction and multiple inheritance. Traditionally, interfaces in Java could only contain abstract methods and final variables, but with the introduction of Java 8, interfaces can also contain default and static methods. Interfaces help define a “contract” or “is-a” relationship between classes, specifying the methods they must implement, without worrying about the internal details.

Syntax for Java Interfaces

To declare an interface in Java, use the interface keyword. By default, all methods declared in an interface are abstract and public, and all fields are public, static, and final.

				
					interface InterfaceName {
    // Declare constant fields
    // Declare abstract methods (no implementation)
}

				
			

A class that implements an interface must provide implementations for all the methods declared in the interface, using the implements keyword.

Features of Interfaces

1. Achieves total abstraction: An interface allows complete abstraction since it contains no method implementation.
2. Multiple inheritance: While Java does not support multiple inheritance with classes, an interface allows classes to implement multiple interfaces.
3. Loose coupling: Interfaces enable loose coupling between classes as they provide a level of abstraction.
4. Final variables: All variables declared in an interface are final, static, and public by default.

Relationship Between Class and Interface

In Java, a class can implement one or more interfaces, but an interface can only be implemented by a class, not the other way around. Classes must implement all the methods defined by the interfaces they implement. Additionally, interfaces can extend other interfaces, allowing for a flexible hierarchy of behavior definitions.

Example of a Simple Java Interface

				
					interface Player {
    int id = 10; // Constant by default
    void move(); // Abstract method
}

				
			
				
					class TestClass implements Player {
    public void move() {
        System.out.println("Player moves");
    }

    public static void main(String[] args) {
        TestClass player = new TestClass();
        player.move();
        System.out.println("Player ID: " + Player.id);
    }
}

				
			

Output:

				
					Player moves
Player ID: 10

				
			

Example: Vehicle Interface

Consider the example of vehicles like bicycles and cars that have common functionalities. We can create a Vehicle interface to define the general behaviors that every vehicle must implement, and let each vehicle type provide its specific implementation.

				
					interface Vehicle {
    void changeGear(int gear);
    void speedUp(int increment);
    void applyBrakes(int decrement);
}

class Bicycle implements Vehicle {
    int speed;
    int gear;

    public void changeGear(int newGear) {
        gear = newGear;
    }

    public void speedUp(int increment) {
        speed += increment;
    }

    public void applyBrakes(int decrement) {
        speed -= decrement;
    }

    public void display() {
        System.out.println("Speed: " + speed + " Gear: " + gear);
    }
}

class Car implements Vehicle {
    int speed;
    int gear;

    public void changeGear(int newGear) {
        gear = newGear;
    }

    public void speedUp(int increment) {
        speed += increment;
    }

    public void applyBrakes(int decrement) {
        speed -= decrement;
    }

    public void display() {
        System.out.println("Speed: " + speed + " Gear: " + gear);
    }
}

public class Main {
    public static void main(String[] args) {
        Bicycle bike = new Bicycle();
        bike.changeGear(2);
        bike.speedUp(10);
        bike.applyBrakes(3);
        System.out.println("Bicycle state:");
        bike.display();

        Car car = new Car();
        car.changeGear(3);
        car.speedUp(20);
        car.applyBrakes(5);
        System.out.println("Car state:");
        car.display();
    }
}

				
			

Output:

				
					Bicycle state:
Speed: 7 Gear: 2
Car state:
Speed: 15 Gear: 3

				
			
Multiple Inheritance Using Interfaces

Java doesn’t allow multiple inheritance with classes, but interfaces provide a way to achieve it. A class can implement multiple interfaces, inheriting behaviors from each one.

				
					interface Machine {
    void start();
}

interface Sound {
    void playSound();
}

class Robot implements Machine, Sound {
    public void start() {
        System.out.println("Robot started.");
    }

    public void playSound() {
        System.out.println("Robot playing sound.");
    }
}

public class Main {
    public static void main(String[] args) {
        Robot r = new Robot();
        r.start();
        r.playSound();
    }
}

				
			

Output:

				
					Robot started.
Robot playing sound.
				
			

‘this’ reference in Java

In Java, this is a reference variable that refers to the current object instance. It helps in distinguishing between instance variables and parameters with the same name and can be used for various purposes such as invoking class methods, passing the current object as a parameter, and improving code readability.

Java this Reference Example:

In Java, this refers to the object on which a method or constructor is being invoked. It allows access to the instance variables and methods of the current object.

Here’s an example demonstrating the usage of this reference:

				
					// Java program demonstrating the use of 'this' reference

class Employee {
    String name;
    int id;

    // Constructor
    Employee(String name, int id) {
        this.name = name;  // 'this' refers to the instance variable 'name'
        this.id = id;      // 'this' refers to the instance variable 'id'
    }

    // Method to display employee details
    public void displayDetails() {
        System.out.println("Employee Name: " + this.name);
        System.out.println("Employee ID: " + this.id);
    }

    // Main method
    public static void main(String[] args) {
        Employee emp1 = new Employee("John", 101);
        Employee emp2 = new Employee("Alice", 102);

        emp1.displayDetails();
        emp2.displayDetails();
    }
}

				
			

Output:

				
					Employee Name: John
Employee ID: 101

Employee Name: Alice
Employee ID: 102

				
			

Explanation:

In the above example, this is used in the constructor to refer to the instance variables name and id, differentiating them from the constructor’s parameters. The displayDetails method prints out the details of each employee using the this reference.

Different Uses of this in Java:

1. Referring to the Current Class Instance Variables :You can use this to differentiate between instance variables and parameters with the same name.

				
					class Test {
    int x;
    Test(int x) {
        this.x = x;  // 'this' is used to differentiate the instance variable from the constructor parameter
    }
}

				
			

2. Invoking the Current Class Constructor :this() can be used to call another constructor in the same class.

				
					class Test {
    int x;
    Test() {
        this(10);  // Calls the parameterized constructor
    }
    Test(int x) {
        this.x = x;
    }
}

				
			

3. Returning the Current Class Instance: this can be used to return the current instance of the class.

				
					class Test {
    int x;
    Test(int x) {
        this.x = x;
    }
    Test getObject() {
        return this;  // Returns the current object
    }
}

				
			

4. Passing the Current Class Instance as a Method Parameter: this can be passed as an argument to methods or constructors.

				
					class Test {
    void display(Test obj) {
        System.out.println("Object received: " + obj);
    }
    void sendThis() {
        display(this);  // Passes the current object as a parameter
    }
}

				
			

5. Invoking the Current Class Method:this can be used to call a method from within another method.

				
					class Test {
    void display() {
        this.show();  // Calls the show method
    }
    void show() {
        System.out.println("Inside show method");
    }
}