December Code

Java OOP interview questions, with answers

The concepts behind object-oriented programming — inheritance, polymorphism, encapsulation, interfaces — have their own pages under OOP. This page is about how Java implements them, because that is what a Java interviewer asks next: which Object methods must be overridden together, what final means on a class, a method and a variable, in what order a new object is initialised, and what records, sealed classes and pattern matching changed. Every answer below comes with code, and every output shown was produced by compiling and running it.

The questions start with the contract every class inherits from Object, then initialisation, then the newer language features. Then take the free Java diagnostic — ten questions across every Java topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.Why must equals() and hashCode() be overridden together in Java?

    In short: Because hash-based collections find an object by hashCode() first and only then compare with equals(), so two equal objects with different hash codes are treated as different keys.

    The contract in java.lang.Object says that equal objects must have equal hash codes. HashMap and HashSet depend on it: they use hashCode() to choose a bucket and call equals() only on the entries in that bucket. Override equals() alone and two objects you consider equal will usually land in different buckets, so a set keeps both and a map lookup with an equal key comes back empty. The reverse is allowed: unequal objects may share a hash code, which costs only speed. Base both methods on the same fields, keep those fields unchanged while the object sits in a hash-based collection, and use Objects.hash, or a record, which generates both correctly, so an equal key finds the entry below.

    record Emp(int id) {}
    
    Map<Emp, String> m =
        new HashMap<>();
    m.put(new Emp(7), "Asha");
    String n = m.get(new Emp(7));
    System.out.println(n);
    // Asha
  2. 2.What does final mean on a variable, a method and a class in Java?

    In short: A final variable can be assigned only once, a final method cannot be overridden, and a final class cannot be extended — and a final reference still lets the object it points to change.

    final is one keyword with three jobs. On a local variable, a field or a parameter it forbids reassignment; a final field must be assigned exactly once, in its declaration, an initializer block or every constructor. It does not make the object immutable: the final list below can still grow; it just cannot be pointed at another list, so xs = new ArrayList<>() would not compile. On a method, final stops subclasses overriding it, which protects behaviour that other methods of the class depend on. On a class, final stops inheritance entirely, which is why String and the wrapper classes are final. finally and the deprecated finalize() are unrelated despite the name.

    final List<String> xs =
        new ArrayList<>();
    xs.add("ok");
    System.out.println(xs);
    // [ok]
  3. 3.In what order is a Java object initialised?

    In short: Static initialisation runs once, when the class is first used; then for each new object the superclass constructor runs first, then field initialisers and instance blocks in source order, then the constructor body.

    Class initialisation happens once per class: static fields and static blocks run in source order the first time the class is actively used, superclasses first. Object creation then goes top-down. A constructor's first statement is always a call to this(...) or super(...), inserted as super() if you write neither, so the parent's constructor completes before anything in the child runs. After super() returns, the class's field initialisers and instance initializer blocks run in the order they appear, and only then does the rest of the constructor body run, which is why B's field is initialised between A's and B's constructors below. The same order explains why calling an overridable method from a constructor is discouraged. This page's sample asks for the order of static and instance blocks.

    class A {
        A() { p("A"); }
        static void p(String s) {
            System.out.print(s);
        }
    }
    class B extends A {
        int x = f();
        B() { p("B"); }
        int f() {
            p("x");
            return 1;
        }
    }
    
    new B();
    // AxB
  4. 4.What is a record in Java, and when should you use one?

    In short: A record, standard since Java 16, is a concise immutable data class: declaring its components generates the constructor, accessors, equals(), hashCode() and toString().

    record Pt(int x, int y) {} declares a final class with two private final fields, a canonical constructor, accessor methods named x() and y() rather than getX(), and value-based equals(), hashCode() and toString(), as below. Records suit data carriers: transfer objects, map keys, several values returned from one method. A compact constructor validates the arguments without repeating the assignments. Records cannot extend another class, since they already extend java.lang.Record, and cannot declare instance fields beyond their components, but they can implement interfaces and add methods and static members. They are only shallowly immutable: a record holding a List still exposes a list that can change, so copy it in the constructor with List.copyOf when that matters.

    record Pt(int x, int y) {}
    
    Pt p = new Pt(2, 3);
    Pt q = new Pt(2, 3);
    System.out.println(p);
    // Pt[x=2, y=3]
    boolean e = p.equals(q);
    System.out.println(e);
    // true
  5. 5.What are sealed classes in Java?

    In short: A sealed class or interface, standard since Java 17, names exactly which classes may extend it, so the compiler knows every subtype and a switch over them needs no default.

    sealed interface Shape permits Sq, Rect says that only those two types may implement Shape. Each permitted subclass must itself be declared final, sealed or non-sealed, so the hierarchy stays closed or is reopened deliberately. The point is modelling a fixed set of alternatives, such as payment methods, parse results or shapes, where inheritance used to be all or nothing: either anyone could extend a class or, with final, no one could. Combined with records and pattern matching, a switch expression over a Shape handles each subtype, as below, and the compiler reports an error when a new subtype is added but not handled. Interviewers ask it to check whether your Java is current.

    sealed interface Shape
        permits Sq, Rect {}
    record Sq(int a)
        implements Shape {}
    record Rect(int w, int h)
        implements Shape {}
    
    static int area(Shape x) {
        return switch (x) {
            case Sq s ->
                s.a() * s.a();
            case Rect r ->
                r.w() * r.h();
        };
    }
    
    int a = area(new Rect(2, 3));
    System.out.println(a);
    // 6
  6. 6.What is the difference between a static nested class and an inner class in Java?

    In short: A static nested class is an ordinary class scoped inside another; an inner class is tied to an instance of its enclosing class and can read that instance's fields.

    A static nested class needs no outer object: you create it with new Outer.Nested(), and it can use only the outer class's static members directly. It is the right default, used for builders and helper types such as Map.Entry. A non-static inner class holds a hidden reference to the enclosing instance, so it is created from one, as outer.new Inner() below, and can use that object's fields. The hidden reference is also a classic memory leak: an inner object that outlives its outer object keeps the outer one alive. Local classes are declared inside a method, and anonymous classes are declared and instantiated in a single expression, which lambdas have largely replaced for one-method interfaces.

    class Outer {
        int n = 5;
        class Inner {
            int twice() {
                return n * 2;
            }
        }
    }
    
    Outer o = new Outer();
    Outer.Inner i = o.new Inner();
    System.out.println(i.twice());
    // 10
  7. 7.What did pattern matching for instanceof change in Java?

    In short: Since Java 16, obj instanceof String s both tests the type and declares s already cast, removing the separate cast line and the risk of casting to the wrong type.

    Before pattern matching, a type check was always followed by a cast: if (o instanceof String) { String s = (String) o; ... }. The pattern form declares the binding variable in the test itself, and the compiler scopes it to where the test is known to be true, so it can be used later in the same condition, as in o instanceof String s && !s.isEmpty(). instanceof is false for null, so the binding is never null. The same patterns work in switch since Java 21, including record patterns that take a record apart, such as case Pt(int x, int y). A long chain of instanceof checks is still a design smell; overriding a method, or a sealed hierarchy with a switch, is usually cleaner.

    Object o = "placement";
    int n = 0;
    if (o instanceof String s)
        n = s.length();
    System.out.println(n);
    // 9
  8. 8.How do you make a class immutable in Java?

    In short: Make the class final and every field private and final, set the fields only in the constructor, provide no setters, and copy any mutable object on the way in and on the way out.

    Immutability is a set of promises the class keeps. final on the class stops a subclass adding mutable state or overriding methods. private final fields are assigned once, in the constructor. No method changes state; an operation that would returns a new object instead, as String's methods do. The step most answers miss is defensive copying: if the constructor stores a caller's array or list directly, the caller can change it later, and a getter that returns the field lets anyone change it. Copy on the way in and on the way out, as below, or return an unmodifiable copy for a list. Records give you the first steps for free but share the same shallow-copy trap.

    final class Scores {
        private final int[] a;
        Scores(int[] a) {
            this.a = a.clone();
        }
        int[] get() {
            return a.clone();
        }
    }
    
    int[] raw = {70, 80};
    Scores s = new Scores(raw);
    raw[0] = 0;
    System.out.println(s.get()[0]);
    // 70

How the diagnostic asks it

One question from the Java bank, exactly as a sitting would show it. The bank has 5 on oop in java and 30 across Java.

OOP in Java · mediumJAVA-013

What does this Java code print?

class Demo {
    static { System.out.print("S"); }
    { System.out.print("I"); }
    Demo() { System.out.print("C"); }
}

public class Main {
    public static void main(String[] args) {
        new Demo();
        new Demo();
    }
}
  1. 1SICSIC
  2. 2SCICI
  3. 3ICSIC
  4. 4SICICcorrect

The static block runs once, when Demo is first initialised, printing S. Each new Demo() then runs the instance initialiser block before the constructor body, printing I then C, so two objects give SICIC. SICSIC runs the static block per object, but it belongs to the class. SCICI puts the constructor body before the instance block; instance initialisers run first, as if copied to the start of every constructor after the call to super(). ICSIC runs the static block after the first object's initialiser, but class initialisation happens before any instance code.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Java questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated