Java is a language in which almost all code lives inside classes. That is why object-oriented programming (OOP) here is not "one of the styles" but the foundation you work with every day. Let's break down step by step what it consists of and why each part is needed.
Why OOP at all
When a program grows, the main problem is not "how to write the logic" but "how not to get lost." OOP offers a simple idea: group the data and the actions on it into a single object and hide the details inside. From the outside you see a small set of operations, and how everything is actually arranged is the object's own business.
A short formula: a class is a blueprint, an object is a concrete thing built from that blueprint.
Classes and objects
A class describes what data a thing has (fields) and what it can do (methods). An object is a concrete instance created from a class via new.
public class Account {
private long id; // field: object's data
private long balance; // balance in kopecks
public void deposit(long amount) { // method: an action
balance += amount;
}
public long getBalance() {
return balance;
}
}
Account acc = new Account(); // created an object
acc.deposit(500);
System.out.println(acc.getBalance()); // 500
Each object stores its own field values: two Account instances are two independent balances.
Constructors
A constructor is a special method that is called when an object is created. It exists so that the object appears in a correct state right away, rather than empty.
public class Account {
private final long id;
private long balance;
public Account(long id, long balance) { // constructor
this.id = id;
this.balance = balance;
}
}
Account acc = new Account(1, 1000); // id and balance set right away
If you don't write a constructor, Java adds an empty no-argument one. As soon as you declare your own, the empty one is no longer created automatically. The keyword this here means "the current object" and helps distinguish a field from a parameter with the same name.
Encapsulation and access modifiers
Encapsulation is hiding the internals of an object. Fields are made private, and access is given through methods. This way the object controls its own data and prevents itself from being put into an invalid state.
public class Account {
private long balance;
public void withdraw(long amount) {
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
balance -= amount; // withdrawing more than you have is not allowed
}
}
Access modifiers control visibility:
private— only inside its own class.- (no modifier) — within the same package (package-private).
protected— the package plus subclasses.public— from anywhere.
A practical rule: make everything as closed as possible and expose exactly what is truly needed from the outside.
Inheritance
Inheritance (extends) lets one class take the fields and methods of another and add its own. The base class is called the parent (superclass), the derived one the child (subclass).
public class Animal {
public String sound() {
return "...";
}
}
public class Dog extends Animal {
@Override
public String sound() { // override the parent's behavior
return "Woof";
}
}
The @Override annotation is not required but is desirable: it checks that you really are overriding a parent method rather than accidentally creating a new one (for example, because of a typo in the name).
Inheritance shouldn't be overused: it tightly couples classes. Often it is more flexible not to inherit but to hold another object inside as a field (this is called composition).
Interfaces
An interface describes what an object can do without saying how. It is a contract: a list of methods with no body. A class promises to fulfill it via implements.
public interface Notifier {
void send(String message); // signature only, no implementation
}
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
A single class can implement several interfaces — unlike inheritance, where there is only one parent. This is the main way to give classes shared "capabilities" without building a rigid hierarchy.
default methods
An interface can give a method a ready-made implementation via default. Then everyone who implements the interface gets it for free and can override it if they wish.
public interface Notifier {
void send(String message);
default void sendAll(List<String> messages) { // ready-made implementation
messages.forEach(this::send);
}
}
default was invented so that methods could be added to existing interfaces without breaking the code of everyone who already implements them.
Abstract classes
An abstract class (abstract) is something between an ordinary class and an interface. It cannot be created via new; it serves as a common template: some methods are implemented, some are left to subclasses.
public abstract class Shape {
public abstract double area(); // no body — a subclass implements it
public String describe() { // shared code for all shapes
return "Area: " + area();
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
When to pick which: an interface — when you only need a contract and shared capabilities (you can implement many interfaces); an abstract class — when subclasses share common state (fields) and common ready-made code.
Polymorphism
Polymorphism means "many forms": a variable of a parent or interface type can refer to an object of any subclass, and its own version of the method will be called. The calling code does not know and should not know which object is in front of it.
List<Animal> animals = List.of(new Dog(), new Animal());
for (Animal a : animals) {
System.out.println(a.sound()); // "Woof", then "..."
}
This is the power of OOP: you add a new class Cat extends Animal — and the loop above works with it without a single edit. Which method to call, Java decides at runtime based on the actual type of the object.
record — a concise immutable data class
Often a class is needed just to hold a set of values. Previously you had to write the fields, constructor, getters, equals, hashCode, and toString by hand. A record does all of this for you in a single line.
public record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3 — the getter is named after the field
System.out.println(p); // Point[x=3, y=4] — ready-made toString
Point p2 = new Point(3, 4);
System.out.println(p.equals(p2)); // true — comparison by values
Record fields are immutable (final): the values don't change after creation. This makes a record ideal for passing data around — DTOs, keys in a Map, method results. You can also add checks in a record's constructor:
public record Point(int x, int y) {
public Point { // compact constructor
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
}
}
enum — an enumeration
An enum defines a fixed set of named values. This is more reliable than storing strings or numbers: the compiler won't let you use a value that isn't in the list.
public enum Status {
NEW, PAID, SHIPPED, CANCELLED
}
Status s = Status.PAID;
if (s == Status.PAID) {
System.out.println("Order paid");
}
An enum is a full-fledged class: you can add fields, a constructor, and methods to it to attach extra data to each value.
public enum Planet {
EARTH(9.8), MARS(3.7); // each value has its own gravity
private final double gravity;
Planet(double gravity) { // enum constructor
this.gravity = gravity;
}
public double gravity() {
return gravity;
}
}
In short
- A class is a blueprint (fields + methods), an object is an instance built from the blueprint, created via
new; a constructor sets the starting state. - Encapsulation: fields are
private, access is through methods; keep everything as closed as possible. - Inheritance (
extends) — take and extend another class; there is only one parent. Don't overuse it, composition is often better. - An interface (
implements) — a "what it can do" contract; you can implement many, and there aredefaultmethods with a ready-made body. - An abstract class — a template with shared code and state, cannot be created directly.
- Polymorphism — one call works with any subclass; new types are added without touching the old code.
- A record — a concise immutable data class (ready-made getters,
equals,hashCode,toString); an enum — a fixed set of values, with fields and methods if needed.
What to read next
- Syntax and data types — variables, primitives,
varand the basic language constructs. - Collections —
List,Set,Mapand how to store groups of objects. - Generics — type-safe classes and methods that collections are built on.