How Hirenix teaches
One chapter. 90 minutes.
Interview-ready.
Every concept starts with a real-world problem — the kind that actually shows up in production code. Nothing to cram; it just clicks. Every question comes with a model answer: exactly what to say in the room, and why. Then an AI mock interview on the same chapter.
- 📖Concept in 5 minutesNo jargon — straight to the point
- 🛠️Real-world problemThe kind production code throws at you
- 💬Model answerExactly what to say in the room
- 🧠FlashcardsRevise in 10 minutes
- 🤖AI mock interviewIt asks follow-ups too
- 📊Weak topicsSee exactly where you're stuck

The difference isn’t the content — it’s the filter. Only what’s actually used in production and actually asked in interviews. Textbook topics the industry never touches don’t make the cut.
What you’ll learn
- ●Classes and objects
- ●Constructors
- Encapsulation and access modifiersFree account
- Inheritance and superFree account
- Polymorphism: overloading vs overridingFree account
- Static vs dynamic bindingFree account
- Abstraction and abstract classesFree account
- InterfacesFree account
- Abstract class vs interfaceFree account
- Object class: equals, hashCode, toStringFree account
- Casting and instanceofFree account
- Composition vs inheritanceFree account
- Designing an immutable classFree account
- RecapFree account
- ●Project: Library System
- Project: Shape CalculatorFree account
- Project: Bank Account HierarchyFree account
Classes and objects
A builder does not sell you a drawing. They sell you a flat. But before the first flat existed, somebody drew a plan — three bedrooms here, kitchen there, balcony this wide. That one plan then produced forty flats. Every flat has the same layout and its own furniture, its own electricity bill, its own family living in it.
The plan is a class. Each flat is an object. You cannot live in the plan, and you cannot redraw the building by moving a sofa.
In production this is how every real system is shaped. Hirenix has one Resume class and about ninety thousand resume objects. One Payment class, one paying user's payment object per transaction. Writing the class once is what stops you from writing ninety thousand copies of the same logic.
🌍 Real-world example: the WhatsApp app on your phone is one class of thing; every chat inside it is an object with its own name, its own message list and its own unread count. Change the app's design once and every chat gets it. Change one chat's name and only that chat changes.
💡 class = a blueprint that describes what state an object holds and what it can do. 💡 object = one concrete thing built from that blueprint, with its own copy of the state. 💡 instance = another word for object. "Instantiate" = create an object. 💡 field (also instance variable) = a variable that belongs to each object. 💡 method = a function that belongs to the class and can see the object's fields. 💡
this= a reference to the object the method is currently working on.
What a class is made of
| part | belongs to | example |
|---|---|---|
| field | each object | String name; |
| method | the class, runs on one object | void introduce() { ... } |
| constructor | runs once per object, at new |
Student(String name, int roll) |
| static member | the class itself, one copy | static int totalCreated; (Chapter 1) |
What new actually does — three steps, in this order
Chapter 1 taught that objects live on the heap and references live on the stack. new Student("Waquar", 101) is where that becomes visible:
- Space for a
Studentis allocated on the heap, and its fields are set to defaults —nullforname,0forroll. - The constructor runs and fills those fields in.
- The address of that heap object is handed back, and you store it in a reference variable.
So Student a is not the student. It is an arrow pointing at the student. This one sentence is the whole reason for the next output:
a.name after changing c.name = Waquar Ahmed
a == c -> true | a == b -> false
d = null
Student c = a; did not copy the object. It copied the arrow, so two variables now point at one heap object, and changing it through c is visible through a. a == c is true because the two arrows hold the same address; a == b is false even if every field matched, because == compares addresses (Chapter 1). And d is a reference pointing at nothing at all — printing it gives null, and calling a method on it is where NullPointerException comes from.
Not every object needs new
A common interview question is "is new the only way to create an object?" — and the honest answer is no. Verified:
"hello" -> java.lang.String
Integer.valueOf -> java.lang.Integer
reflection -> Reflected
A String literal is already an object, made for you by the JVM from the String pool (Chapter 1). Integer.valueOf(7) is a static factory method — it hands you an object and may reuse a cached one instead of making a new one, which is exactly why the −128..127 Integer cache exists. And reflection (getDeclaredConstructor(...).newInstance(...)) creates one at runtime from the class object; frameworks like Spring build your objects this way, which is worth remembering for Chapter 7. Deserialisation and clone() are two more. new is the normal way, not the only way.
When to use it: make a class when a thing has state that belongs to it and behaviour that acts on that state — a Student has a name and a roll number and can introduce itself. That is the test. Chapter 1 gave you the alternative: a static utility method, which works purely off its arguments and holds nothing. Math.max(a, b) needs no object because there is no "max" to be a thing; calculateGrade(marks) as a static helper is fine. The moment you find yourself passing the same three values into every method together, those three values are an object waiting to happen.
When NOT to use it: do not create a class whose only job is to hold static methods and no state — that is a utility holder, and Java has that pattern, but calling it object-oriented design is self-deception. Equally, do not create an object where a single value would do: a class wrapping one int with no behaviour adds a heap allocation and a layer of indirection for nothing. And do not model something as a class because it is a noun in the requirements; model it as a class because something in your code needs to hold its state and ask it questions.
Standard definition: A class is a blueprint that defines the fields and methods its objects will have; an object is a runtime instance of that class with its own copy of the instance fields. The new operator allocates the object on the heap, initialises its fields to their defaults, runs the constructor, and returns a reference to it. A reference variable holds the address of the object, not the object itself, so assigning one reference to another creates a second arrow to the same object rather than a copy.
public class Student {
String name; // instance field - one copy per object
int roll;
static int totalCreated; // one copy for the whole class (Ch1)
Student(String name, int roll) {
this.name = name; // this = the object the constructor is building
this.roll = roll;
totalCreated++;
}
void introduce() {
System.out.println("I am " + name + ", roll " + roll);
}
public static void main(String[] args) throws Exception {
Student a = new Student("Waquar", 101);
Student b = new Student("Aisha", 102);
a.introduce();
b.introduce();
System.out.println("objects created = " + Student.totalCreated);
Student c = a; // a SECOND reference to the SAME object
c.name = "Waquar Ahmed";
System.out.println("a.name after changing c.name = " + a.name);
System.out.println("a == c -> " + (a == c) + " | a == b -> " + (a == b));
Student d = null; // a reference pointing at no object
System.out.println("d = " + d);
System.out.println("objects that exist without new:");
System.out.println(" \"hello\" -> " + "hello".getClass().getName());
System.out.println(" Integer.valueOf -> " + Integer.valueOf(7).getClass().getName());
System.out.println(" reflection -> "
+ Student.class.getDeclaredConstructor(String.class, int.class)
.newInstance("Reflected", 103).name);
System.out.println("objects created = " + Student.totalCreated);
}
}Constructors
When a new SIM card is activated, it does not just appear in your hand and start working. There is a switch-on step: the number is assigned, the plan is attached, the balance is set to zero, the KYC is stamped. Only after that is it a usable SIM. Nobody can use it before that step, and the step runs exactly once — a SIM is not activated twice.
A constructor is that activation step for an object. new gets the memory; the constructor makes the memory into a valid object.
In production this is a correctness tool, not a convenience. Hirenix's payment object must never exist with a null amount, because a later line will add it to a total. A constructor that demands the amount makes the broken object impossible to create, which is a much stronger guarantee than remembering to set it afterwards.
🌍 Real-world example: a bank does not open an account and then ask for your name. The form is the constructor — no name, no PAN, no account. The account cannot exist in a half-filled state.
💡 constructor = a special block that runs once when an object is created, to initialise it. 💡 default constructor = the no-argument constructor the compiler writes for you, when you write none. 💡 constructor overloading = several constructors in one class, differing in their parameters. 💡 constructor chaining = one constructor calling another, with
this(...)orsuper(...). 💡 instance initializer block ={ ... }with nostatic, run on everynewbefore the constructor body.
Constructor vs method — three differences, and the middle one is the trap
| constructor | method | |
|---|---|---|
| name | must be exactly the class name | anything |
| return type | has none at all — not even void |
must have one |
| called | once, by new |
any number of times, by you |
The second row is where candidates lose the question. Write void Student() { ... } and it compiles perfectly — but it is now an ordinary method that happens to be named Student, so new Student() never calls it and your fields stay null. A void in front turns a constructor into a method, silently. No warning, no error.
The default constructor: the half of the rule everybody forgets
If you write no constructor at all, the compiler inserts a public no-argument one for you. That is why new Student() works on a class you never gave a constructor.
The moment you write any constructor, that gift is withdrawn. Verified:
NoDefaultCtor.java:6: error: constructor Student in class Student cannot be applied to given types;
public static void main(String[] a) { Student s = new Student(); System.out.println(s); }
^
required: String
found: no arguments
reason: actual and formal argument lists differ in length
This is the real-world version: working code breaks the first time somebody adds a parameterised constructor to a class that other code was constructing with new Student(). If you want both, write both — the no-arg one does not come back on its own.
Overloading and chaining — write the logic once
Several constructors are fine; they must differ in their parameter list. But three constructors each setting the same three fields is three places for the same bug, so one of them does the real work and the others delegate to it with this(...):
--- new Employee() ---
[instance block] runs on every new, before the constructor body
Employee(String,String,int) built Unknown
Employee() body ran last
Read the order carefully. Employee() called this("Unknown", "Bench", 0) on its first line, so the three-argument constructor finished before Employee()'s own body printed. Delegation happens first, always.
Why this() and super() cannot both be written
Both are required to be the first statement in the constructor, and two statements cannot both be first. That is the entire answer, and the compiler says exactly that:
ThisSuperBoth.java:5: error: call to this must be first statement in constructor
this(5);
^
It is not a limitation to memorise — it follows from the rule that the parent must be fully constructed before the child touches anything. this(...) defers that job to the constructor it delegates to, which will itself start with a super(...).
What a constructor cannot be
| modifier | allowed? | why |
|---|---|---|
private / protected / public |
✅ yes | a private constructor is how Singleton and static factories work (Chapter 1's static topic showed the mechanism) |
static |
❌ error: modifier static not allowed here |
static means "no object involved" — but a constructor's whole job is initialising an object |
final |
❌ error: modifier final not allowed here |
final means "cannot be overridden", and constructors are not inherited, so they are never overridden |
abstract |
❌ | abstract means "a subclass will supply the body" — same reason |
Both error messages above are verbatim from JDK 17.
The full initialization order — Chapter 1's static block, completed
Chapter 1 showed a static block running before main. With inheritance in the picture, here is the whole sequence, verified:
--- new Child() #1 ---
1 Parent static block
2 Child static block
3 Parent instance block
4 Parent constructor
5 Child instance block
6 Child constructor
--- new Child() #2 (static blocks should NOT repeat) ---
3 Parent instance block
4 Parent constructor
5 Child instance block
6 Child constructor
Three things to take from this. First, the order is not "parent fully, then child fully" — static blocks for both classes run before any instance work, because that is class loading, which happens once. Second, per object, the parent's instance block and constructor complete before the child's start. Third, the second new proves static blocks do not repeat, while everything else does.
Also verified in the Employee run: the instance block printed once per object, not once per constructor invocation, even though two constructors ran for each object. It is tied to new, not to the constructor.
When to use it: write an explicit constructor whenever an object has a field that must never be missing — an id, an amount, an owner. Making it a constructor parameter turns "somebody forgot to set it" from a runtime bug into a compile error, which is the strongest form of the guarantee. Use overloading plus this(...) delegation when there are genuinely a few different sets of information a caller might have, and keep exactly one constructor doing the real assignment. Use a private constructor when the class should not be instantiated freely — a utility holder, or a static factory such as Integer.valueOf.
When NOT to use it: do not write a constructor with six or seven parameters — nobody can read new Resume(a, b, true, false, null, 3, "") at the call site, and two booleans next to each other are a bug waiting to happen. That is where a static factory with a meaningful name (Resume.blank(), Resume.fromPdf(file)) or a builder becomes worth it. Do not put slow or risky work in a constructor — a network call, a file read, a database query — because the object is half-built while it runs and there is no good way to report the failure. Construct cheaply, then call an explicit load(). And do not write an empty no-arg constructor just to have one; if you write no constructor, the compiler already gives you exactly that.
Standard definition: A constructor is a special block with the same name as its class and no return type, invoked once by new to initialise a newly allocated object. If a class declares no constructor the compiler supplies a public no-argument default constructor, and that default disappears as soon as any constructor is declared. Constructors can be overloaded and can delegate with this(...) or invoke the superclass constructor with super(...), but only one of those may appear and it must be the first statement. A constructor cannot be static, final or abstract. On object creation, static initialisers run once at class loading, then for each object the instance initialiser blocks and constructors run from the superclass downwards.
public class Employee {
static int hired;
String name;
String dept;
int salary;
static { System.out.println("[static block] class loaded, hired=" + hired); }
{ System.out.println("[instance block] runs on every new, before the constructor body"); }
Employee() { // no-arg
this("Unknown", "Bench", 0); // this() = call another constructor, FIRST line
System.out.println(" Employee() body ran last");
}
Employee(String name) { // overload
this(name, "Bench", 25000);
}
Employee(String name, String dept, int salary) { // the real one
this.name = name;
this.dept = dept;
this.salary = salary;
hired++;
System.out.println(" Employee(String,String,int) built " + name);
}
public static void main(String[] args) {
System.out.println("--- new Employee() ---");
Employee a = new Employee();
System.out.println("--- new Employee(\"Waquar\") ---");
Employee b = new Employee("Waquar");
System.out.println("a = " + a.name + "/" + a.dept + "/" + a.salary);
System.out.println("b = " + b.name + "/" + b.dept + "/" + b.salary);
System.out.println("hired = " + hired + " (4 constructor calls, 2 objects, 2 counter bumps)");
}
}Project: Library System
This is the chapter's entry project, and it deliberately uses only the first three topics — classes and objects, constructors, encapsulation. No inheritance, no interfaces, no abstract classes. If you have read those three topics, you can finish this, and by the end you will have written the single most common OOP interview task in India: "design a library system".
What you will build: two classes, a Book that cannot be put into an invalid state, and a Library that holds books and can find them. It runs from the command line and prints a formatted report.
What this project proves you can do: put state and the rules that protect it in the same class, use a constructor as a gate rather than a formality, and model HAS-A with a field instead of reaching for extends.
Step 1 — Decide what is a class, and what each one owns
Before typing, answer the question from the classes-and-objects topic: what has state that belongs to it?
- A book has an ISBN, a title, an author, a number of copies, and how many are currently out. It can be borrowed and returned. → a class.
- A library has a name and a collection of books. It can add and find. → a class.
- A borrower? Not in this project. Adding one would need a second collection and a relationship, and that belongs to the capstone project.
Note what is not happening here: a Library does not extend anything, and a Library is not a Book. A Library has books, so books live in a field. That is the IS-A / HAS-A test applied before a single line is written.
Step 2 — Write Book, and make the invalid book impossible
private final String isbn; // identity - set once, never changes
private final String title;
private int totalCopies;
private int borrowedCopies;
Two decisions worth noticing:
isbnandtitlearefinal. They are identity, they are set at construction, and there is no legitimate reason to change them later. This is stronger than a getter with no setter — the compiler enforces it.borrowedCopiesis not in the constructor's parameter list. A brand-new book has nothing out, so the constructor sets it to0itself. Do not accept a parameter for a value the object can work out.
Then the constructor validates:
if (isbn == null || isbn.isBlank()) throw new IllegalArgumentException("isbn required");
if (totalCopies < 1) throw new IllegalArgumentException("need at least 1 copy");
This is the whole point of the constructors topic. A Book with a blank ISBN or zero copies cannot be created, so no later code has to check for one. Verified:
rejected: isbn required
rejected: need at least 1 copy
Step 3 — Expose operations, not fields
The tempting API is getBorrowedCopies() and setBorrowedCopies(int). Do not write it. Here is the difference:
| API | who enforces the rule |
|---|---|
setBorrowedCopies(n) |
every caller, separately, forever |
borrow() returning boolean |
the Book, once |
So Book exposes exactly two mutating methods, and each refuses when it should:
boolean borrow() {
if (available() <= 0) return false;
borrowedCopies++;
return true;
}
And available() is a derived value — totalCopies - borrowedCopies — computed on demand rather than stored. A stored copy of a derived value is a second source of truth, and the two will drift.
Verified, with a book that has exactly one copy:
borrow Head First Java -> true
borrow it again -> false (only 1 copy)
return it -> true
return it again -> false (nothing is out)
All four cases behave, including the one people forget: returning a book nobody borrowed. Without that check, borrowedCopies would go negative and available() would report more copies than the library owns.
💡 Why
booleanand not an exception? Failing to borrow is a normal outcome, not a bug — the book is simply out. Exceptions are Chapter 4; for now, return abooleanfor expected failures and throw only for programmer errors, which is what the constructor does.
Step 4 — Write Library, holding books in a field
private final String name;
private final Book[] shelf; // fixed-size store
private int count;
An array, not an ArrayList — collections are Chapter 3, and Chapter 1's arrays are enough here. count tracks how many slots are actually filled, because shelf.length is the capacity, not the contents.
addBook enforces two rules the library owns, not the book:
if (count == shelf.length) return false; // shelf full
if (findByIsbn(b.getIsbn()) != null) return false; // duplicate ISBN
Notice where the duplicate rule lives. A Book cannot know whether another book with its ISBN exists — only the Library can. Putting each rule in the class that has the information to check it is what encapsulation actually means in practice. Verified:
duplicate ISBN accepted? false
Step 5 — Search, and decide what "not found" looks like
Book findByIsbn(String isbn) {
for (int i = 0; i < count; i++) {
if (shelf[i].getIsbn().equals(isbn)) return shelf[i];
}
return null;
}
Two things:
.equals(isbn), not==. Chapter 1's lesson, and the first place in this project where getting it wrong would fail silently for some inputs and work for others.- Return
nullfor not-found, and the caller checks. Verified:findByIsbn("999") = null— no crash. (Java's better answer isOptional, which is Chapter 5.)
Step 6 — Print a report a human can read
return String.format("%-14s %-26s %-18s %2d/%2d",
isbn, title, author, available(), totalCopies);
%-14s left-aligns in 14 characters, %2d right-aligns a number in 2. That is what makes the columns line up:
=== Hirenix Community Library (3 titles) ===
ISBN TITLE AUTHOR AVAIL
978-81-001 Let Us Java Y. Kanetkar 2/ 2
978-81-002 Head First Java K. Sierra 1/ 1
978-81-003 Effective Java J. Bloch 3/ 3
Note that the second report() at the end of the run shows all three books back at full availability — the borrow and the return balanced out, which is the quiet proof that the state changes were correct.
Run it
$ javac LibraryApp.java
$ java LibraryApp
What to extend, if you want more
Each of these uses only what you already know:
findByAuthor(String)returning a count, so you practise looping without needing a collection.- A
Memberclass with a name and a borrow limit, and pass it intoborrow(Member m). That is a second class and a relationship — and it is the shape the capstone project builds on. - A
totalCopiesInLibrary()onLibrarythat sums across the shelf. - Replace the array with
ArrayListafter Chapter 3, and notice thatLibrary's public methods do not change at all. That is encapsulation paying you back.
The interview answer this project gives you
When asked "design a library system", do not start listing classes. Say this:
"Two classes.
Bookowns its own state — ISBN and title arefinalbecause they are identity, and the only ways to change the copy count areborrow()andgiveBack(), which both refuse when they should, so the count can never go negative. The constructor validates, so a book with a blank ISBN or zero copies cannot be created at all.Libraryhas books rather than being one, and it owns the rules only it can check — a duplicate ISBN, and a full shelf. Not-found returnsnullfor now;Optionalwould be better."
That answer demonstrates encapsulation, constructor validation, HAS-A, derived state and a considered API — from a project small enough to write in twenty minutes.
public class LibraryApp {
// ---------- Book: one class, fully encapsulated ----------
static class Book {
private final String isbn; // identity - set once, never changes
private final String title;
private final String author;
private int totalCopies;
private int borrowedCopies;
Book(String isbn, String title, String author, int totalCopies) {
if (isbn == null || isbn.isBlank()) throw new IllegalArgumentException("isbn required");
if (title == null || title.isBlank()) throw new IllegalArgumentException("title required");
if (totalCopies < 1) throw new IllegalArgumentException("need at least 1 copy");
this.isbn = isbn;
this.title = title;
this.author = author;
this.totalCopies = totalCopies;
this.borrowedCopies = 0;
}
// read-only: getters, no setters
String getIsbn() { return isbn; }
String getTitle() { return title; }
String getAuthor() { return author; }
int available() { return totalCopies - borrowedCopies; }
// the ONLY two ways state can change, and both check first
boolean borrow() {
if (available() <= 0) return false;
borrowedCopies++;
return true;
}
boolean giveBack() {
if (borrowedCopies == 0) return false; // nobody borrowed it
borrowedCopies--;
return true;
}
String line() {
return String.format("%-14s %-26s %-18s %2d/%2d",
isbn, title, author, available(), totalCopies);
}
}
// ---------- Library: holds Books (HAS-A) ----------
static class Library {
private final String name;
private final Book[] shelf; // fixed-size store (Ch1 arrays)
private int count;
Library(String name, int capacity) {
this.name = name;
this.shelf = new Book[capacity];
}
boolean addBook(Book b) {
if (count == shelf.length) return false;
if (findByIsbn(b.getIsbn()) != null) return false; // no duplicate ISBN
shelf[count++] = b;
return true;
}
Book findByIsbn(String isbn) {
for (int i = 0; i < count; i++) {
if (shelf[i].getIsbn().equals(isbn)) return shelf[i];
}
return null;
}
void report() {
System.out.println("=== " + name + " (" + count + " titles) ===");
System.out.println(String.format("%-14s %-26s %-18s %s", "ISBN", "TITLE", "AUTHOR", "AVAIL"));
for (int i = 0; i < count; i++) System.out.println(shelf[i].line());
}
}
public static void main(String[] args) {
Library lib = new Library("Hirenix Community Library", 5);
lib.addBook(new Book("978-81-001", "Let Us Java", "Y. Kanetkar", 2));
lib.addBook(new Book("978-81-002", "Head First Java", "K. Sierra", 1));
lib.addBook(new Book("978-81-003", "Effective Java", "J. Bloch", 3));
boolean dup = lib.addBook(new Book("978-81-001", "Let Us Java (2nd)", "Y. Kanetkar", 1));
System.out.println("duplicate ISBN accepted? " + dup);
lib.report();
System.out.println("--- borrowing ---");
Book hf = lib.findByIsbn("978-81-002");
System.out.println("borrow Head First Java -> " + hf.borrow());
System.out.println("borrow it again -> " + hf.borrow() + " (only 1 copy)");
System.out.println("return it -> " + hf.giveBack());
System.out.println("return it again -> " + hf.giveBack() + " (nothing is out)");
System.out.println("--- the constructor refuses a broken Book ---");
try {
new Book("", "No ISBN", "Nobody", 1);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
try {
new Book("978-81-004", "Zero Copies", "Nobody", 0);
} catch (IllegalArgumentException e) {
System.out.println("rejected: " + e.getMessage());
}
System.out.println("--- a missing book is null, not a crash ---");
System.out.println("findByIsbn(\"999\") = " + lib.findByIsbn("999"));
lib.report();
}
}Object-Oriented Javainterview questions & answers
10 sample questions below — 201+ in the full bank inside.
Can a class be both abstract and final?
No. abstract means it must be extended and final means it cannot be extended, so the two cancel out and javac reports illegal combination of modifiers: abstract and final. If you want to ban both instantiation and subclassing, use a final class with a private constructor instead.
In simple terms: A door marked "must be used" and "permanently sealed" at the same time. Example: for a utility holder, final class Utils with a private Utils() {} says it precisely, and does not invite anyone to try extending it.
What is an abstract class, and why can't it be instantiated?
An abstract class is declared with the abstract keyword and is meant to be extended rather than used directly. It cannot be instantiated because it may contain abstract methods — signatures with no body — so new Shape() would hand you an object on which area() could be called with no code to run. javac says: Shape is abstract; cannot be instantiated.
In simple terms: An exam answer sheet has the cover page printed and the answer pages blank; the board cannot hand it in as a completed paper. Example: it is not a safety rail bolted on — there genuinely is nothing to execute for the missing methods.
What is the difference between an abstract class and an interface?
An abstract class models what a thing IS and you may extend only one; an interface models what a thing CAN DO and you may implement any number. An abstract class can hold instance fields, a constructor, members of any access level and final methods. An interface holds only public static final constants, has no constructor and no per-object state.
In simple terms: You belong to one family and hold many memberships. The family gave you a surname and a history; a gym card just gives you access. Example: Manager extends Employee implements Printable, Auditable is one family and two memberships, on one line.
What is abstraction in OOP?
Abstraction means exposing what an object does while hiding how it does it. The caller is given a smaller contract than the full reality, so they can use the thing without knowing its mechanism — and the mechanism can be replaced without touching them. In Java it is achieved with abstract classes and interfaces.
In simple terms: You press the accelerator without being told whether the car is petrol or electric. Example: a caller holding a Payment reference with a receipt() method never learns whether the money moved over UPI or a card.
How do you make a field readable but never changeable from outside?
Give it a getter and no setter, and mark it private final so even the class cannot reassign it after construction. Set it in the constructor. A public field cannot express this distinction at all — it is readable and writable or nothing.
In simple terms: Your date of birth is printed on your ID for anyone to read and cannot be edited by them. Example: private final String holder with getHolder() and no setHolder() means the holder is visible for the object's whole life and never changes.
What is the difference between upcasting and downcasting?
Upcasting treats an object as its supertype. It is implicit, needs no cast syntax and is always safe, because the object unavoidably IS an instance of the supertype. Downcasting treats a supertype reference as a subtype, must be written explicitly, and is verified by the JVM at runtime, throwing ClassCastException if the claim was false.
In simple terms: Airport security treats everyone as a passenger, which is always true. Someone claiming you are a pilot may be wrong, and that fails on the spot. Example: Vehicle v = car; needs no cast, while Car c = (Car) v; is a claim the JVM will check.
Can you use an abstract class and an interface together? Is that a compromise?
You can, and it is the standard shape rather than a compromise. An interface declares the public contract and an abstract class implements the parts that are shared, leaving the rest to subclasses. The JDK does exactly this with Collection and AbstractCollection.
In simple terms: A rulebook plus a starter kit that already covers the common cases. Example: abstract class Shape implements Drawable satisfies label() from the interface's default and passes asciiArt() down to its children, alongside its own abstract area().
What does the default toString() return, and why should you override it?
The class name, then @, then the hash code in hexadecimal — for example java.lang.Object@3fee733d. The hex part varies from run to run, so never treat a specific value as fixed. Override it because one method changes every log line, debugger view and println for the life of the class.
In simple terms: A parcel with only a barcode on it tells the sorter nothing useful. Example: Full[roll=101, name=Waquar] is readable and Full@452b3a41 is not, and the second is what you will be staring at in a production log at 2am.
What is the default access level if you write no modifier, and when is it the right choice?
Package-private — visible inside the same package only. It is the right choice for a helper class or method that is an implementation detail of one package: callers inside the package can use it, and nothing outside can depend on it, so you stay free to change or delete it.
In simple terms: A staff-only corridor inside one office — everyone on that floor uses it, and no visitor knows it exists. Example: a package-private ParseHelper can be rewritten any day, whereas a public one may already have callers you have never seen.
Give the one-line version of abstract class versus interface.
An abstract class models what a thing IS and you get exactly one; an interface models what a thing CAN DO and you can have any number. Everything else — state, constructor, final methods — follows from that.
In simple terms: Family versus memberships. Example: if you can say the difference in one sentence and then justify the three surviving differences after Java 8, you have answered the chapter's signature question completely.
191+ more Object-Oriented Java questions inside
Create a free account to read the full question bank, learn every topic, and practise with an AI mock interview.
Unlock all questions — freeReady to practise Object-Oriented Java?
Unlock every topic free, then face an AI interviewer that asks follow-ups and grades your answers.