Java and OOPS
Table of Contents
1. Java’s Memory Model
1.1. Stack vs Heap
int age = 25; String a = "hey"; String b = "hey"; String c = new String("hey");
- Primitive declarations like
age = 25, are stored on the stack. - For object declarations like
a = "hey"orc = new String("hey"), the stack stores object references (addresses). - The main object itself (in this case, the string “hey”), is stored in the heap memory.
- Heap memory stores dynamically allocated objects.
1.2. String Pool
For similar string literals, Java reuses the same object. Continuing from the above example:
System.out.println(a==b); //output is true System.out.println(a==c); //output is false System.out.println(a.equals(c)); //output is true
1.3.
2. Object Oriented Programming
2.1. Access Modifiers
| Modifier | Same Class | Same Package | Sub-Class in a different package | Everywhere |
|---|---|---|---|---|
private |
Yes | |||
default |
Yes | Yes | ||
protected |
Yes | Yes | Yes | |
public |
Yes | Yes | Yes | Yes |
protectedis a good way to provide inheritance across packages, without exposing implementation details (because you’d have to be a subclass to be able to access information).- One more thing to note is that constructors can never be inherited.
2.2. Inheritance
class Animal { // superclass (parent) String name; void makeSound() { System.out.println("Some sound"); } } class Dog extends Animal { // Dog IS-A Animal void fetch() { // Dog's own new method System.out.println(name + " fetches the ball!"); // 'name' is inherited from Animal — no redeclaration needed } } Dog d = new Dog(); d.name = "Bruno"; // inherited field d.makeSound(); // inherited method d.fetch(); // Dog's own method
Dog IS-A Animal. “IS-A” is the name of the relationship.
2.2.1. Method Overriding
class Animal { void makeSound() { System.out.println("Generic animal sound"); } } class Dog extends Animal { @Override // annotation — tells compiler to verify this IS an override void makeSound() { // same name, same parameters System.out.println("Woof!"); // Dog's own behaviour } } class Cat extends Animal { @Override void makeSound() { System.out.println("Meow!"); } } Dog d = new Dog(); d.makeSound(); // "Woof!" — Dog's version is called
- This is when a subclass provides a different implementation for a function declared by its parent.
- The method name, parameters, return type should be the exact same.
- The access modifier used in the subclass’ implementation should be the same or wider in scope.
2.2.2. super
superis a keyword that is synonymous to the parent class name.- You can call the parent’s constructor with the
superkeyword. Note that when you do, it must be the first line of whatever method you’re calling it in.
class Animal { String name; int age; public Animal(String name, int age) { this.name = name; this.age = age; } } class Dog extends Animal { String breed; public Dog(String name, int age, String breed) { super(name, age); // MUST be first line — calls Animal's constructor this.breed = breed; // then initialise Dog's own fields } } // If you forget super(), Java inserts super() automatically. // But if the parent has constructor with arguments, you MUST call super(args) yourself.
Let’s say you’re overriding a method. Instead of making an object out of the parent class, and calling the method that you’re overriding, you can directly use
super.methodName().class Animal { public Animal(String name, int age) { this.name = name; this.age = age; } void describe() { System.out.println("I am an animal named " + name); } } class Dog extends Animal { public Dog(String name, int age, String breed) { super(name, age); this.breed = breed; } @Override void describe() { super.describe(); // first print Animal's description System.out.println("I am a " + breed + " breed."); // then add Dog's info } } // Output: // I am an animal named Bruno // I am a Labrador breed.
2.2.3. Runtime Polymorphism aka. Dynamic Dispatch
- Runtime polymorphism is a concept in OOP, where the method implementation to execute is decided at runtime, based on the object created and not the reference variable used.
Basically, you can use the reference variable of the parent, but you can assign an object of a child of any hierarchy to it.
class Shape { void draw(){ System.out.println("generic shape"); } } class Rectangle extends Shape { @override void draw(){ // code to draw a rectangle } } class Circle extends Shape { @override void draw(){ // code to draw a circle } } public class Main { public static void main(String[] args){ Shape s; s = new Rectangle(); s.draw(); // Rectangle's implementation of draw will be chosen at runtime s = new Circle(); s.draw(); // Circle's implementation of draw will be chosen at runtime } }
- You can’t override static methods.
- Only single-inheritance is possible, as in a class can extend only one class.
2.2.4. Abstract Classes
abstractclasses can’t be instantiated. They have to be extended.They may contain a mix of implemented and unimplemented /
abstractmethods (meaning, only a function definition is given, and the child class must provide the implementation).abstract class Animal { String name; Animal(String name) { // can have constructors this.name = name; } void breathe() { // concrete method (shared behavior) System.out.println("inhale... exhale..."); } abstract void speak(); // subclasses MUST implement this } class Dog extends Animal { Dog(String name) { super(name); } @Override void speak() { System.out.println("Woof!"); } }
2.2.5. Interfaces
This is also a way for classes to follow some behaviour.
interface Flyable { double MAX_ALTITUDE = 40000; // implicitly public static final void fly(); // implicitly public abstract void land(); default void checkWeather() { // default method (Java 8+) System.out.println("Checking weather..."); } } class Airplane implements Flyable { public void fly() { System.out.println("Jet engines on"); } public void land() { System.out.println("Deploying landing gear"); } }
- Every field in an
interfaceispublic static final, meaning it’s one global variable for all classes and their instances. fly()andland()are method signatures. The body is given in the class thatimplementsthis interface.- If you want a body in the interface itself, you have to declare it as
default. - Just like with single inheritance, you can use the reference of the interface, but an object of the child class.
This is an example of multiple inheritance:
interface Flyable { void fly(); void land(); } interface Swimmable { void swim(); } class Duck implements Flyable, Swimmable { public void fly() { System.out.println("Duck flying"); } public void land() { System.out.println("Duck landing"); } public void swim() { System.out.println("Duck swimming"); } }
2.2.6. When to use Interface over Abstract Class
- Use abstract class if the classes share a true “is-a” relationship. For
example,
Dog,CatandBirdshould all extendAnimal. - If you have unrelated types, you should use an interface. Being unrelated also
hints at having multiple inheritance, and again this points to interfaces. For
example,
Airplane,BirdandSupermancanimplement Flyable.
2.3. Encapsulation
- The steps to achieve encapsulation is:
- Make all fields
private - Initialize them using the constructor
- Use Getters and Setters.
- Make all fields
Here’s an example:
class Employee { private String name; // pattern 1: read-write (getter + setter) private final int id; // pattern 2: read-only (getter only, set in constructor) private double salary; // pattern 3: write-only (setter only — e.g. password hash) private String internalCode; // pattern 4: no access (fully hidden — internal use only) public Employee(int id, String name, double salary) { this.id = id; // id set once — no setter provided setName(name); setSalary(salary); } // Pattern 1: read-write public String getName() { return name; } public void setName(String n) { if(n!=null)this.name=n; } // Pattern 2: read-only (no setter) public int getId() { return id; } // Pattern 3: write-only (no getter — salary is sensitive) public void setSalary(double s) { if(s>=0)this.salary=s; } // Pattern 4: internalCode has no getter or setter — fully encapsulated }
2.4. JavaBean
- A JavaBean is a class that follows three conventions:
- all fields are private
- it provides public getters and setters following the get/set/is naming standard
- it has a public no-argument constructor.
- Many Java frameworks (Spring, Hibernate, JSP) rely on JavaBean conventions to automatically read and write object properties via reflection.
2.5. Defensive Copying
2.6. All 4 pillars
| Inheritance | Abstraction | Encapsulation | Polymorphism/ Dynamic Dispatch |
|---|---|---|---|
| Subclass aquiring fields and methods of a super class and forming is-a relationship | Hiding implementation details by using interface and abstract class |
Bundling data and methods together by making fields private and enabling access using getters and setters | One parent reference invokes different behaviours during runtime based on object assigned to it |