One of Java's strengths is its excellent support for object-oriented programming. This support is provided through classes and interfaces, which are the building blocks of all Java programs. The ability to effectively use classes and interfaces is an essential Java programming skill. The OCP Java SE 21 certification exam recognizes the importance of class and interface fundamentals and contains a substantial number of questions that test your knowledge of them. This module reviews these topics, introduces modern class forms added in Java 8 through Java 25, and identifies the key points you need to know in preparation for the exam.
In Java, classes, interfaces, and objects are the three pillars of the object-oriented programming paradigm. They each play distinct roles and have specific relationships with one another. Understanding how they interact is foundational to both effective Java development and certification success.
A class is a blueprint for creating objects. It defines the data (fields) and behaviors (methods) that objects created from the class will have. Every Java program consists of at least one class.
public class Animal {
String name;
int age;
void makeSound() {
System.out.println("Animal sound");
}
}
In modern Java, not every data-holding class needs to be written in this verbose form. Java 16 introduced records as a special-purpose class form for immutable data carriers. A record automatically generates a canonical constructor, accessor methods, equals(), hashCode(), and toString():
public record Point(int x, int y) { }
A Point record replaces what would otherwise require a full class with two fields, a constructor, two getters, and three overridden Object methods. Records are final by default and cannot extend other classes, though they may implement interfaces.
Java 17 introduced sealed classes, which restrict which classes may extend or implement them using the permits clause. Sealed classes work well with pattern matching and exhaustive switch expressions introduced in later Java versions:
public sealed interface Shape permits Circle, Rectangle, Triangle { }
public record Circle(double radius) implements Shape { }
public record Rectangle(double width, double height) implements Shape { }
public record Triangle(double base, double height) implements Shape { }
The compiler enforces that only the permitted subtypes exist, enabling exhaustive pattern matching without a default branch.
An interface is a reference type that defines a contract that implementing classes must fulfill. Interfaces cannot contain instance fields or constructors. Prior to Java 8, interfaces could contain only abstract method signatures and constants. Java 8 and later versions significantly expanded what interfaces can contain:
public static final
public interface AnimalBehavior {
// Abstract method - must be implemented
void makeSound();
// Default method - Java 8+
default void breathe() {
System.out.println("Inhale. Exhale.");
}
// Static utility method - Java 8+
static AnimalBehavior silent() {
return () -> { };
}
// Private helper - Java 9+
private void logBehavior(String action) {
System.out.println("Behavior: " + action);
}
}
Interfaces provide the primary mechanism for achieving abstraction and multiple inheritance of type in Java. A class may implement any number of interfaces simultaneously, which is not possible with class inheritance where only a single superclass is permitted.
An object is an instance of a class. It is created based on the structure and behavior defined by the class blueprint. Objects are created using the new keyword, which allocates heap memory and invokes the class constructor:
public class Dog extends Animal implements AnimalBehavior {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "Buddy";
myDog.age = 5;
myDog.makeSound();
myDog.breathe();
}
}
Java 10 introduced local variable type inference using the var keyword. The compiler infers the declared type from the initializer expression, reducing verbosity without losing static typing:
var myDog = new Dog(); // inferred as Dog
var point = new Point(3, 4); // inferred as Point
var may only be used for local variables with an initializer. It is not permitted for fields, method parameters, or return types.
new keyword or factory methods.
AnimalBehavior b1 = new Dog();
AnimalBehavior b2 = AnimalBehavior.silent();
b1.makeSound(); // Bark
b2.makeSound(); // silent - no output
Sealed interfaces narrow the set of permitted implementations, making the polymorphic type hierarchy explicit and verifiable at compile time.
Classes and interfaces are the building blocks of every Java application. Effective class design has a significant impact on overall application quality. Poor design decisions compound over time: failing to override hashCode() and equals() correctly causes objects to behave unexpectedly in collections such as HashSet and HashMap. Incorrect access modifiers expose internal state to unwanted manipulation from other packages. Poorly designed method signatures make APIs difficult to use and extend.
This module covers the following topics, each of which appears on the OCP Java SE 21 certification exam:
public, protected, package-private, and privateinstanceof operator, pattern matching for instanceof (Java 16+), and castingObject methods: equals(), hashCode(), and toString()Anonymous inner classes always extend directly from the Object class.
When you create an anonymous class that implements an interface, it implicitly extends Object. However, when you create an anonymous class from another class, it extends that class directly. In modern Java, anonymous classes implementing a single-abstract-method (SAM) interface are typically replaced by lambda expressions since Java 8:
// Anonymous class - still valid in all Java versions
Runnable r1 = new Runnable() {
public void run() {
System.out.println("anonymous class");
}
};
// Lambda - preferred in modern Java for SAM interfaces
Runnable r2 = () -> System.out.println("lambda");
Both are valid. The anonymous class is a distinct class that implicitly extends Object and implements Runnable. The lambda is not a class instance - it is a functional interface implementation handled by the JVM's invokedynamic instruction. When an anonymous class extends an existing class rather than implementing an interface, it extends that class directly:
class BaseGreeter {
public void greet() {
System.out.println("Hello from BaseGreeter");
}
}
// Anonymous class extending BaseGreeter - NOT extending Object
BaseGreeter g = new BaseGreeter() {
@Override
public void greet() {
System.out.println("Hello from anonymous subclass");
}
};
This anonymous class extends BaseGreeter, not Object directly, making the statement false.