Java Language Specification — Basics of OOP
Source: https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html
1. Spec Reference
- JLS Chapter 8: Classes — https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html
- JLS Chapter 9: Interfaces — https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html
- JLS §8.1: Class Declarations
- JLS §8.1.1: Class Modifiers
- JLS §8.1.4: Superclasses and Subclasses
- JLS §8.1.5: Superinterfaces
- JLS §8.2: Class Members
- JLS §8.3: Field Declarations
- JLS §8.4: Method Declarations
- JLS §8.4.8: Inheritance, Overriding, Hiding
- JLS §8.5: Member Class and Interface Declarations
- JLS §8.6: Instance Initializers
- JLS §8.7: Static Initializers
- JLS §8.8: Constructor Declarations
- JLS §8.9: Enum Types
- JLS §8.10: Record Classes
- JLS §9.1: Interface Declarations
- JLS §9.4: Abstract Method Declarations
- JLS §9.4.3: Default Methods
- JLS §15.12: Method Invocation Expressions
- JLS §15.9: Class Instance Creation Expressions
-- JLS §8.1: Class Declaration --
NormalClassDeclaration:
{ClassModifier} class TypeIdentifier [TypeParameters]
[ClassExtends] [ClassImplements] [ClassPermits] ClassBody
ClassModifier:
Annotation
public | protected | private
abstract | static | final | sealed | non-sealed | strictfp
ClassExtends:
extends ClassType
ClassImplements:
implements InterfaceTypeList
ClassPermits:
permits TypeName { , TypeName }
ClassBody:
{ {ClassBodyDeclaration} }
ClassBodyDeclaration:
ClassMemberDeclaration
InstanceInitializer
StaticInitializer
ConstructorDeclaration
ClassMemberDeclaration:
FieldDeclaration
MethodDeclaration
ClassDeclaration
InterfaceDeclaration
;
-- JLS §8.3: Field Declaration --
FieldDeclaration:
{FieldModifier} UnannType VariableDeclaratorList ;
FieldModifier:
Annotation
public | protected | private
static | final | transient | volatile
-- JLS §8.8: Constructor Declaration --
ConstructorDeclaration:
{ConstructorModifier} ConstructorDeclarator [Throws] ConstructorBody
ConstructorDeclarator:
[TypeParameters] SimpleTypeName ( [ReceiverParameter ,] [FormalParameterList] )
ConstructorBody:
{ [ExplicitConstructorInvocation] [BlockStatements] }
ConstructorModifier:
Annotation
public | protected | private
-- JLS §9.1: Interface Declaration --
InterfaceDeclaration:
NormalInterfaceDeclaration
AnnotationInterfaceDeclaration
NormalInterfaceDeclaration:
{InterfaceModifier} interface TypeIdentifier [TypeParameters]
[InterfaceExtends] [InterfacePermits] InterfaceBody
InterfaceModifier:
Annotation
public | protected | private
abstract | static | sealed | non-sealed | strictfp
InterfaceExtends:
extends InterfaceTypeList
-- JLS §8.9: Enum Declaration --
EnumDeclaration:
{ClassModifier} enum TypeIdentifier [ClassImplements] EnumBody
EnumBody:
{ [EnumConstantList] [,] [EnumBodyDeclarations] }
EnumConstantList:
EnumConstant { , EnumConstant }
EnumConstant:
{EnumConstantModifier} Identifier [( [ArgumentList] )] [ClassBody]
-- JLS §8.10: Record Declaration --
RecordDeclaration:
{ClassModifier} record TypeIdentifier [TypeParameters]
RecordHeader [ClassImplements] RecordBody
RecordHeader:
( [RecordComponentList] )
RecordComponentList:
RecordComponent { , RecordComponent }
RecordComponent:
{RecordComponentModifier} UnannType Identifier
VariableArityRecordComponent
3. Core Rules & Constraints
3.1 Encapsulation Rules
private fields/methods: accessible only within the declaring class. protected fields/methods: accessible within package and subclasses. - Package-private (no modifier): accessible within the same package.
public fields/methods: accessible from anywhere. - Best practice (enforced by convention, not spec): fields should be
private; access via methods.
3.2 Inheritance Rules (JLS §8.1.4)
extends clause specifies a single direct superclass. - Cannot
extend a final class. - Cannot
extend an enum type (except through the enum declaration itself). - If no
extends clause, the direct superclass is java.lang.Object (except for Object itself). - A class can
extend only one class (single inheritance for classes). - Transitive: if C extends B and B extends A, then C is a subtype of A.
3.3 Interface Implementation Rules (JLS §8.1.5)
- A class may implement multiple interfaces.
- All abstract methods of implemented interfaces must be overridden (or the class is abstract).
- Default method conflicts: if two interfaces provide default methods with same signature, the implementing class must override (compile error otherwise).
- A class that implements an interface is a subtype of that interface.
3.4 Constructor Rules (JLS §8.8)
- Constructors are not inherited.
- If no constructor is declared, the compiler provides a default no-arg constructor.
- The default constructor calls
super() implicitly. - Every constructor must invoke
super(...) or this(...) as its first statement (or the compiler inserts super() implicitly). - A class without an accessible
super() constructor must explicitly call another super(args...).
3.5 Method Overriding Rules (JLS §8.4.8.1)
- An instance method in a subclass overrides an accessible instance method in a superclass if:
- Same name, same number and type of parameters.
- Return type is covariant (same or subtype).
- Not less accessible than overridden method.
- Does not declare new checked exceptions.
@Override annotation verifies override at compile time. static methods are hidden (not overridden) — @Override on static method is an error. private methods are not overridden (they are hidden and not visible to subclasses).
3.6 Abstract Classes (JLS §8.1.1.1)
- Abstract classes cannot be instantiated (
new AbstractClass() is a compile error). - May contain abstract methods (no body, ends with
;). - May also contain concrete (non-abstract) methods.
- A class with at least one abstract method must be declared
abstract. - Abstract class may have constructors (for use by subclass constructors via
super(...)).
3.7 final Classes and Methods (JLS §8.1.1.2, §8.4.3.3)
final class cannot be extended. final method cannot be overridden. String, Integer, and all wrapper types are final. enum types are implicitly final (unless they have subclasses via constant-specific bodies). - Records are implicitly
final (JLS §8.10).
4. Type Rules
4.1 Polymorphism (JLS §15.12.4)
- A variable of type
T may refer to an object of any subtype of T at runtime. - Method calls on reference variables use dynamic dispatch (virtual method lookup).
- The JVM looks up the actual runtime type of the object to find the correct method.
- Fields are NOT polymorphic — field access uses the compile-time type of the reference.
4.2 this Reference (JLS §15.8.3)
- Inside an instance method or constructor,
this refers to the current object. this.field disambiguates when a local variable shadows an instance field. - Cannot use
this in static contexts. this(...) in a constructor calls another constructor of the same class.
4.3 super Reference (JLS §15.11.2)
super.method() calls the superclass's version of an overridden method. super.field accesses a hidden field in the superclass. super(...) in a constructor invokes the superclass constructor. - Cannot chain:
super.super.method() is not legal.
4.4 Records (JLS §8.10, Java 16+)
- Records are immutable data carriers.
- Implicitly
final — cannot be extended. - Automatically provide: constructor, accessor methods (same name as component),
equals(), hashCode(), toString(). - Record components are declared in the record header.
- Can have
static fields, methods, and instance methods. - Cannot have explicit instance fields beyond the record components.
4.5 Enums (JLS §8.9)
- Enum constants are
public static final instances of the enum type. - Enum types implicitly extend
java.lang.Enum. - Cannot be instantiated with
new. values() returns all constants; valueOf(String) finds by name. - Can have fields, methods, and constructors.
- Constructor is always
private (or package-private — but effectively private since enum cannot be subclassed externally).
5. Behavioral Specification
5.1 Object Creation Sequence (JLS §12.5)
- Allocate memory for the new object.
- Zero-initialize all instance fields.
- Invoke the specified constructor.
- Constructor begins with
super(...) or this(...) call. - Execute instance initializers and instance variable initializers in textual order.
- Execute the constructor body remainder.
5.2 Virtual Method Dispatch (JLS §15.12.4)
- The JVM looks up the method in the runtime class of the object.
- If not found, searches up the class hierarchy.
- This is called dynamic dispatch or virtual dispatch.
invokevirtual bytecode instruction performs dynamic dispatch. invokeinterface is used for interface method calls.
5.3 Interface Default Methods (JLS §9.4.3, Java 8+)
- Interfaces may provide default implementations of methods.
- A class that does not override the default method inherits it.
- If a class inherits two conflicting defaults, it must override to resolve the conflict.
- Default methods enable interface evolution without breaking existing implementations.
5.4 Access Control Summary (JLS §6.6)
| Modifier | Same Class | Same Package | Subclass (other pkg) | Other Package |
public | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | No |
| (none) | Yes | Yes | No | No |
private | Yes | No | No | No |
6. Defined vs Undefined Behavior
| Situation | Behavior per JLS |
new AbstractClass() | Compile error |
new FinalClass() as extends target | Compile error |
| Overriding with less access | Compile error (e.g., override public with protected) |
| Overriding with new checked exception | Compile error |
this() not as first constructor statement | Compile error |
Circular this() calls | Compile error |
| Two interfaces with conflicting defaults, not overridden | Compile error |
super.super.method() | Compile error (no chained super) |
static method with @Override | Compile error |
| Record with explicit instance field | Compile error |
7. Edge Cases from Spec
7.1 Field Hiding vs Method Overriding
class Parent {
int x = 10;
int getX() { return x; }
}
class Child extends Parent {
int x = 20; // hides Parent.x
@Override int getX() { return x; } // overrides
}
Parent p = new Child();
System.out.println(p.x); // 10 (field: compile-time type)
System.out.println(p.getX()); // 20 (method: runtime type)
7.2 Default Method Diamond Problem
interface A {
default String name() { return "A"; }
}
interface B extends A {
default String name() { return "B"; }
}
class C implements A, B {
// Must override to resolve conflict (or not — B is more specific and wins here)
// In this case B overrides A, so C inherits B's default
@Override
public String name() { return B.super.name(); } // explicit super call
}
7.3 Covariant Return Type
class Producer {
Object produce() { return new Object(); }
}
class StringProducer extends Producer {
@Override
String produce() { return "hello"; } // covariant: String is-a Object
}
7.4 Constructor Chaining
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
Person(String name) {
this(name, 0); // chains to two-arg constructor
}
Person() {
this("Unknown"); // chains to one-arg constructor
}
}
7.5 Abstract Class with Constructor
abstract class Vehicle {
protected String brand;
Vehicle(String brand) { // abstract class CAN have constructor
this.brand = brand;
}
abstract void move();
}
class Car extends Vehicle {
Car(String brand) {
super(brand); // must call abstract class constructor
}
@Override
void move() {
System.out.println(brand + " car is moving");
}
}
7.6 Record Compact Constructor
record Range(int lo, int hi) {
Range { // compact constructor — no parameter list needed
if (lo > hi) {
throw new IllegalArgumentException("lo > hi");
}
// lo and hi are implicitly assigned from components
}
}
8. Version History
| Java Version | Change | JEP/Reference |
| Java 1.0 | Classes, interfaces, inheritance, polymorphism, access control | JLS 1st ed. |
| Java 1.1 | Inner classes; anonymous classes; local classes | JLS 2nd ed. |
| Java 5 | Generics; enums; annotations; covariant returns; varargs | JSR 14, JSR 175 |
| Java 8 | Default methods in interfaces; static methods in interfaces | JEP 126 |
| Java 9 | private methods in interfaces | JEP 213 |
| Java 14 | Records (preview) | JEP 359 |
| Java 15 | Sealed classes (preview) | JEP 360 |
| Java 16 | Records (standard); pattern matching instanceof (standard) | JEP 395, JEP 394 |
| Java 17 | Sealed classes (standard) | JEP 409 |
| Java 21 | Pattern matching for switch (standard); record patterns (standard) | JEP 441, JEP 440 |
| Java 21 | Virtual threads — new thread type in platform | JEP 444 |
9. Implementation-Specific Behavior (JVM-Specific)
9.1 Virtual Method Table (vtable)
- HotSpot JVM uses a vtable per class for virtual method dispatch.
- Entries point to method bytecode (or JIT-compiled native code).
- Interface dispatch uses an itable (interface table) — slightly slower than vtable.
- JIT's inline caching: monomorphic call site compiles to direct call after seeing one class.
- Bimorphic and megamorphic call sites: different optimization strategies.
9.2 Object Memory Layout (HotSpot)
- Object header: 8 bytes (32-bit) or 12 bytes (64-bit with compressed oops).
- Fields follow in order: superclass fields first, then subclass fields.
- JVM may reorder fields for alignment efficiency.
java.lang.Object has no instance fields (header only).
9.3 instanceof and checkcast
instanceof compiles to instanceof bytecode — checks class hierarchy at runtime. - Pattern matching
instanceof additionally assigns the variable if check passes. checkcast is used for explicit casts — throws ClassCastException on failure.
9.4 Records vs Traditional Classes
- Records compile to
final classes with private final fields. - Accessors compile to regular instance methods (no
get prefix). equals(), hashCode(), toString() are synthesized by the compiler. - Compact constructors compile to a regular constructor.
9.5 Enum JVM Implementation
- Enum constants are
public static final fields of the enum class. - JVM initializes them as static fields during class initialization.
Enum.name() returns the constant's identifier. Enum.ordinal() returns the 0-based position. - Enum serialization: by name (not ordinal) —
readResolve() returns the canonical instance.
10. Spec Compliance Checklist
11. Official Examples (Compilable Java 21 Code)
// Example 1: Classes, Inheritance, Polymorphism
// File: OopBasics.java
public class OopBasics {
// Abstract superclass
abstract static class Shape {
protected String color;
Shape(String color) {
this.color = color;
}
abstract double area();
abstract double perimeter();
@Override
public String toString() {
return "%s[color=%s, area=%.2f]".formatted(
getClass().getSimpleName(), color, area());
}
}
// Concrete subclass
static class Circle extends Shape {
private final double radius;
Circle(String color, double radius) {
super(color); // call superclass constructor
this.radius = radius;
}
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public double perimeter() { return 2 * Math.PI * radius; }
}
static class Rectangle extends Shape {
private final double width, height;
Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
@Override
public double perimeter() { return 2 * (width + height); }
}
public static void main(String[] args) {
Shape[] shapes = {
new Circle("red", 5.0),
new Rectangle("blue", 4.0, 6.0),
new Circle("green", 3.0)
};
for (Shape s : shapes) {
System.out.println(s); // polymorphic toString and area()
}
// Pattern matching with sealed types
for (Shape s : shapes) {
String desc = switch (s) {
case Circle c -> "Circle with radius " + c.radius;
case Rectangle r -> "Rect " + r.width + "x" + r.height;
default -> "Unknown shape";
};
System.out.println(desc);
}
}
}
// Example 2: Interfaces with Default Methods
// File: InterfaceDemo.java
import java.util.Comparator;
public class InterfaceDemo {
// Interface with abstract and default methods
interface Printable {
void print(); // abstract
default void printTwice() { // default
print();
print();
}
static Printable of(String msg) { // static factory
return () -> System.out.println(msg);
}
}
interface Saveable {
void save();
default String describe() { return "Saveable"; }
}
// Class implementing multiple interfaces
static class Document implements Printable, Saveable {
private final String content;
Document(String content) { this.content = content; }
@Override
public void print() { System.out.println("Doc: " + content); }
@Override
public void save() { System.out.println("Saving: " + content); }
@Override
public String describe() { return "Document"; } // resolves ambiguity
}
interface Sortable<T> extends Comparable<T> {
default boolean isLessThan(T other) {
return compareTo(other) < 0;
}
}
static class Version implements Sortable<Version> {
final int major, minor;
Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
@Override
public int compareTo(Version other) {
int cmp = Integer.compare(this.major, other.major);
return cmp != 0 ? cmp : Integer.compare(this.minor, other.minor);
}
@Override
public String toString() { return major + "." + minor; }
}
public static void main(String[] args) {
Document doc = new Document("Hello, Java!");
doc.printTwice();
doc.save();
System.out.println(doc.describe());
// Static factory method on interface
Printable p = Printable.of("From static factory");
p.print();
// Interface type variable
Version v1 = new Version(2, 0);
Version v2 = new Version(1, 9);
System.out.println(v2 + " < " + v1 + ": " + v2.isLessThan(v1)); // true
}
}
// Example 3: Encapsulation with Accessors and Mutators
// File: Encapsulation.java
import java.util.Objects;
public class Encapsulation {
static class BankAccount {
private final String accountId;
private double balance;
private String owner;
BankAccount(String accountId, String owner, double initialBalance) {
this.accountId = Objects.requireNonNull(accountId, "accountId cannot be null");
this.owner = Objects.requireNonNull(owner, "owner cannot be null");
if (initialBalance < 0) throw new IllegalArgumentException("Initial balance must be >= 0");
this.balance = initialBalance;
}
// Accessor (getter)
public String getAccountId() { return accountId; }
public double getBalance() { return balance; }
public String getOwner() { return owner; }
// Mutator (setter)
public void setOwner(String owner) {
this.owner = Objects.requireNonNull(owner, "owner cannot be null");
}
// Business logic methods
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive");
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Withdrawal must be positive");
if (amount > balance) throw new IllegalStateException("Insufficient funds");
balance -= amount;
}
@Override
public String toString() {
return "Account[%s, owner=%s, balance=%.2f]".formatted(accountId, owner, balance);
}
}
public static void main(String[] args) {
BankAccount account = new BankAccount("ACC-001", "Alice", 1000.00);
System.out.println(account);
account.deposit(500.00);
System.out.println("After deposit: " + account.getBalance());
account.withdraw(200.00);
System.out.println("After withdrawal: " + account.getBalance());
try {
account.withdraw(2000.00);
} catch (IllegalStateException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
// Example 4: Records and Sealed Classes (Java 21)
// File: ModernOop.java
public class ModernOop {
// Sealed interface hierarchy
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error, Exception cause) implements Result<T> {
Failure(String error) { this(error, null); }
}
// Enum with methods and fields
enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private final double mass; // kg
private final double radius; // meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
// Generic utility with sealed result
static Result<Integer> safeDivide(int a, int b) {
if (b == 0) return new Failure<>("Division by zero");
return new Success<>(a / b);
}
static <T> T unwrapOrDefault(Result<T> result, T defaultValue) {
return switch (result) {
case Success<T> s -> s.value();
case Failure<T> f -> {
System.err.println("Error: " + f.error());
yield defaultValue;
}
};
}
public static void main(String[] args) {
// Records
Result<Integer> r1 = safeDivide(10, 2);
Result<Integer> r2 = safeDivide(10, 0);
System.out.println(unwrapOrDefault(r1, -1)); // 5
System.out.println(unwrapOrDefault(r2, -1)); // -1 (with error logged)
// Enum usage
double earthWeight = 75.0;
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values()) {
System.out.printf("Weight on %s: %.2f%n", p, p.surfaceWeight(mass));
}
}
}
// Example 5: Complete OOP Design — Strategy Pattern
// File: StrategyPattern.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class StrategyPattern {
// Interface (strategy)
@FunctionalInterface
interface SortStrategy<T> {
List<T> sort(List<T> items);
}
// Concrete strategies
static <T extends Comparable<T>> SortStrategy<T> naturalOrder() {
return items -> items.stream().sorted().toList();
}
static <T> SortStrategy<T> by(Comparator<T> comparator) {
return items -> items.stream().sorted(comparator).toList();
}
// Context class
static class DataProcessor<T extends Comparable<T>> {
private final List<T> data;
private SortStrategy<T> strategy;
DataProcessor(List<T> data) {
this.data = List.copyOf(data);
this.strategy = naturalOrder(); // default strategy
}
void setStrategy(SortStrategy<T> strategy) {
this.strategy = strategy;
}
List<T> process() {
return strategy.sort(data);
}
}
record Employee(String name, int age, double salary) {}
public static void main(String[] args) {
List<Employee> employees = List.of(
new Employee("Charlie", 35, 80000),
new Employee("Alice", 28, 95000),
new Employee("Bob", 42, 70000)
);
DataProcessor<String> nameProcessor = new DataProcessor<>(
employees.stream().map(Employee::name).toList()
);
// Default: natural order
System.out.println("Names sorted: " + nameProcessor.process());
// Custom strategy: reverse order
nameProcessor.setStrategy(by(Comparator.reverseOrder()));
System.out.println("Names reversed: " + nameProcessor.process());
// Strategy using method reference
List<Employee> byAge = employees.stream()
.sorted(Comparator.comparingInt(Employee::age))
.toList();
byAge.forEach(e -> System.out.printf("%s: %d%n", e.name(), e.age()));
// Pattern matching for records
for (var emp : employees) {
switch (emp) {
case Employee(String n, int age, double sal) when sal > 85000 ->
System.out.println(n + " is a high earner");
case Employee(String n, _, _) ->
System.out.println(n + " is a regular employee");
}
}
}
}
| Section | Topic | URL |
| JLS §8 | Classes | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html |
| JLS §9 | Interfaces | https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html |
| JLS §8.1 | Class Declarations | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.1 |
| JLS §8.4.8 | Method Overriding | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.4.8 |
| JLS §8.8 | Constructor Declarations | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.8 |
| JLS §8.9 | Enum Types | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.9 |
| JLS §8.10 | Record Classes | https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-8.10 |
| JLS §9.4.3 | Default Methods | https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html#jls-9.4.3 |
| JEP 395 | Records | https://openjdk.org/jeps/395 |
| JEP 409 | Sealed Classes | https://openjdk.org/jeps/409 |
| JEP 441 | Pattern Matching for switch | https://openjdk.org/jeps/441 |
| JEP 440 | Record Patterns | https://openjdk.org/jeps/440 |