Java OOPs Concepts
JVM and Java Memory Model
Variables, Data Types And Wrapper Classes
Operators And Assignement
Objects, Classes, Methods, Constructors and Interfaces
Java Modifiers And Access Specifiers
Method Overloading And Overriding
Nested/ Inner Classes
Flow Control and Loops
String And StringBuffer
Exception Handling
Multithreading
Java Serialization
Java Database Connectivity- JDBC
Garbage Collection
Java Programming Essentials
Object-Oriented Programming (OOP) is a programming paradigm that organizes software around objects—instances of classes that contain both data (attributes) and behavior (methods). OOP emphasizes structuring a program by modeling real-world entities, allowing data to control access to code through well-defined interfaces.
In procedural programming, logic is executed step by step through procedures or functions, and data is often shared across the entire program. In contrast, object-oriented programming centers around objects that encapsulate both data and behavior. The data within an object is typically protected and only accessible through defined interfaces, improving security and modularity.
OOP is based on four main principles:
Abstraction
Encapsulation
Inheritance
Polymorphism
Abstraction is the concept of focusing on the essential features of an object while hiding unnecessary details. It allows developers to work with complex systems by interacting only with relevant aspects, making the design more manageable.
Encapsulation refers to the practice of keeping an object's data and methods together in one unit and restricting direct access to some components. It protects the internal state of an object by exposing only necessary parts through public methods. This prevents unauthorized access and misuse of the internal data.
Encapsulation is often described as a protective barrier that wraps data and the code that manipulates it into a single unit.
Abstraction is about exposing only what is necessary while hiding complex inner workings. It focuses on what an object does.
Encapsulation is about restricting access to internal details and protecting data from external modification. It focuses on how an object achieves its behavior.
Abstraction is more concerned with the design phase, whereas encapsulation is more about the implementation. Encapsulation is often used to realize abstraction in code.
Inheritance allows a class (called a subclass) to acquire properties and behaviors from another class (called a superclass). This promotes code reuse and supports polymorphism by enabling objects to be treated as instances of their parent class. In Java, inheritance is implemented using the extends keyword.
Polymorphism means "many forms." It allows a single interface or method name to represent different underlying forms or behaviors. Polymorphism enables flexibility in code by allowing the same method to operate on different types of objects.
There are two primary types of polymorphism:
Compile-Time Polymorphism (Static Binding): Achieved through method overloading, where multiple methods in the same class have the same name but different parameter lists.
Run-Time Polymorphism (Dynamic Binding): Achieved through method overriding, where a subclass provides a specific implementation of a method already defined in its superclass or interface.
Java implements polymorphism through:
Method Overloading: Same method name with different parameter types or counts.
Method Overriding: Same method name, return type, and parameters in a subclass.
Interfaces and Inheritance: Allowing objects to be treated as instances of parent types, enabling dynamic method resolution at runtime.
In addition to the four core OOP principles (Abstraction, Encapsulation, Inheritance, and Polymorphism), there are other important OOP concepts like Composition, Association, Aggregation, and Dependency. Here's a clear explanation of each:
Composition is a design principle in which one class contains an object of another class, implying a "has-a" relationship.
For example, a Car has an Engine. In this case, Car is composed of Engine.
The lifetime of the composed object (e.g., Engine) is tightly bound to the lifetime of the containing object (e.g., Car).
If the Car is destroyed, the Engine will also be destroyed.
It provides better flexibility than inheritance because it allows dynamic behavior changes by replacing the composed object.
Example in Java:
class Engine {
void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine = new Engine();
void startCar() {
engine.start();
}
}
Association represents a relationship between two separate classes that are aware of each other and may interact.
It is a broad term that includes aggregation and composition.
It is typically a "uses-a" relationship.
It can be one-way (unidirectional) or two-way (bidirectional).
Example:
A Teacher can be associated with multiple Students.
class Teacher {
String name;
}
class Student {
String name;
}
Aggregation is a special form of association where the relationship represents a "has-a" relationship, but with loose coupling.
The contained object can exist independently of the container.
If the container is destroyed, the contained object can still exist.
Example: A Department has Professors, but professors can exist without the department.
Example in Java:
class Professor {
String name;
}
class Department {
List<Professor> professors;
}
Dependency indicates a situation where one class relies on another to function correctly.
It’s a temporary relationship, usually seen when one class uses another class as a parameter in a method or instantiates it within a method.
Often referred to as a "uses" relationship.
Example:
class Printer {
void print(Document doc) {
// uses Document temporarily
System.out.println(doc.getText());
}
}
Inheritance is a "is-a" relationship (e.g., Dog is an Animal)
Composition is a "has-a" relationship (e.g., Car has an Engine)
Composition is generally favored over inheritance in modern design, because it provides better flexibility and encapsulation.
The Java Virtual Machine (JVM) is the runtime environment that executes Java applications. It provides an abstract computing machine that enables Java’s platform independence. Every Java application runs within a JVM instance, which adheres to standard specifications but varies in implementation depending on the platform.
JVM: Executes Java bytecode and manages memory, garbage collection, and thread management.
JRE (Java Runtime Environment): Includes the JVM, core Java libraries, and other runtime components necessary to run Java applications—but not development tools like a compiler or debugger.
JDK (Java Development Kit): A full development suite containing the JRE, JVM, and tools for compiling, debugging, and developing Java applications.
When a Java program starts, the JVM instance is created. It begins by calling the main() method of the application. This method runs on the initial thread, which can then spawn additional threads.
Each thread has its own stack and program counter (PC).
When main() is called, a stack frame is pushed onto the thread’s stack.
Each subsequent method call adds a new frame, and returning from a method pops that frame.
Objects are created in the heap, which is shared across all threads.
Java threads can be either:
Daemon threads – background threads (e.g., for garbage collection).
Non-daemon threads – active application threads (e.g., main thread).
The JVM continues running as long as there is at least one non-daemon thread alive. Once all non-daemon threads finish, the JVM terminates unless explicitly stopped via System.exit() or Runtime.exit().
Heap: The shared memory area where all Java objects live. Managed by the garbage collector. Common to all threads.
Stack: Thread-specific memory used for method execution. Each method call creates a stack frame that holds:
Local variables
Method arguments
Intermediate results
The topmost frame is the active frame, and when a method completes, its frame is removed from the stack.
Stack:
Used for method call execution and local variables.
Memory is automatically managed with method entry/exit.
Each thread has its own stack.
Heap:
Used to allocate memory for all Java objects.
Shared among threads.
Managed by the Garbage Collector.
JVM Memory Settings:
You can configure JVM memory using these options:
-Xms : Initial heap size
-Xmx : Maximum heap size
-Xss : Stack size per thread
Example:
java -Xms512m -Xmx1024m -Xss1024k YourApp
The Class Loader is a JVM subsystem responsible for loading Java classes into memory when required.
Responsibilities include:
Loading class files.
Verifying the correctness and format of classes.
Allocating memory for static variables and initializing them.
Resolving symbolic references to direct references.
Types of Class Loaders:
Bootstrap Class Loader:
Loads core Java classes (e.g., from rt.jar).
Part of the JVM, written in native code.
User-Defined Class Loaders:
Written in Java.
Allow custom loading logic, often used to load untrusted or dynamic classes.
Java allows applications to define their own class loaders for greater flexibility and control over the loading process.