Java OOP Deep Dive: From Blueprints to Real-World Code (A Mega One-Short Guide)

Table of contents
- 1. Class and Objects in Java: The Blueprint and the Real Deal
- 2. Why Use OOPs in the First Place?
- 3. Methods: What Objects Can Do
- 4. Method Overloading vs. Method Overriding
- 5. The this Keyword: Referring to the Current Object
- 6. Constructors: Building Your Objects
- 7. Getter and Setter Methods: Controlled Access
- 8. The static Keyword: Class-Level Members
- 9. The 4 Musketeers of OOPs
- 10. abstract Keyword: Classes and Methods
- 11. interface: Pure Contracts
- 12. Other Important Keywords
- 13. instanceof operator
- 14. Inner Classes (Nested Classes)

Object-Oriented Programming (OOP) is more than just a buzzword; it's a powerful paradigm that shapes how we design and build robust, scalable, and maintainable software. Java, at its core, is an object-oriented language. If you're looking to master Java, understanding OOP concepts is non-negotiable.
This guide will walk you through the essential pillars and keywords of Java OOP, transforming abstract concepts into practical knowledge you can apply immediately. Let's dive in!
1. Class and Objects in Java: The Blueprint and the Real Deal
So, what's the deal with Classes and Objects?
Think of a Class like a blueprint. Just like a car company designs a blueprint before manufacturing, a class defines the structure (attributes) and behavior (methods) of future objects. It specifies everything:
For a
Car
class: how many wheels, the type of engine, dimensions, weight, available colors.And what the car can do: like
accelerate()
,brake()
,turnSteeringWheel()
.
An Object, on the other hand, is a real-world instance created from that class. If the class is a car blueprint, then the object is the actual car that you can drive. You can have multiple cars (objects) based on the same blueprint (class). For example, you might have two Car
objects of the same model:
myRedCar
: color = "Red", currentSpeed = 0myBlueCar
: color = "Blue", currentSpeed = 0 Both have the same functionality (defined by theCar
class) but can have different properties (their current state).
So What Does a Class Contain?
Each class typically includes:
Properties (fields/attributes): These are the characteristics of an object, representing its state.
For a
Car
:color
,engineType
,maxSpeed
.For a
Person
:name
,age
.
Methods (behaviors/functions): These are the actions the object can perform, often manipulating its own properties.
For a
Car
:accelerate()
,brake()
.For a
Person
:speak()
,walk()
.
Setting Properties & Calling Methods: The Dot Operator (.
)
To set or access the properties and methods of an object, Java uses the dot operator (.
). It's like saying "hey object, do this" or "give me that."
Here's a basic example:
// Define the Animal class (the blueprint)
class Animal {
// Properties (fields/attributes)
String name;
String breed;
int age;
// A simple method (behavior)
void makeSound() {
System.out.println("Animal sound");
}
}
// Main class to run our example
public class Main {
public static void main(String[] args) {
// Creating an object 'dog' of class 'Animal'
// 'new Animal()' actually creates the instance in memory
Animal dog = new Animal();
// Setting properties using the dot operator
dog.name = "Sheu";
dog.breed = "Axy"; // Corrected from "Axy"; (semicolon was misplaced in original notes)
dog.age = 5;
// Accessing properties and printing them
System.out.println("Dog's name: " + dog.name);
System.out.println("Dog's breed: " + dog.breed);
System.out.println("Dog's age: " + dog.age);
// Calling a method on the object
dog.makeSound(); // Output: Animal sound
}
}
2. Why Use OOPs in the First Place?
Object-Oriented Programming isn't just a fancy way to write code; it offers tangible benefits like....
Modularity: Your code is divided into self-contained units (classes). This makes it easier to design, maintain, debug, and test. If something goes wrong with a
Car
object, you know to look in theCar
class.Code Reuse: With features like inheritance (which we'll cover soon!), you can extend existing code without rewriting it. This means more efficiency and less redundancy.
Flexibility (Polymorphism): Objects can be treated as instances of their parent class or an interface they implement. This allows for cleaner, more adaptable code that can handle different object types in a uniform way.
Real-World Mapping: OOP helps you model real-world entities (like cars, people, bank accounts) naturally. This makes problem-solving more intuitive because your code structure can mirror the problem domain.
Encapsulation (Data Hiding): Bundling data (attributes) and methods that operate on the data within a single unit (class). It also involves protecting the internal state of an object from outside interference.
3. Methods: What Objects Can Do
Methods define the behaviors or actions an object can perform. Think of them as functions that "live" inside classes and typically act on the object's data (its properties).
Example:
class Person {
// Properties
String name;
int age;
// Method to make the person speak
void speak() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}
// Method to display details (could be combined with speak or separate)
void displayDetails() {
System.out.println(name + " is " + age + " and they are walking.");
}
}
public class PersonDemo {
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "John";
person1.age = 30;
person1.speak();// Output: Hello, my name is John and I am 30 years old.
person1.displayDetails();// Output: John is 30 and they are walking.
}
}
Types of Methods
1. Parameterized Methods
These methods accept input parameters (arguments) so they can perform operations using those values.
class Messenger {
void sendMessage(String message) { // 'message' is a parameter
System.out.println("Sending: " + message);
}
int addNumbers(int a, int b) { // 'a' and 'b' are parameters
return a + b;
}
}
public class MessengerDemo {
public static void main(String[] args) {
Messenger outlook = new Messenger();
outlook.sendMessage("Hello World!"); // "Hello World!" is an argument
int sum = outlook.addNumbers(10, 20); // 10 and 20 are arguments
System.out.println("Sum: " + sum); // Output: Sum: 30
}
}
2. Default Parameters in Java? (Method Overloading to the Rescue!)
Java does not support default parameters directly in method definitions like Python or C++. However, you can achieve similar behavior using method overloading.
Method overloading means having multiple methods in the same class with the same name but different parameter lists (different number or types of parameters). Here's how we can mimic default parameters:
class Greeter {
// Method that takes a custom message
void say(String message) {
System.out.println(message);
}
// Overloaded method without parameters - acts as the "default"
void say() {
System.out.println("Default Greeting!"); // The "default" message
}
}
public class TestGreeter {
public static void main(String[] args) {
Greeter greeter = new Greeter();
greeter.say("Hello there!"); // Calls the version with String parameter
// Output: Hello there!
greeter.say(); // Calls the version with no parameters
// Output: Default Greeting!
}
}
4. Method Overloading vs. Method Overriding
Let's clarify this common point of confusion:
Method Overloading:
Same method name.
Different parameters (number, type, or order of types).
Within the same class.
Resolved at compile-time (static polymorphism).
Purpose: Provide multiple ways to call a method with different inputs.
eg area of circle triangle etc as difft shape have diff formulas
Method Overriding:
Same method name.
Same parameters (number, type, and order).
In a subclass providing a different implementation of a method defined in its superclass.
Resolved at run-time (dynamic polymorphism).
Purpose: Allow a subclass to provide a specific implementation of a method that is already provided by its superclass.
(We'll dive deeper into overriding when we get to Inheritance.)
5. The this
Keyword: Referring to the Current Object
The this
keyword is a reference variable in Java that refers to the current object — the object whose method or constructor is being called. Why is it useful?
Distinguish Instance Variables from Parameters: When a method parameter or a local variable has the same name as an instance variable,
this
helps to disambiguate.this.variableName
refers to the instance variable, whilevariableName
(withoutthis
) refers to the parameter or local variable.Passing the Current Object as an Argument: You can pass the current object as an argument to another method using
this
.Calling Another Constructor from a Constructor (Constructor Chaining):
this()
can be used to call another constructor of the same class.
class Student {
String name;
int id;
// Constructor using 'this' to differentiate instance variable from parameter
public Student(String name, int id) {
// 'this.name' is the instance variable
// 'name' is the parameter
this.name = name;
// 'this.id' is the instance variable
// 'id' is the parameter
this.id = id;
System.out.println("Student object created: " + this.name);
}
void display() {
// Here, 'this.id' and 'id' would refer to the same instance variable
// 'this' is often optional if there's no ambiguity
System.out.println("ID: " + this.id + ", Name: " + this.name);
}
void updateName(String name) {
// Using 'this' to clearly set the instance variable 'name'
this.name = name;
}
}
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student("Alice", 101); // Output during construction: Student object created: Alice
s1.display(); // Output: ID: 101, Name: Alice
Student s2 = new Student("Bob", 102); // Output during construction: Student object created: Bob
s2.display(); // Output: ID: 102, Name: Bob
s2.updateName("Robert");
s2.display(); // Output: ID: 102, Name: Robert
}
}
6. Constructors: Building Your Objects
Maybe to construct/build something? So what is a constructor? Exactly! In OOPs, we can't build any object without a constructor. A constructor is a special method that is called automatically when an object of a class is created (using the new
keyword). Its main job is to initialize the object's state (its properties).
Key characteristics of a constructor:
It has the same name as the class.
It has no explicit return type (not even
void
).
The Default Constructor
But wait, in our first Animal
example, we didn't write any constructor, but we could still create objects! So then what?
Well, Java (and C++) are helpful. If you don't provide any constructor in your class, the compiler provides a default constructor for you.
This default constructor takes no arguments.
It usually initializes instance variables to their default values (0 for numbers,
null
for objects,false
for booleans).
Defining Our Own Constructor
Let's try defining our own:
class Person {
String name;
int age;
// This is our constructor
// It has the same name as the class and no return type.
public Person() {
System.out.println("Creating an object of Person...");
// We can initialize properties here
name = "Default Name";
age = 0;
}
void speak() {
System.out.println(name + " says: I can Speak");
}
void tell(String whatToSay) {
System.out.println(name + " says: " + whatToSay);
}
}
public class ConstructorDemo {
public static void main(String[] args) {
// When 'new Person()' is called, the Person() constructor executes.
Person shiv = new Person(); // Output: Creating an object of Person...
shiv.speak(); // Output: Default Name says: I can Speak
shiv.tell("Hello!"); // Output: Default Name says: Hello!
System.out.println(shiv.name + " is " + shiv.age); // Output: Default Name is 0
}
}
Parameterized Constructor
Constructors, like methods, can also be parameterized. This is super useful for initializing an object with specific values right when it's created.
Basically a constructor that accepts arguments.
and what's the are benefits ??
class Person {
String name;
int age;
// This is a parameterized constructor
public Person(String n, int a) { // 'n' and 'a' are parameters
System.out.println("Creating an object with name and age...");
// Assigning the passed name to the object's name
name = n; // Or using 'this.name = n;' for clarity
// Assigning the passed age to the object's age
age = a; // Or using 'this.age = a;'
}
// Overloaded constructor (no-argument)
public Person() {
System.out.println("Creating an object with default values...");
name = "No Name";
age = -1; // Indicate not set
}
void displayDetails() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class ParameterizedConstructorDemo {
public static void main(String[] args) {
// Creating an object using the parameterized constructor
// The note had 'Person shiv = new Person(name: "Shiv");' which isn't valid Java
// for passing arguments. It should be positional.
Person shiv = new Person("Shiv", 25); // Output: Creating an object with name and age...
shiv.displayDetails(); // Output: Name: Shiv, Age: 25
Person anjali = new Person("Anjali", 30); // Output: Creating an object with name and age...
anjali.displayDetails(); // Output: Name: Anjali, Age: 30
Person unknown = new Person(); // Output: Creating an object with default values...
unknown.displayDetails(); // Output: Name: No Name, Age: -1
}
}
Constructor Overloading
Just like methods, you can have multiple constructors in the same class, as long as they have different parameter lists (different number or types of arguments). This is called constructor overloading. It's a type of polymorphism.
In one class, we can have many constructors.
class Box {
double width;
double height;
double depth;
// Constructor 1: No arguments (default-like behavior if we define it)
public Box() {
System.out.println("Box object created with default dimensions (0).");
this.width = this.height = this.depth = 0; // Or some sensible defaults
}
// Constructor 2: Takes one argument (assumes a cube)
public Box(double side) {
System.out.println("Box object created as a cube.");
this.width = this.height = this.depth = side;
}
// Constructor 3: Takes three arguments
public Box(double w, double h, double d) {
System.out.println("Box object created with specific dimensions.");
this.width = w;
this.height = h;
this.depth = d;
}
void displayVolume() {
System.out.println("Volume is " + (width * height * depth));
}
}
public class ConstructorOverloadingDemo {
public static void main(String[] args) {
Box box1 = new Box(); // Calls Constructor 1
box1.displayVolume(); // Output: Volume is 0.0
Box box2 = new Box(10); // Calls Constructor 2
box2.displayVolume(); // Output: Volume is 1000.0
Box box3 = new Box(3, 4, 5); // Calls Constructor 3
box3.displayVolume(); // Output: Volume is 60.0
}
}
7. Getter and Setter Methods: Controlled Access
Sometimes, you want to control how the properties (fields) of your class are accessed or modified. This is a key part of Encapsulation.
Often, you'll make your class properties private
(we'll discuss access modifiers next) and then provide public
methods to get (read) and set (write) their values.
Getter: A method that returns the value of a private field. Conventionally named
getXxx()
for fieldxxx
(orisXxx()
for booleans).Setter: A method that sets or updates the value of a private field. Conventionally named
setXxx()
. Setters can include validation logic.
Access Modifiers
Access modifiers define the visibility/accessibility of classes, properties, and methods. Common ones are:
public
: Accessible from any other class.private
: Accessible only within its own class. This is key for encapsulation.protected
: Accessible within its own package and by subclasses (even if in different packages).Default (no modifier): Accessible only within its own package.
In simple terms: Anyone can access data members (properties) if they are public
. But if they are private
, they can only be accessed by methods within that same class.
Here's an example with getters and setters:
class Account {
private String accountNumber; // private property
private double balance; // private property
public Account(String accNum, double initialBalance) {
this.accountNumber = accNum;
if (initialBalance >= 0) {
this.balance = initialBalance;
} else {
this.balance = 0;
System.out.println("Initial balance cannot be negative. Set to 0.");
}
}
// Getter for accountNumber (read-only after construction)
public String getAccountNumber() {
return this.accountNumber;
}
// Getter for balance
public double getBalance() {
return this.balance;
}
// Setter for balance (e.g., deposit)
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
System.out.println(amount + " deposited. New balance: " + this.balance);
} else {
System.out.println("Deposit amount must be positive.");
}
}
// Another "setter-like" method for withdrawal
public boolean withdraw(double amount) {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
System.out.println(amount + " withdrawn. New balance: " + this.balance);
return true;
} else if (amount > this.balance) {
System.out.println("Insufficient funds for withdrawal of " + amount);
return false;
} else {
System.out.println("Withdrawal amount must be positive.");
return false;
}
}
}
public class BankDemo {
public static void main(String[] args) {
Account myAccount = new Account("ACC12345", 100.0);
// System.out.println(myAccount.balance); // COMPILE ERROR! 'balance' is private
System.out.println("Account: " + myAccount.getAccountNumber()); // OK
System.out.println("Initial Balance: " + myAccount.getBalance()); // OK
myAccount.deposit(50.0); // 50.0 deposited. New balance: 150.0
myAccount.withdraw(30.0); // 30.0 withdrawn. New balance: 120.0
myAccount.withdraw(150.0); // Insufficient funds...
myAccount.deposit(-10.0); // Deposit amount must be positive.
System.out.println("Final Balance: " + myAccount.getBalance()); // Final Balance: 120.0
}
}
Why is this important (Encapsulation)?
Code Readability: Clearer how data is managed through dedicated methods.
Code Security/Integrity: Protects data from unintended direct modification. Allows for validation logic within setters (e.g.,
balance
cannot be negative,age
must be positive). You control how your data is changed.Flexibility: You can change the internal implementation of how a property is stored or calculated without affecting the code that uses the getter/setter, as long as the method signature remains the same.
8. The static
Keyword: Class-Level Members
When you put the static
keyword on a class member (a variable or a method), it means that member belongs to the class itself, not to any particular instance (object) of the class.
Static Variables (Class Variables):
Shared among all objects of that class. There's only one copy of a static variable, regardless of how many objects are created.
If one object changes a static variable, the change is visible to all other objects.
Example:
Person.populationCount
. Ifjohn.populationCount
andjane.populationCount
are accessed, they refer to the samepopulationCount
variable in thePerson
class.
Static Methods (Class Methods):
Can be called directly on the class name, without needing to create an object (e.g.,
Math.sqrt()
).Can only access static variables and other static methods of the class directly.
Cannot use the
this
keyword becausethis
refers to an instance, and static methods are not tied to any specific instance.Cannot directly access instance variables or instance methods (they would need an object reference to do so).
class Thing {
String instanceName; // Instance variable (each object has its own)
static int count = 0; // Static variable (shared by all Thing objects)
public Thing(String name) {
this.instanceName = name;
count++; // Increment static count each time a Thing is created
System.out.println(instanceName + " created. Total things: " + count);
}
// Instance method (operates on a specific object's data)
public void displayName() {
System.out.println("My instance name is: " + this.instanceName);
// Can access static members from an instance method:
// System.out.println("Current count from instance: " + count);
}
// Static method (belongs to the class)
public static void displayCount() {
System.out.println("Total things created so far: " + count);
// System.out.println("My name is: " + this.instanceName); // ERROR! Cannot use 'this' here.
// System.out.println(instanceName); // ERROR! Cannot access instance variable directly.
}
}
public class StaticDemo {
public static void main(String[] args) {
Thing.displayCount(); // Call static method on class: Total things created so far: 0
Thing t1 = new Thing("Widget"); // Widget created. Total things: 1
Thing t2 = new Thing("Gadget"); // Gadget created. Total things: 2
t1.displayName(); // My instance name is: Widget
t2.displayName(); // My instance name is: Gadget
Thing.displayCount(); // Total things created so far: 2
// Access static variable via class name (recommended)
System.out.println("Count via class: " + Thing.count); // Output: Count via class: 2
// Or via an instance (not recommended as it can be misleading, implies it's an instance var)
// System.out.println("Count via t1: " + t1.count); // Still accesses the static 'count'
}
}
Correct way to deal with static variables/methods? Usually access static members using the class name (e.g., Thing.count
, Thing.displayCount()
), not through an instance variable (like t1.count
), as the latter can be misleading.
Can't use this
with static variable/method, why? The this
keyword refers to the current instance of a class. Static variables and methods belong to the class itself, not to any specific instance. So, in a static context (like a static method or initializing a static variable), there is no "current instance" for this
to refer to.
9. The 4 Musketeers of OOPs
1. Abstraction
Abstraction means hiding complex implementation details and showing only the essential features of an object. It focuses on what an object does rather than how it does it.
Example: When you drive a car, you interact with the steering wheel, accelerator, and brakes. You don't need to know the intricate mechanics of the engine, transmission, or braking system to operate the car. The car's internal complexity is abstracted away, providing you with a simplified interface.
In Code: Abstract classes and interfaces (discussed later) are primary tools for abstraction. They define a contract of what methods should be available, but the concrete implementation can vary.
2. Encapsulation
Encapsulation is the bundling of data (attributes/properties) and the methods that operate on that data into a single unit (a class). It also often involves restricting direct access to some of an object's components, a concept known as information hiding (achieved using access modifiers like private
).
Example: A
Capsule
for medicine. The medicine (data) is contained within the capsule (object/class), and you interact with it in a controlled way (e.g., swallowing it – a method). You don't typically open it up and manipulate the raw ingredients.In Code: We achieve encapsulation by:
Declaring instance variables as
private
.Providing
public
getter and setter methods to control access and modification of these private variables. This protects the internal state of an object and ensures data integrity (e.g., a setter forage
can prevent negative values).
If we didn't use encapsulation, we might have global variables and functions scattered everywhere, making the codebase confusing, hard to manage, and prone to errors where data is changed unexpectedly.
3. Inheritance
Inheritance is a mechanism where a new class (subclass or child class) derives properties and methods from an existing class (superclass or parent class). It promotes code reusability and establishes an "is-a" relationship (e.g., a Dog
is an Animal
).
It's just like in humans, where a child inherits features from their parents.
How this is done / Syntax (in Java): The extends
keyword is used.
// ParentClass (Superclass)
class ParentClass {
String familyName;
public ParentClass(String familyName) {
this.familyName = familyName;
System.out.println("ParentClass constructor called for family: " + familyName);
}
void greet() {
System.out.println("Hello from the family!");
}
}
// ChildClass (Subclass) inherits from ParentClass
class ChildClass extends ParentClass {
String childName;
public ChildClass(String familyName, String childName) {
super(familyName); // Calls the ParentClass constructor. MUST be the first line.
this.childName = childName;
System.out.println("ChildClass constructor called for: " + childName);
}
void introduce() {
// Accesses familyName inherited from ParentClass
System.out.println("I am " + childName + " of family " + familyName + ".");
}
// Method Overriding: ChildClass provides its own version of greet()
@Override
void greet() {
super.greet(); // Optionally call the parent's version
System.out.println("And a special hello from " + childName + "!");
}
}
public class InheritanceDemo {
public static void main(String[] args) {
ChildClass child = new ChildClass("Smith", "John");
// Output from constructors:
// ParentClass constructor called for family: Smith
// ChildClass constructor called for: John
child.greet();
// Output:// Hello from the family!// And a special hello from John!
child.introduce();
// Output: I am John of family Smith.
System.out.println(child.childName + " " + child.familyName); // Accessing properties // Output: John Smith
}
}
What a child can access: This depends on access modifiers in the parent class:
public
members: Accessible by the child class.protected
members: Accessible by the child class (even if in a different package).private
members: Not directly accessible by the child class. The child class must usepublic
orprotected
getters/setters if provided by the parent to interact with private parent members.Default (package-private) members: Accessible if the child class is in the same package.
Types of Inheritance Java Supports:
Single Inheritance: A class can inherit from only one superclass. (e.g.,
Class B extends Class A
)Multilevel Inheritance: A class can inherit from a class that itself inherits from another class. (e.g.,
Class C extends Class B
, andClass B extends Class A
. SoC -> B -> A
)Hierarchical Inheritance: One parent class can have multiple child classes. (e.g.,
Class B extends Class A
andClass C extends Class A
)
Java does not support multiple inheritance of classes (a class inheriting from two or more unrelated classes directly, e.g., Class C extends Class A, Class B
). This is primarily to avoid the "diamond problem" (ambiguity if both A
and B
have a method with the same signature). Multiple inheritance of type can be achieved using interfaces (covered next).
Overriding: As seen in the example, a child class can provide a specific implementation for a method that is already defined in its parent class. This is method overriding.
- Example: A parent
Car
class has a genericaccelerate()
method. A childElectricCar
class might overrideaccelerate()
to describe silent electric motor acceleration, while aSportsCar
child class might override it to describe loud engine revving.
4. Polymorphism
Polymorphism basically means "many forms." It allows objects of different classes to respond to the same method call in different ways. It's a powerful concept that enables flexibility and extensibility.
Types of Polymorphism:
Compile-time Polymorphism (Method Overloading):
Already discussed. The compiler decides which method to call at compile time based on the method signature (name and parameter list).
Example: Having
calculator.add(int a, int b)
andcalculator.add(double a, double b)
.
Run-time Polymorphism (Method Overriding):
This is a core concept, achieved through inheritance and overriding.
A subclass provides a specific implementation for a method that is already defined in its superclass.
The decision of which method version to execute (parent's or child's) is made at runtime based on the actual object type, not the reference variable's type.
Example (Method Overriding for Run-time Polymorphism):
class Animal {
void makeSound() {
System.out.println("Generic animal sound");
}
}
class Dog extends Animal {
@Override // Good practice
void makeSound() {
System.out.println("Woof woof!");
}
}
class Cat extends Animal {
@Override // Good practice
void makeSound() {
System.out.println("Meow!");
}
}
public class PolymorphismDemo {
public static void main(String[] args) {
// 'myAnimal' is an Animal reference, but it can point to Dog or Cat objects
Animal myAnimal;
myAnimal = new Dog(); // myAnimal now refers to a Dog object
myAnimal.makeSound(); // Calls Dog's makeSound() -> Output: Woof woof!
myAnimal = new Cat(); // myAnimal now refers to a Cat object
myAnimal.makeSound(); // Calls Cat's makeSound() -> Output: Meow!
Animal anotherAnimal = new Animal();
anotherAnimal.makeSound(); // Calls Animal's makeSound() -> Output: Generic animal sound
}
}
In this example, myAnimal.makeSound()
calls a different actual method depending on whether myAnimal
currently holds a Dog
object or a Cat
object. This decision is made at runtime.
10. abstract
Keyword: Classes and Methods
The abstract
keyword is used for classes and methods to achieve abstraction.
Abstract Class:
An abstract class cannot be instantiated (can't create objects of an abstract class directly using
new
).It can have both abstract methods (methods without a body) and concrete methods (methods with implementation).
It's designed to be subclassed. Subclasses must implement all abstract methods of the parent abstract class, or they too must be declared abstract.
Why is it important? To stop the user from creating an object of a parent class that is too generic to be useful on its own.
Example:
Shape
could be an abstract class. You don't create a generic "Shape"; you create specific objects likeCircle
orSquare
(whichextend Shape
).If a class like
Vehicle
is not abstract, we can make objects ofVehicle
and also objects ofCar
(a subclass). But ifVehicle
is just a concept, we might want to discourage creating genericVehicle
objects and only allow specific types likeCar
orMotorcycle
.
Abstract Method:
An abstract method is declared without an implementation (no body, ends with a semicolon).
public abstract void draw(); // Notice the semicolon and no {}
It can only exist within an abstract class (or an interface, where methods are implicitly abstract by default before Java 8).
It forces subclasses to provide a concrete implementation for that method. It defines a contract that subclasses must fulfill.
Example: Abstract Class and Methods
What about an abstract method in a parent class? So what to do? Subclasses must implement it.
// Abstract class
abstract class Vehicle {
String modelName;
boolean isEngineOn;
// Concrete method (constructor)
public Vehicle(String modelName) {
this.modelName = modelName;
this.isEngineOn = false;
System.out.println(modelName + " vehicle blueprint created.");
}
// Abstract methods - no implementation here, subclasses must provide it
public abstract void startEngine();
public abstract void stopEngine();
// Concrete method
public void honk() {
System.out.println("Generic vehicle honk!");
}
public String getModelName() {
return modelName;
}
}
// Concrete subclass Car
class Car extends Vehicle {
public Car(String modelName) {
super(modelName); // Call superclass constructor
}
@Override
public void startEngine() {
if (!isEngineOn) {
isEngineOn = true;
System.out.println(getModelName() + " car engine started with a key turn.");
} else {
System.out.println(getModelName() + " car engine is already on.");
}
}
@Override
public void stopEngine() {
if (isEngineOn) {
isEngineOn = false;
System.out.println(getModelName() + " car engine stopped.");
} else {
System.out.println(getModelName() + " car engine is already off.");
}
}
// Car can also override concrete methods if needed
@Override
public void honk() {
System.out.println(getModelName() + " car says: Beep beep!");
}
}
// Concrete subclass Motorcycle
class Motorcycle extends Vehicle {
public Motorcycle(String modelName) {
super(modelName);
}
@Override
public void startEngine() {
if(!isEngineOn){
isEngineOn = true;
System.out.println(getModelName() + " motorcycle engine started with a kick.");
} else {
System.out.println(getModelName() + " motorcycle engine is already on.");
}
}
@Override
public void stopEngine() {
if(isEngineOn){
isEngineOn = false;
System.out.println(getModelName() + " motorcycle engine stopped.");
} else {
System.out.println(getModelName() + " motorcycle engine is already off.");
}
}
// Motorcycle will use the generic honk() from Vehicle unless overridden
}
public class AbstractDemo {
public static void main(String[] args) {
// Vehicle myVehicle = new Vehicle("SomeVehicle"); // ERROR! Cannot instantiate abstract class
Vehicle myCar = new Car("Sedan"); // Sedan vehicle blueprint created.
myCar.startEngine(); // Sedan car engine started with a key turn.
myCar.honk(); // Sedan car says: Beep beep!
myCar.stopEngine(); // Sedan car engine stopped.
Vehicle myBike = new Motorcycle("SportBike"); // SportBike vehicle blueprint created.
myBike.startEngine(); // SportBike motorcycle engine started with a kick.
myBike.honk(); // Generic vehicle honk! (Motorcycle didn't override it)
myBike.stopEngine(); // SportBike motorcycle engine stopped.
}
}
11. interface
: Pure Contracts
What is an interface? Maybe something related to structure? Yes, an interface in Java is a reference type, similar to a class, that is primarily a collection of abstract methods. A class implements
an interface, thereby inheriting the abstract methods of the interface and promising to provide implementations for them.
It defines a contract that implementing classes must adhere to.
A class can implement multiple interfaces (this is how Java achieves a form of multiple inheritance of type or behavior, not state).
Before Java 8:
Methods in an interface were implicitly
public
andabstract
. You couldn't have concrete methods.Variables declared in an interface were implicitly
public
,static
, andfinal
(constants).
Java 8 and later:
- Interfaces can have
default
methods (concrete methods with an implementation) andstatic
methods (also with implementation). This provides more flexibility.
- Interfaces can have
And when using an interface, we need to override the existing (abstract) functions. But why? Because the interface only defines what methods a class should have (the contract), not how they should be implemented. The implementing class is responsible for providing the actual logic.
We can use many interfaces in one class.
Example: Interfaces
// Interface 1: Defines actions a person can take
interface IPersonActions {
// All fields in an interface are implicitly public static final
// int MAX_AGE = 120; // Example of a constant
// All methods (pre-Java 8) are implicitly public abstract
boolean canVote(int currentAge);
void performDailyActivity();
}
// Interface 2: Defines actions a worker can take
interface IWorkerActions {
void goToWork();
double calculateSalary(int hoursWorked);
}
// A class can implement multiple interfaces
class Person implements IPersonActions, IWorkerActions {
String name;
int age;
double hourlyRate;
public Person(String name, int age, double hourlyRate) {
this.name = name;
this.age = age;
this.hourlyRate = hourlyRate;
}
// Implementing methods from IPersonActions
@Override
public boolean canVote(int currentAge) { // Parameter name can be different
return currentAge >= 18;
}
@Override
public void performDailyActivity() {
System.out.println(name + " is eating and sleeping.");
}
// Implementing methods from IWorkerActions
@Override
public void goToWork() {
System.out.println(name + " is going to work.");
}
@Override
public double calculateSalary(int hoursWorked) {
// double rate = 15.0; // Example rate from notes, better to make it a property
return hoursWorked * this.hourlyRate;
}
// A regular method of the Person class
public void introduce() {
System.out.println("Hi, I'm " + name + " and I am " + age + " years old.");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
Person shiv = new Person("Shiv", 25, 20.0); // Name, Age, Hourly Rate
shiv.introduce(); // Hi, I'm Shiv and I am 25 years old.
System.out.println("Can Shiv vote? " + shiv.canVote(shiv.age)); // Can Shiv vote? true
shiv.performDailyActivity(); // Shiv is eating and sleeping.
shiv.goToWork(); // Shiv is going to work.
System.out.println("Shiv's salary for 40 hours: " + shiv.calculateSalary(40));
// Shiv's salary for 40 hours: 800.0
// You can also have references of interface type
IPersonActions actions = shiv; // 'actions' can only see IPersonActions methods
actions.performDailyActivity(); // Shiv is eating and sleeping.
// actions.goToWork(); // COMPILE ERROR! goToWork() is not in IPersonActions
IWorkerActions worker = shiv;
System.out.println("Salary via worker ref: " + worker.calculateSalary(10)); // Salary via worker ref: 200.0
}
}
12. Other Important Keywords
final
keyword
The final
keyword can be used in several contexts to denote "unchangeability":
final
variable: Its value cannot be changed once assigned. It creates a constant. Must be initialized when declared or in the constructor.final double PI = 3.14159; // PI = 3.14; // ERROR! Cannot assign a value to final variable PI
final
method: A final method cannot be overridden by subclasses.class Base { final void show() { System.out.println("Base::show() called - I am final!"); } } class Derived extends Base { // void show() { } // ERROR! show() in Derived cannot override show() in Base; // overridden method is final }
final
class: A final class cannot be subclassed (inherited from). This is often done for security or to ensure immutability.final class Immutable { // ... class members ... } // class MoreImmutable extends Immutable {} // ERROR! Cannot inherit from final Immutable
The
String
class in Java is a good example of afinal
class.
super
keyword
The super
keyword is a reference variable that is used to refer to the immediate parent class object.
super
to call parent class constructor:super()
is used to call the constructor of the immediate parent class.It must be the first statement in a subclass constructor.
If you don't explicitly call
super()
in a subclass constructor, Java implicitly inserts a call to the parent's no-argumentsuper()
constructor. If the parent doesn't have a no-arg constructor, you'll get a compile error unless you explicitly call anothersuper(...)
constructor.
class Parent {
String message;
Parent() {
this.message = "Default Parent";
System.out.println("Parent constructor called (default)");
}
Parent(String msg) {
this.message = msg;
System.out.println("Parent constructor with message: " + msg);
}
}
class Child extends Parent {
Child() {
// Implicit call to super() here if not specified.
// super(); // Explicitly calls Parent's no-arg constructor
System.out.println("Child constructor called (default). Parent message: " + super.message);
}
Child(String childMsg, String parentMsg) {
super(parentMsg); // Calls Parent(String msg) constructor. Must be first.
System.out.println("Child constructor with message: " + childMsg + ". Parent message: " + super.message);
}
}
// public class SuperConstructorDemo { ... main method with new Child()... }
// Output when `new Child("Hi Child", "Hi Parent")` is created:
// Parent constructor with message: Hi Parent
// Child constructor with message: Hi Child. Parent message: Hi Parent
super
to call parent class methods: Used if the subclass has overridden a parent class method and you still want to call the parent's version from within the subclass's overriding method.class Animal { void eat() { System.out.println("Animal is eating"); } } class Dog extends Animal { @Override void eat() { super.eat(); // Calls Animal's eat() method System.out.println("Dog is eating bones"); } } // Output when dog.eat() is called: // Animal is eating // Dog is eating bones
super
to access parent class instance variables: If a child and parent have instance variables with the same name (name shadowing - generally discouraged),super.variableName
refers to the parent's variable, andthis.variableName
(or justvariableName
) refers to the child's.
13. instanceof
operator
The instanceof
operator is used to test whether an object is an instance of a particular class, a subclass of that class, or an instance of a class that implements a particular interface. It returns a boolean
value.
interface Runnable {} // A simple marker interface
class Animal {}
class Dog extends Animal implements Runnable {}
class Cat extends Animal {}
public class InstanceOfDemo {
public static void main(String[] args) {
Dog myDog = new Dog();
Animal myAnimal = myDog; // Upcasting: Dog is an Animal
Cat myCat = new Cat();
System.out.println("myDog instanceof Dog: " + (myDog instanceof Dog)); // true
System.out.println("myDog instanceof Animal: " + (myDog instanceof Animal)); // true
System.out.println("myDog instanceof Runnable: " + (myDog instanceof Runnable)); // true
System.out.println("myDog instanceof Cat: " + (myDog instanceof Cat)); // false (compile error with unrelated types if not in hierarchy)
System.out.println("myAnimal instanceof Dog: " + (myAnimal instanceof Dog)); // true (myAnimal currently refers to a Dog)
System.out.println("myAnimal instanceof Animal: " + (myAnimal instanceof Animal)); // true
System.out.println("myAnimal instanceof Cat: " + (myAnimal instanceof Cat)); // false
// System.out.println("myAnimal instanceof String: " + (myAnimal instanceof String)); // COMPILE ERROR: Incompatible types
Animal anotherAnimal = new Animal();
System.out.println("anotherAnimal instanceof Dog: " + (anotherAnimal instanceof Dog)); // false
System.out.println("anotherAnimal instanceof Animal: " + (anotherAnimal instanceof Animal)); // true
// Useful for safe casting
if (myAnimal instanceof Dog) {
Dog specificDog = (Dog) myAnimal; // Safe to cast
System.out.println("Successfully cast myAnimal to Dog.");
}
}
}
14. Inner Classes (Nested Classes)
Java allows you to define a class within another class. Such a class is called a nested class or inner class. They are useful for:
Logically grouping classes that are only used in one place.
Increasing encapsulation.
Creating more readable and maintainable code.
There are a few types:
1. Regular Inner Class (or Non-Static Nested Class)
Defined within another class without the
static
keyword.An object of an inner class can exist only within an instance of the outer class.
It has access to all members (including private) of the outer class instance.
To instantiate, you need an instance of the Outer class:
OuterClass.InnerClass innerObj =
outerObj.new
InnerClass();
class Outer {
private int outerVar = 10;
private String outerMessage = "Hello from Outer!";
// Regular Inner Class
class Inner {
void display() {
// Can access outer class's private members
System.out.println("Outer variable: " + outerVar);
System.out.println("Outer message: " + outerMessage);
// System.out.println("Accessing outer this: " + Outer.this.outerVar); // explicit way
}
}
void testInner() {
Inner innerObj = new Inner(); // Instantiating Inner from within Outer
innerObj.display();
}
}
public class RegularInnerDemo {
public static void main(String[] args) {
Outer outerObj = new Outer();
outerObj.testInner();
// Output:
// Outer variable: 10
// Outer message: Hello from Outer!
// To instantiate Inner from outside Outer (but still need an Outer instance):
Outer.Inner innerObjExternally = outerObj.new Inner();
innerObjExternally.display();
// Output:
// Outer variable: 10
// Outer message: Hello from Outer!
}
}
2. Static Nested Class
A nested class declared with the
static
keyword.It does not have access to instance members (non-static fields and methods) of the outer class. It can only access static members of the outer class.
It's more like a regular top-level class that has been nested for packaging convenience or to associate it closely with its outer class.
It does not need an instance of the outer class to be instantiated:
OuterClass.StaticNestedClass nestedObj = new OuterClass.StaticNestedClass();
class OuterStatic {
private static int staticOuterVar = 20;
private int instanceOuterVar = 25; // Non-static
// Static Nested Class
static class StaticNested {
void display() {
System.out.println("Static Outer variable: " + staticOuterVar);
// System.out.println(instanceOuterVar); // ERROR! Cannot access non-static member
}
}
}
public class StaticNestedDemo {
public static void main(String[] args) {
// To instantiate:
OuterStatic.StaticNested nestedObj = new OuterStatic.StaticNested();
nestedObj.display(); // Output: Static Outer variable: 20
}
}
3. Local Inner Class
Defined within a method body.
It's only visible within the scope of that method.
Can access local variables of the method only if they are
final
or effectively final (Java 8+). (Effectively final means the variable's value is never changed after initialization).Cannot be declared
public
,private
,protected
, orstatic
.
class OuterLocal {
void myMethod() {
final int localVar = 30; // Must be final or effectively final
String effectivelyFinalVar = "Hello from method!";
// localVar = 31; // This would make localVar not effectively final
// Local Inner Class
class LocalInner {
void display() {
System.out.println("Local variable from method: " + localVar);
System.out.println("Effectively final variable: " + effectivelyFinalVar);
// Can also access OuterLocal's instance members if OuterLocal is not static context
}
}
LocalInner li = new LocalInner();
li.display();
}
}
public class LocalInnerDemo {
public static void main(String[] args) {
OuterLocal ol = new OuterLocal();
ol.myMethod();
// Output:
// Local variable from method: 30
// Effectively final variable: Hello from method!
}
}
4. Anonymous Inner Class
An inner class without a name.
It's declared and instantiated in a single statement.
Typically used for creating objects of an interface or an abstract class "on the fly," often for event handlers or simple, one-off implementations.
They are expressions, so they must be part of a statement.
interface Greeter {
void greet();
void farewell(String name);
}
abstract class SpecialGreeter {
abstract void specialGreeting();
void commonMessage() {
System.out.println("This is a common message.");
}
}
public class AnonymousInnerDemo {
public static void main(String[] args) {
// Anonymous Inner Class implementing an interface
Greeter englishGreeting = new Greeter() {
@Override
public void greet() {
System.out.println("Hello!");
}
@Override
public void farewell(String name) {
System.out.println("Goodbye, " + name + "!");
}
}; // Semicolon is part of the assignment statement
englishGreeting.greet(); // Output: Hello!
englishGreeting.farewell("Alice"); // Output: Goodbye, Alice!
// Anonymous Inner Class extending an abstract class
SpecialGreeter mySpecialGreeter = new SpecialGreeter() {
@Override
void specialGreeting() {
System.out.println("A very special anonymous greeting!");
}
};
mySpecialGreeter.specialGreeting(); // A very special anonymous greeting!
mySpecialGreeter.commonMessage(); // This is a common message.
// Anonymous Inner Class extending a concrete class (less common but possible)
// Example: Runnable for a thread
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Anonymous Runnable running!");
}
};
Thread t = new Thread(r);
t.start(); // Output: Anonymous Runnable running!
}
}
Subscribe to my newsletter
Read articles from Shivprasad Roul directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Shivprasad Roul
Shivprasad Roul
Software developer with a strong foundation in React, Node.js, PostgreSQL, and AI-driven applications. Experienced in remote sensing, satellite image analysis, and vector databases. Passionate about defense tech, space applications, and problem-solving. Currently building AI-powered solutions and preparing for a future in special forces.