- Learn
- Computer Science & IT
- Programming
- Object-Oriented Programming with Java
- Java Inheritance and Polymorphism
Java Inheritance, Interfaces and Polymorphism
extends and super, override inherited methods, and give your classes useful toString, equals and hashCode methods from Object. Define shared behavior with abstract classes and interfaces, predict which method runs through polymorphism and dynamic binding, and model fixed options and simple data with enums and records. Worked code examples show each step, along with the mistakes beginners make most often.BeginnerLesson 8 of 11 in Object-Oriented Programming with Java Course
What you will learn
- Build a new class on top of an existing one with
extends, know what the new class inherits, and make sure the inherited part of every object is set up correctly withsuper.- Create a subclass with extendsDeclare a subclass, work out which members it inherits and can use, and decide with the is-a test whether inheritance fits.
- Set up objects with superclass constructorsPass values to the superclass constructor with
super(...), predict the order in which constructors run, and avoid the classic constructor mistakes.
- Create a subclass with extends
- Change inherited behavior by overriding methods, build on the superclass version with
super, and give your classes meaningfultoString,equalsandhashCodemethods fromObject.- Override inherited methodsReplace or extend an inherited method with a correct override, call the superclass version with
super, and tell overriding apart from overloading. - Use and override the methods of ObjectExplain why every object has
toString,equals,hashCodeandgetClass, read their default behavior, and write a usefultoString. - Override equals and hashCode correctlyDefine when two objects are equal with a correct
equals(Object), keephashCodeconsistent with it, and handle equality across a class hierarchy.
- Override inherited methods
- Use abstract classes to share code among closely related classes, use interfaces to promise behavior across unrelated classes, and choose the right tool for a design.
- Write abstract classes and abstract methodsDeclare abstract classes and methods, complete them in concrete subclasses, and use an abstract class to share fields and code.
- Define and implement interfacesWrite an interface as a contract, implement it in one or more classes, and combine interfaces with inheritance.
- Add default, static and private methods to interfacesGive interfaces shared behavior with default methods, add helper methods with static and private methods, and resolve conflicts between default methods.
- Choose between an abstract class and an interfaceCompare abstract classes and interfaces and pick the right one, or both together, for a given design.
- Write abstract classes and abstract methods
- Refer to objects through general types, predict which method runs at run time, convert types safely, and write code that keeps working when new subclasses are added.
- Refer to objects through superclass and interface typesDistinguish a variable's declared type from the object's actual type, and use superclass and interface types for variables, parameters and lists.
- Predict which method runs with dynamic bindingTrace method calls through a class hierarchy, separating what the compiler decides from what is decided at run time.
- Convert between types with casts and instanceofDowncast safely with
instanceofand pattern matching, recognizeClassCastException, and know when a type test signals a missing overridden method. - Design flexible code with polymorphismWrite methods and collections against general types so that new classes fit in without changes, and make classes sortable with
Comparable.
- Refer to objects through superclass and interface types
- Represent a fixed set of options with an enum and a simple piece of data with a record, using the members the compiler generates for you.
- Represent fixed sets of values with enumsDeclare enums, use their built-in methods and
switch, and give enum constants fields, constructors and behavior. - Model data with recordsDeclare records for simple data, use the generated constructor, accessors,
equals,hashCodeandtoString, and validate data in a compact constructor.
- Represent fixed sets of values with enums
Java Inheritance and Polymorphism study guide
1Extend classes with inheritance
A subclass starts with the fields and behavior of an existing class, then adds what makes it different. That relationship affects both how other code can use the object and how Java builds it. Follow a person and a student through their declarations and constructors: private data stays under the superclass's control, and constructor calls supply the values that inherited state needs.
1.1 Build a subclass and protect its data
Inheritance creates a subclass from an existing superclass. In class Student extends Person, every student is a person. A student object holds its person state and its extra student state together in one object.
Add the student-specific parts
Here is a small superclass and a subclass:
class Person {
private String name;
public Person(String name) { this.name = name; }
public String getName() { return name; }
}
class Student extends Person {
private int credits;
public Student(String name) { super(name); }
public void study() { credits += 5; }
public int getCredits() { return credits; }
}
Student inherits getName(). It adds a credits field and two methods. After Student s = new Student("Ada Lovelace");, the credits field starts at 0. Two calls to s.study() raise it first to 5 and then to 10. s.getName() still returns "Ada Lovelace": the inherited method reads the private field declared in Person.
The statement super(name) passes the name to the Person constructor. Constructors are not inherited, so Student declares its own constructor. A subclass may add methods and override inherited behavior, but it cannot remove an inherited public method from its public API.
Know which members are accessible
| Access modifier | Where the member can be accessed |
|---|---|
public | Any class with access to the declaring type |
protected | The same package, plus subclass access across packages |
| No modifier | The same package |
private | The declaring class |
Private members are not inherited. The private name field still exists inside each student, but Student cannot read it directly. Keeping the field private and providing a getter lets Person control its own data. Across packages, protected instance access has an extra restriction: subclass code accesses the member through itself or a reference of its own subclass type, not through an arbitrary superclass reference.
Test the relationship before extending
Java allows one direct superclass per class. A chain such as GraduateStudent extends Student and Student extends Person is valid; extends Student, Teacher is not. A class with no explicit superclass extends Object. Object itself has no superclass.
Use the is-a test: can every subclass object be used wherever the superclass is expected? A truck is a vehicle. A customer has an address, but a customer is not an address. For that second relationship, use composition:
class Customer {
private Address address;
}
Here Address is another class. The field lets a customer use an address without claiming to be one. Extending a class just to borrow a convenient method also inherits its whole public contract. Finally, a final class cannot be extended. Because String is final, class ShortString extends String fails to compile.
Key Takeaways
- A subclass object includes superclass state and any state the subclass adds.
- Private superclass fields are accessed through the superclass's methods.
- A class extends one direct superclass; constructors are not inherited.
- Use inheritance for an is-a relationship and composition for a has-a relationship.
- A
finalclass cannot be extended.
1.2 Pass values through a constructor chain
A subclass constructor must arrange construction of the superclass part of the same object. It can call a superclass constructor with super(...), or delegate to another constructor in its own class with this(...).
Choose the superclass constructor
class Person {
private final String name;
Person(String name) { this.name = name; }
}
class Student extends Person {
private final String studentId;
Student(String name, String studentId) {
super(name);
this.studentId = studentId;
}
Student(String name) {
this(name, "no id yet");
}
}
The two-argument constructor passes the name to Person and stores the ID itself. The one-argument constructor delegates to it. A constructor contains at most one explicit constructor invocation: this(...) or super(...). Following the this(...) chain eventually reaches a constructor that invokes super(...).
If neither is written, Java inserts super(). That fails here because Person has only a constructor requiring a name. Declaring any constructor prevents Java from supplying a default no-argument constructor. A class with no declared constructor gets a default one that calls super().
Writing new Person(name) inside Student does something else: it constructs a separate object. It does not initialize the current student's person state or replace the required superclass constructor call.
Trace initialization in order
class Base {
Base() { System.out.println("Base ready"); }
}
class Middle extends Base {
Middle() { System.out.println("Middle ready"); }
}
class Top extends Middle {
Top() { super(); System.out.println("Top ready"); }
}
new Top() enters the constructor chain up to Object. As calls return, the output is "Base ready", "Middle ready", then "Top ready". Each class's field initializers run after its superclass constructor returns and before that class's remaining constructor body.
An overridable method called by a superclass constructor can already run a subclass override. If that override reads a subclass reference field whose assignment has not run, it sees null, even if the field has a declaration initializer. Keep constructor work to setting up state; avoid calls to methods subclasses can override.
Recognize the Java 25 change
In Java 25, a constructor can validate parameters before its explicit constructor invocation:
Student(String name, String studentId, int credits) {
if (credits < 0) {
throw new IllegalArgumentException("negative credits");
}
super(name);
this.studentId = studentId;
}
Java 25 also allows simple early assignments to fields declared in the current class without declaration initializers. It still forbids reading the current object's fields, calling its instance methods, or passing this around before the invocation. For standard language levels before Java 25, keep super(...) or this(...) first. Java 22–24 offered earlier forms as preview features.
Key Takeaways
super(...)constructs the superclass part of the current object.- An implicit
super()needs an accessible no-argument superclass constructor. this(...)delegates within the same class; its chain must reach a superclass constructor.- Field initializers run after the superclass constructor returns.
- Java 25 permits restricted work before an explicit constructor invocation.
- An override called during construction may observe subclass fields before their assignments.
2Override methods and use the Object class
Inherited methods give related objects a shared starting point. Overriding lets each subclass answer the same call in its own way, while the methods inherited from Object control how an object prints and compares with others. Small signature differences matter here: a method can look right to a reader yet fail to replace the method library code actually calls. Equality also has promises that hash-based collections rely on.
2.1 Replace inherited behavior with an override
An override gives a subclass its own implementation of an inherited instance method. Its name and parameter types match the original method, so callers can keep using the same operation.
Extend a working method
Suppose an employee's yearly pay is twelve monthly payments. A manager also receives a yearly bonus:
class Employee {
private final double monthlySalary;
Employee(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public double yearlyPay() { return 12 * monthlySalary; }
}
class Manager extends Employee {
private final double bonus;
Manager(double monthlySalary, double bonus) {
super(monthlySalary);
this.bonus = bonus;
}
@Override
public double yearlyPay() {
return super.yearlyPay() + bonus;
}
}
For new Manager(3000.0, 5000.0), the superclass call returns 36000.0. Adding 5000.0 gives 41000.0. super.yearlyPay() deliberately selects the superclass implementation. The superclass method remains available inside the subclass even though ordinary calls on the manager run the override.
Writing return yearlyPay() + bonus; calls the manager's own method again. No call finishes, so execution eventually fails with StackOverflowError. The missing super. changes the meaning of the call.
Let the compiler check the signature
Put @Override above each overriding method. A misspelling such as yearlypay() or an extra parameter then causes a compile error. Without the annotation, Java may accept a new method that callers never use.
A valid override follows these rules:
- Its parameter types match exactly.
- Its return type is the same, or a subtype when returning an object type. That permitted narrowing is a covariant return type.
- Its access is at least as open as the inherited method's access.
- It does not add checked exceptions that the inherited declaration forbids.
For example, public String toString() cannot be replaced with package-private String toString(). Existing callers must retain access.
Separate overriding from overloading and hiding
| Declaration | What it does |
|---|---|
| Same instance-method signature in a subclass | Overrides inherited behavior |
| Same name, different parameter list | Adds an overload |
| Same static-method signature in a subclass | Hides a static method |
role() and role(String language) are overloads; the second does not replace the first. Overload choice uses declared argument types at compile time. Static methods are also selected from the type used at compile time, so they are not dynamically overridden.
A private method is not inherited; a same-named subclass method is independent. A final method cannot be overridden, and constructors cannot be overridden. Beyond compiling, the method must still fulfill its original purpose. A manager override that returns monthly pay would break code that totals yearly pay even if its signature were correct.
Key Takeaways
- An override matches an inherited instance method's name and parameter types.
@Overridecatches accidental overloads and misspellings.super.method()reuses the superclass implementation.- An override cannot reduce access or broaden the permitted checked exceptions.
- Overloading changes parameters; static methods are hidden, and final methods cannot be overridden.
2.2 Give objects useful text and identify their class
Every Java class extends Object directly or indirectly. Arrays are objects too. This shared root provides methods such as toString(), equals(Object), hashCode() and getClass() even when a class declares none of them.
Replace the default printed description
The inherited Object.toString() produces a class name, @, and the object's hash code in hexadecimal. Text such as "school.Person@1b6d3586" identifies the class but does not explain the person's data. The number may change across runs and is not a memory address promised by Java.
A useful override shows the fields readers need. Here a student has inherited getName() and stores a credits field:
@Override
public String toString() {
return getName() + " (" + credits + " credits)";
}
For a student named Ada Lovelace with 10 credits, System.out.println(s) prints "Ada Lovelace (10 credits)". The print call obtains the object's text automatically. String concatenation does this too: "Student: " + s includes the same description.
For a null reference, printing and concatenation produce "null". An explicit call s.toString() when s is null throws NullPointerException, because there is no object to receive that call.
If Person.toString() already returns the person's name, a subclass can build on it:
@Override
public String toString() {
return super.toString() + ", student id " + studentId;
}
For ID "S-17", that produces "Ada Lovelace, student id S-17". The usefulness of super.toString() depends on what the superclass actually returns. If it still uses Object's version, it contributes the default class-and-hash text.
Keep identity and class information separate
Object.equals(Object) compares identity: it returns true when both references refer to the same object. Two separate new Person("Ada") objects are not equal under that inherited implementation, despite having matching names. Classes such as String, Integer and ArrayList supply value-based overrides.
getClass() reports the actual runtime class:
Person p = new Student("Ada Lovelace");
System.out.println(p.getClass().getSimpleName());
This prints "Student" when Student has the constructor shown. The variable's declared type does not alter the object's class. getClass() is final and cannot be overridden.
hashCode() supports hash-based collections. Other methods from Object include the protected clone() and the thread-coordination methods wait(), notify() and notifyAll().
Check exact spelling
Java distinguishes toString() from tostring(). A method toString(String prefix) is an overload, so printing does not call it. Both mistakes are caught by @Override. A text representation also does not define equality: two objects can print the same text while still comparing unequal.
Key Takeaways
Objectsupplies common methods to all Java objects, including arrays.- Printing and string concatenation use
toString()and render a null reference as "null". Object.equalscompares identity until a class overrides it.getClass()returns the actual class and is final.
2.3 Keep equality and hashing consistent
Two objects can represent the same value without being the same object. A book catalog can treat two book objects as equal when their catalog code and edition match. Define that rule by overriding equals(Object) and keep hashing consistent with it.
Compare the fields that define the value
import java.util.Objects;
final class Book {
private final String code;
private final int edition;
Book(String code, int edition) {
this.code = code;
this.edition = edition;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (!(other instanceof Book b)) return false;
return edition == b.edition && Objects.equals(code, b.code);
}
@Override
public int hashCode() {
return Objects.hash(code, edition);
}
}
The identity check accepts the same object quickly. The type check rejects an unrelated object or null, then introduces b as a Book. Primitive editions use ==; Objects.equals compares codes safely even when a code is null. Two books with code "B-1042" and edition 2 compare equal. Edition 3 makes a different value.
The parameter must be Object. Writing equals(Book other) adds an overload; it leaves the inherited equals(Object) in place. Library code calling that signature would still compare identity. @Override makes the wrong declaration fail at compile time.
Follow the equality promises
For non-null references, equality must be:
- Reflexive: an object equals itself.
- Symmetric: switching the two objects gives the same answer.
- Transitive: if one equals a second and the second equals a third, the first equals the third.
- Consistent: repeated comparisons agree while the compared data stays unchanged.
An object must also compare unequal to null. Equal objects must return equal hash codes. Unequal objects are allowed to share a hash code: a hash collision does not establish equality. Derive the hash from the same fields as equality, as Objects.hash(code, edition) does here.
A hash collection uses the hash to locate candidates before checking equality. Overriding only equals can make a set retain separate equal books or make a map fail to find an equal key. Changing a field used in hashing while its object is stored as a key can leave that object filed under its old hash.
Protect symmetry across subclasses
Suppose an extensible Book accepts any instanceof Book, while Ebook adds file size to equality and accepts only ebooks. A book may accept an ebook with the same code and edition, but the ebook rejects the book. The comparison has lost symmetry.
One approach requires matching runtime classes, checking null before getClass(). Another makes superclass equality final so subclasses cannot extend the equality rule. A final value class, as above, avoids subclass equality entirely. Records generate equality and hashing from their components; enum constants already have identity-based equality suited to their fixed values.
Key Takeaways
- Override
equals(Object), not an overload with the class as its parameter. - Equality must be reflexive, symmetric, transitive and consistent; comparison with null is false.
- Equal objects need equal hash codes; a shared hash does not establish equality.
- Keep equality fields stable while an object is a hash collection key or set element.
- A superclass and subclass must not use equality rules that disagree when the operands are reversed.
- Records and enums supply suitable built-in equality behavior.
3Define contracts with abstract classes and interfaces
Some related classes share fields and working methods; other classes only share a capability. Abstract classes and interfaces express those different relationships. A drawing program can keep common shape data in an abstract class while a charging program works with unrelated devices through an interface. Both approaches let the compiler check required behavior before the program runs, and Java provides precise rules when inherited implementations compete.
3.1 Share state and require subclass behavior
An abstract class is a base class that cannot be instantiated directly. It can hold common fields and working methods while leaving selected operations for subclasses to complete.
Leave a required operation abstract
abstract class Shape {
private final String name;
Shape(String name) { this.name = name; }
public abstract double area();
public String describe() {
return name + " with area " + area();
}
}
class Rectangle extends Shape {
private final double width;
private final double height;
Rectangle(double width, double height) {
super("rectangle");
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
}
area() has a signature and a semicolon, with no body. It states what every concrete shape must provide. Because Shape contains an abstract method, the class must be abstract too. Rectangle supplies the missing implementation and can be instantiated.
For new Rectangle(3.0, 4.0), the superclass constructor stores "rectangle". The subclass constructor stores the dimensions. Its area is 12.0 square meters when dimensions are in meters. Calling describe() returns "rectangle with area 12.0". The inherited method's call to area() runs the rectangle's implementation.
Use constructors and concrete methods normally
An abstract class can have fields, constructors, and methods with bodies, with the usual access levels. Its constructor runs as part of creating a subclass object. new Shape() is forbidden even if a matching constructor exists; new Rectangle(3.0, 4.0) is valid.
A second subclass can use the same inherited description while calculating area differently. A circle implementation returns Math.PI * radius * radius. For radius 1.5 m, the calculation is π times 2.25, about 7.07 square meters. The shared describe() method needs no circle-specific branch.
A variable can still have the abstract type: Shape s = new Rectangle(3.0, 4.0);. Java forbids constructing the abstract class, not referring to a concrete object through it.
Make missing work a compile error
Every concrete subclass must provide or inherit implementations of all abstract methods. A subclass that leaves some unfinished must also be declared abstract. Returning 0 from a placeholder area() would let an incomplete concrete subclass compile and silently give wrong results. Declaring area() abstract makes that missing work visible to the compiler.
Abstract methods cannot be private, static or final: each would prevent the subclass override that supplies the operation. An abstract class may contain no abstract methods; the modifier then only prevents direct construction. Use this kind of base class when its subclasses pass the is-a test and benefit from shared state or code.
Key Takeaways
- An abstract class cannot be constructed directly, but its constructor runs for subclass objects.
- An abstract method has no body and requires a concrete implementation.
- A concrete subclass must complete every inherited abstract method.
- An abstract type can be used for variables, parameters and lists.
- Abstract classes can combine common fields, working methods and required operations.
3.2 Give unrelated classes a shared contract
An interface names a capability that several classes can provide. It states the methods available through that capability without requiring the classes to share the same instance fields or superclass.
Declare and implement the methods
interface Chargeable {
int batteryPercent();
void charge(int minutes);
}
class Phone implements Chargeable {
private int battery;
Phone(int battery) { this.battery = battery; }
@Override
public int batteryPercent() { return battery; }
@Override
public void charge(int minutes) {
battery = Math.min(100, battery + 2 * minutes);
}
}
The interface's abstract methods are implicitly public. The implementing methods must explicitly be public; omitting that modifier would reduce access and fail compilation. A concrete class must supply or inherit suitable public implementations of every abstract method. An abstract implementing class may leave some for its subclasses.
For a phone at 35 percent, charge(20) adds 40 percentage points and leaves 75 percent. Another 20 minutes would reach 115 before the cap, so Math.min stores 100. An ElectricScooter can implement the same interface while adding only one point per minute: starting at 50 percent, it reaches 70 after 20 minutes. The interface fixes the available operation, while the class supplies its behavior.
Use the interface as a type
Chargeable device = new Phone(35);
device.charge(20);
System.out.println(device.batteryPercent());
This prints 75. A caller can use the interface's methods and the public methods available on every object. Phone-only operations are not available through device unless the declared interface exposes them.
Interfaces can also be parameter, return, and list element types. A method accepting Chargeable can work with a phone or a scooter without knowing their field layouts. Java's library uses the same approach: List has implementations such as ArrayList and LinkedList; Runnable declares run(); Comparable declares natural comparison.
Combine more than one capability
A class extends one class and may implement several interfaces. The order is class Phone extends Device implements Chargeable, Connectable. An interface can extend several interfaces: interface SmartDevice extends Chargeable, Connectable. Implementers then owe all the inherited abstract operations.
An interface has no constructors and no instance fields. new Chargeable() does not compile. A declared field such as int FULL = 100; is implicitly a public static final constant, read as Chargeable.FULL.
An abstract interface method contributes no working code. Its implementation may come from the class itself or an inherited public superclass method. Default methods are the interface methods that can supply inherited instance behavior.
Key Takeaways
- An interface is a type describing a shared capability.
- A concrete implementing class must provide or inherit suitable public implementations.
- A class can implement several interfaces while extending one class.
- Interfaces have no constructors or instance fields; their fields are constants.
- The interface type lets callers use its contract across unrelated classes.
3.3 Use shared interface behavior and resolve conflicts
Interfaces can supply working code as well as abstract method declarations. The method's modifier decides whether implementing objects inherit it, callers use the interface name, or only code inside the interface can use it.
Build default behavior from the contract
interface Chargeable {
int batteryPercent();
void charge(int minutes);
default boolean needsCharge() {
return batteryPercent() < 20;
}
static int clamp(int percent) {
return Math.max(0, Math.min(100, percent));
}
}
A default method is a public instance method with a body. A phone reporting 15 percent inherits needsCharge() and returns true. At 20 percent it returns false. The default calls the abstract batteryPercent() operation; it does not read a phone's private field. A class may override the method with its own public implementation.
A static interface method belongs to the interface: Chargeable.clamp(130) returns 100 and Chargeable.clamp(-5) returns 0. Implementing classes do not inherit it, so Phone.clamp(130) is not a valid call to this method. Default and static interface methods were introduced in Java 8.
Since Java 9, an interface may also declare private methods to share helper code among its own methods. A private instance helper supports default methods; a private static helper can also support static methods. Implementing classes cannot call these helpers directly.
Resolve competing defaults
Two unrelated interfaces can supply the same signature:
interface Lamp {
default String describe() { return "lamp"; }
}
interface Speaker {
default String describe() { return "speaker"; }
}
class SmartLamp implements Lamp, Speaker {
@Override
public String describe() {
return Lamp.super.describe() + " and "
+ Speaker.super.describe();
}
}
Without the override, SmartLamp has a conflict and does not compile. The qualified InterfaceName.super.method() calls select the individual defaults from its direct superinterfaces. This implementation returns "lamp and speaker".
| Competing implementations | Rule |
|---|---|
| Inherited public concrete superclass method and interface default | The class method wins |
| Default from an interface and its extending interface | The more specific interface wins |
| Defaults from unrelated interfaces | The class must resolve the conflict |
An abstract superclass declaration can also require a concrete subclass to implement the method; an interface default does not remove that obligation.
Consider existing implementers
Adding an abstract method requires each concrete implementer to provide or inherit a matching implementation. A default often avoids that change by supplying working behavior, but it can introduce a conflict with another inherited method. Defaults help an interface evolve; the inheritance rules still determine whether the resulting classes compile.
Key Takeaways
- A default method supplies overridable public instance behavior.
- Call a static interface method through the interface name.
- Private interface methods are helpers visible only inside the interface.
- A concrete class method takes priority over an interface default.
- Unrelated competing defaults require an override; the more specific interface default otherwise wins.
- Adding a default can introduce inherited-method conflicts.
3.4 Choose a shared base, a capability, or both
An abstract class and an interface can both define required methods. The choice depends on the relationship between objects and whether their implementation needs shared instance state.
Compare what each one supplies
| Question | Abstract class | Interface |
|---|---|---|
| Can it store per-object fields? | Yes | No |
| Can it have constructors? | Yes | No |
| Can it supply working instance methods? | Yes | Yes, with default methods |
| How many can a class directly use? | One superclass | Several implemented interfaces |
| Which access levels are available? | All normal levels | Public methods and private helpers |
Interface fields are public static final constants, not per-object data. Private interface helpers support its own working methods; they do not create private state for each implementing object.
Put closely related state in an abstract class
A drawing program has circles, rectangles and triangles. Each shape stores a name and a position, and each computes its area differently. An abstract Shape can keep the common fields and constructor, provide working movement or description methods, and declare area() abstract.
All concrete shapes are kinds of Shape, so other code can ask for a shape and receive any of them. Similarly, an abstract Enemy might own health points and a shared takeDamage(int points) method while leaving movement to particular enemy classes. The shared state gives these base classes useful work to do.
Put a cross-cutting capability in an interface
The same drawing program can export shapes, text labels and images. Exporting does not make a text label a shape, and these objects may already need different superclasses. An Exportable interface can declare String exportText(). Each class implements the operation using its own data.
A class may combine the two roles: class Circle extends Shape implements Exportable. The superclass provides shape state and shared code; the interface makes the circle usable by export code. A class implementing several interfaces promises several capabilities without inheriting instance fields from them.
Combine a contract with a partial implementation
An abstract class can implement an interface and complete only some methods. Concrete subclasses finish the remaining operations. Java's List and AbstractList use this arrangement; ArrayList extends AbstractList. Callers can depend on the interface while implementers reuse a base class where it fits.
For example, List<String> names = new ArrayList<>(); lets callers add, read and remove names through List. Replacing the initializer with new LinkedList<>() preserves those calls. A parameter declared as ArrayList<String> would reject a LinkedList<String> even when it only needs list operations.
An abstract class containing only abstract methods still occupies the subclass's single superclass slot. It is not interchangeable with an interface. Choose the interface when only the common contract is needed, and add a base class when related implementations benefit from shared state or code.
Key Takeaways
- Use an abstract class for related kinds of objects that share state or code.
- Use an interface for a capability that unrelated classes can provide.
- A class can extend an abstract base and implement interfaces together.
- A partial abstract implementation can support an interface without restricting callers to that base class.
4Write flexible programs with polymorphism
A variable's type and an object's class answer different questions. The variable's declared type controls which calls compile; the object's actual class controls which override runs. Keeping those decisions separate makes it possible to trace unfamiliar code, use casts safely, and write loops that work with new implementations. The same approach supports library interfaces such as Comparable, which gives objects a natural sorting order.
4.1 Separate the reference type from the object class
Polymorphism lets a general reference point to objects of different subtypes. The object keeps its full state and behavior; the reference gives callers a particular view of the available operations.
Name the two types
Person p = new Student("Ada Lovelace", "S-17");
The declared type, also called the static type, is Person: it appears in the variable declaration. The actual type, or runtime type, is Student: that is the class instantiated by new. The object's actual class does not change after construction.
Assigning a student reference to a person reference is an upcast. It is automatic and safe because every student is a person. The same student can also be referred to by an Object variable. An implementing object can be upcast to an interface it implements, as in Chargeable c = new Phone(35);.
No student data is deleted by the assignment. There is still one student object with its student ID, inherited person data and overridden methods. Giving it a more general reference changes what the compiler lets that reference access.
Check calls against the declared type
If Person declares getName(), then p.getName() compiles. If only Student declares getStudentId(), then p.getStudentId() does not compile. The compiler checks the Person API even when the initializer clearly creates a student.
The reverse assignment Student s = p; is not accepted without a cast. A person reference could point to a non-student person. That restriction protects code from treating an object as a subtype it does not have.
| Declared type | Calls available through the reference |
|---|---|
Object | Public operations provided by Object |
Person | Accessible Person operations, including inherited ones |
Student | Accessible student operations and inherited operations |
Let parameters and lists accept several subtypes
A method static void printCard(Person person) can receive a Person, Student or Teacher object, assuming those last two extend Person. Its code can rely on the person contract. A return type can be general too, for example a method that returns a Shape.
A list with a general element type can store different shape classes:
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1.5));
shapes.add(new Rectangle(3.0, 4.0));
for (Shape shape : shapes) {
System.out.println(shape.area());
}
Here List and ArrayList come from java.util, and each concrete shape supplies area(). The loop uses only that common operation.
A List<Circle> is not a List<Shape>. If it were, code receiving it as a shape list could add a rectangle into a list promised to contain only circles. When a method requires List<Shape>, create the list with that element type from the start.
Key Takeaways
- The declared type controls which operations a reference can access.
- The actual type is the class of the object and does not change after construction.
- Upcasting to a superclass or implemented interface is automatic and safe.
- A general parameter or list element type can support several concrete subtypes.
List<Circle>cannot be used asList<Shape>.
4.2 Trace overload selection and dynamic binding
A method call is resolved in two stages. At compile time, Java checks the declared types and chooses a method signature. At run time, dynamic binding chooses the overriding instance-method implementation for the object's actual class.
Follow a call inside an inherited method
class A {
String who() { return "A"; }
String hello() { return "Hello from " + who(); }
}
class B extends A {
@Override String who() { return "B"; }
}
class C extends B { }
For A x = new C();, x.hello() is allowed because A declares hello(). Class C has no override of hello(), nor does B, so Java runs the implementation in A.
Inside that method, who() means this.who(). The receiver is still the C object. Java searches from C, then finds the nearest who() implementation in B. The result is "Hello from B". Entering an inherited method does not turn the receiver into an object of the superclass.
Choose an overload before choosing an override
static String kind(Object value) { return "object"; }
static String kind(String value) { return "string"; }
Inside a class containing these methods, Object value = "hi"; kind(value) selects kind(Object) and returns "object". The variable's declared type is Object; Java does not reconsider overloads using the actual string at run time.
For an instance call, runtime dispatch can still choose an override of the already-selected signature. It cannot switch to a different overload that merely looks more specific for the actual argument. First identify the signature, then look for its override.
Recognize calls and members that do not dispatch this way
| Member or call | What selects it |
|---|---|
| Overridden instance method | Actual receiver class, after signature selection |
| Static method | Compile-time type used for the call |
| Hidden field | Declared type of the reference |
| Private method | Its declaring class's code |
super.method() | Superclass implementation |
If P declares String label = "P"; and Q extends P declares its own String label = "Q";, then P p = new Q(); p.label reads "P". Fields are hidden, not overridden. Both fields exist in the object. Avoid reusing inherited field names because the reference type can make identical-looking accesses read different state.
super.method() selects the superclass implementation, including one that superclass inherits. If the selected method itself makes an ordinary call on this, that inner call can still dispatch to a subclass override.
Key Takeaways
- Compile time fixes the callable signature, including overload choice.
- Dynamic binding selects the nearest override of that signature from the actual class upward.
- An ordinary call inside an inherited method still uses the actual receiver.
- Fields, static methods, private methods and explicit
supercalls do not use ordinary override dispatch.
4.3 Check a subtype before using its special methods
A general reference sometimes needs access to an operation that belongs only to one subtype. A downcast tells the compiler to treat the reference as that subtype. At run time, Java checks whether the object already has that type.
Understand what a cast changes
Suppose Person p refers to a student. Student s = (Student) p; allows s.getStudentId(). Both variables still refer to the same object. The cast does not copy it, change its actual class, or add student fields to a plain person.
If p actually refers to new Person("Grace Hopper"), that cast throws ClassCastException. A compiler accepts the possible person-to-student relationship, but the runtime object must still meet it. A cast between classes that cannot be related, such as a Person reference cast to the final class Integer, fails at compile time.
This differs from a primitive conversion: (int) 3.9 creates the integer value 3. A reference cast only checks and changes the type through which an existing reference is used.
Combine the test and the new variable
Since Java 16, pattern matching makes the safe case compact:
static void showStudentId(Person person) {
if (person instanceof Student student) {
System.out.println(student.getStudentId());
}
}
The test is true for a student or a subclass of student. The variable student is available inside the block, where the test is known to have succeeded. A plain person skips the block. A null reference also makes the test false, so the block never tries to call a method on null.
The older form tests person instanceof Student and then declares Student student = (Student) person; inside the successful branch. Both forms protect the cast. Testing an unrelated object elsewhere does not protect a cast of person; the test and cast must concern the same reference.
An upcast needs no explicit cast. Person person = student; already compiles and cannot fail from a type mismatch. A cast of null to a compatible reference type produces null; using that result for an instance-method call still fails with NullPointerException.
Decide whether a type test is needed
Type tests fit code that receives an Object, such as equals(Object other), or needs a subtype-only operation such as a student ID. They can also help when working with classes that cannot be changed.
A repeated type-test chain for a shared operation is a different problem. If code checks for each shape class to calculate its area, every added shape requires another branch. Declaring area() on Shape and letting subclasses implement it lets the caller use shape.area() instead.
A cast cannot make a missing common operation available across every subtype. Put shared behavior in the common contract, and reserve a subtype test for work that actually requires that particular subtype.
Key Takeaways
- A downcast checks an existing object; it does not transform the object.
- A possible cast can compile and still throw
ClassCastExceptionat run time. instanceofis false for null and true for instances of the type or its subtypes.- An
instanceofpattern introduces a safely typed variable where the test is known true. - Shared operations belong in the common type; subtype-only operations may need a type test.
4.4 Write general loops and define a natural order
A method stays useful across new classes when its parameter exposes exactly the operations the method needs. The caller supplies objects; dynamic binding supplies their individual behavior.
Add areas without class-specific branches
static double totalArea(List<Shape> shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.area();
}
return total;
}
With a circle of radius 1.5 m, a rectangle of dimensions 3.0 m and 4.0 m, and a triangle of base 4.0 m and height 2.5 m, the areas are about 7.06858, 12.0 and 5.0 square meters. Adding the unrounded circle area gives about 24.07 square meters. Each object supplies its own area().
Adding a Triangle class does not require editing the loop. The new class implements the existing shape operation. A negative result would violate the area contract even if the override compiled; general code relies on what the operation means as well as its signature.
The same approach works with an interface. A chargeAll(List<Chargeable> devices, int minutes) method loops over devices and calls device.charge(minutes). For 20 minutes, the phone starting at 35 percent reaches 75 percent and the scooter starting at 50 percent reaches 70 percent. The loop never selects a charging rule itself.
Let callers choose a list implementation
A parameter List<Shape> accepts both an ArrayList<Shape> and a LinkedList<Shape>. A parameter ArrayList<Shape> rejects the linked list despite needing only list operations. Use the most general type that supplies the required methods; declaring everything as Object would be too general because it lacks area() and list operations.
Make objects sortable with Comparable
Comparable<T> defines an object's natural order. Its compareTo result is negative when this object comes before the other, zero for an ordering tie, and positive when it comes after. The result's sign matters; it need not be exactly −1 or 1.
record Runner(String name, int seconds)
implements Comparable<Runner> {
@Override
public int compareTo(Runner other) {
return Integer.compare(seconds, other.seconds);
}
}
A modifiable list with Mia at 3605 s, Ken at 3490 s and Ana at 3720 s becomes Ken, Mia, Ana after Collections.sort(runners). list.sort(null) also uses natural ordering. Build a modifiable list with new ArrayList<>(List.of(...)); attempting to sort the unmodifiable result of List.of(...) throws UnsupportedOperationException.
Use Integer.compare instead of subtracting the values. Subtraction can overflow for large values of opposite signs and return the wrong sign. The same idea applies to Double.compare for decimal fields. In this runner example, equal times tie in ordering even if the names differ, so a comparison tie and object equality need not mean the same thing. Prefer a natural order whose zero result agrees with equality; document an intentional difference.
Key Takeaways
- General code calls a shared operation and lets each object supply its implementation.
- An interface parameter can accept different implementations of the same contract.
compareTouses a negative, zero or positive result to express ordering.Integer.compareavoids the overflow risk of subtracting integer fields.- Sorting requires a modifiable list;
List.of(...)alone is unmodifiable. - An ordering tie can differ from value equality when comparison uses fewer fields.
5Model fixed values and data with enums and records
Some programs need a fixed set of choices, while others need a small group of values that belongs together. Enums give choices a type of their own; records supply much of the routine code for data objects. Both fit Java's type system and can implement interfaces. Their built-in behavior saves work, but choosing between them and an ordinary class still depends on what can change and what makes two values equal.
5.1 Represent fixed choices with an enum
An enum gives a fixed set of named choices its own type. enum Size { SMALL, MEDIUM, LARGE } allows Size size = Size.MEDIUM;. It prevents a caller from passing the misspelled string "MEDUIM" or the integer 7 to a method that requires Size.
Use names, constants and switch
Each enum constant is a single object, so size == Size.LARGE is a suitable comparison. An enum variable can also be null. Size.values() returns all constants in declaration order; Size.valueOf("LARGE") finds the exact name. The lookup is case-sensitive: "large" causes IllegalArgumentException.
name() returns the declared name. ordinal() returns the zero-based position, so Size.MEDIUM.ordinal() is 1. Inserting a constant changes later positions, so an ordinal is a poor stored code or business value. By default, toString() also returns the name.
A switch expression, standard since Java 14, can return a value:
String label = switch (size) {
case SMALL -> "S";
case MEDIUM -> "M";
case LARGE -> "L";
};
Naming every constant makes the expression exhaustive without a default branch. These cases assume a non-null size; listing the constants does not handle null.
Give each constant meaningful data
enum CupSize {
SMALL(250), MEDIUM(350), LARGE(500);
private final int milliliters;
CupSize(int milliliters) { this.milliliters = milliliters; }
public int milliliters() { return milliliters; }
}
The semicolon separates the constant list from the members. The constructor runs once for each constant when the enum initializes. It is implicitly private, and callers cannot create new constants with new CupSize(400).
Two large cups and one small cup hold 1250 mL, or 1.25 L. That calculation uses the milliliters field, not the constants' positions. Looping over CupSize.values() and printing each constant and capacity gives "SMALL: 250 mL", "MEDIUM: 350 mL" and "LARGE: 500 mL".
Give constants their own behavior
An enum can implement an interface, and it may give individual constants different method bodies:
enum Operation {
ADD { int apply(int a, int b) { return a + b; } },
MULTIPLY { int apply(int a, int b) { return a * b; } };
abstract int apply(int a, int b);
}
Operation.MULTIPLY.apply(6, 7) returns 42. Each constant completes the abstract operation. Enums implicitly extend java.lang.Enum; they cannot extend another class, and ordinary classes cannot extend them.
Use an enum for options fixed when the program is written, such as directions or order states. A shop's changing list of products belongs in data: adding products should not require rewriting and recompiling an enum declaration.
Key Takeaways
- An enum gives a fixed set of constants a distinct type.
values()preserves declaration order andvalueOfmatches the exact name.- Store meaningful numbers in fields, because ordinal positions can change.
- Enum constructors initialize constants; callers cannot use
newto add constants. - Enums can implement interfaces and give constants different behavior.
5.2 Carry values with a record and validate them
A record is a concise data class, standard since Java 16. Its header lists the values, called components, that define each instance: record Point(int x, int y) { }.
Use the generated members
The compiler supplies a private final field for each component, a canonical constructor with those components in order, and public accessors named after them. It also supplies equals, hashCode and toString based on the components.
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.x());
System.out.println(a.equals(b));
System.out.println(a == b);
This prints 3, true and false on separate lines. Matching component values make the records equal, but the two new expressions create separate objects. Equal records have equal hash codes. Their useful generated text includes the record and component names; current JDKs show "Point[x=3, y=4]". That exact text format is not guaranteed, so do not parse it as a data format.
Accessors are x() and y(), not getX() and getY(). A setter that assigns this.x = x does not compile because the component field is final. A method can return a new value instead: Point moveRight(int dx) { return new Point(x + dx, y); }.
Validate in a compact constructor
record Temperature(double celsius) {
Temperature {
if (celsius < -273.15) {
throw new IllegalArgumentException("below absolute zero");
}
}
}
A compact constructor repeats the record's name without a parameter list. It checks or normalizes the incoming parameters; Java assigns the component fields after its body. This check rejects −300 °C and accepts 21.5 °C. A text component could be normalized with name = name.trim(); before automatic assignment.
A public record needs a public compact constructor. Records may also declare static fields, static methods and instance methods. An additional constructor delegates to the canonical constructor with this(...). Extra instance fields beyond the components are forbidden.
Protect mutable component objects
Final component fields make immutability shallow. A record holding a mutable list still refers to that list:
record Team(String name, List<String> members) {
Team {
members = List.copyOf(members);
}
}
With java.util.List imported, this stores an unmodifiable copy. Later additions to the original list do not affect the team's membership, and team.members().add("Lee") throws UnsupportedOperationException. The copy protects the list structure; it does not make mutable element objects deeply immutable.
Records implicitly extend java.lang.Record and are final. They cannot extend another class or be extended, but can implement interfaces such as Comparable. Use them for data defined by its values; use an ordinary class for changing state, extra hidden instance fields or an extensible class hierarchy.
Key Takeaways
- A record header defines components and the generated constructor, accessors and value methods.
- Record accessors use component names such as
x(). - A compact constructor checks or normalizes parameters before automatic field assignment.
- Record component references are final, but referenced objects may remain mutable.
- An unmodifiable list copy protects a record's list structure.
- A record is final, cannot extend a class explicitly, and can implement interfaces.
Java Inheritance and Polymorphism examples
1Build an inherited device step by step
12 steps
- 1IntroductionOne object with a general and a specific role
A sensor is a kind of device. Every device needs a label, while a sensor also needs a measurement unit. Inheritance lets the device own its label and the sensor add its unit.
We will build that relationship, use an inherited method, and trace how constructor calls initialize each part. The key question is which class owns each field and therefore which constructor should assign it.
- 2ExampleStart with the state every device needs
class Device { private final String label; Device(String label) { this.label = label; } public String label() { return label; } }Devicecontrols its private label. Other code reads it throughlabel(). A subclass will inherit that public method, but it cannot assign the private field directly. That keeps label initialization in one place. - 3ExampleAdd only the sensor-specific state
class Sensor extends Device { private final String unit; Sensor(String label, String unit) { super(label); this.unit = unit; } public String unit() { return unit; } }Sensorextends one class and adds one field. Itssuper(label)invocation passes responsibility for the label toDevice; the sensor constructor stores the unit itself. - 4Key InsightRead both parts through the sensor
After
Sensor s = new Sensor("Room", "C");,s.label()returns "Room" ands.unit()returns "C". The first method is inherited; the second belongs toSensor.Both calls concern the same object. Extending
Devicedid not create a second device object beside the sensor. The private label is present, even though sensor code accesses it through a method. - 5ChallengeChoose the relationship for a lab
A
Labcontains several sensors. ShouldLabextendSensor, or keep references to sensors? Explain the relationship.---reveal---
Keep sensor references in fields or a collection. A lab has sensors but is not itself a sensor, so composition fits. Extending
Sensorwould promise that a lab can be used wherever a sensor is required, including operations that describe one sensor's unit. - 6ExplanationEnter the superclass constructor
Calling
new Sensor("Room", "C")selects the two-argument sensor constructor. Itssuper(label)invokesDevice(String)for that same object.Devicefirst reaches its own superclass,Object, then assigns the label. WhenDevicereturns, sensor field initialization and the remaining sensor constructor body run. Finally, the new reference becomes available to the caller. - 7TipFind the missing no-argument constructor
Remove
super(label)from the sensor constructor and Java insertssuper(). That call has no match:Devicedeclares onlyDevice(String).The constructor error is about arguments, not about the private field. Replacing the invocation with
new Device(label)creates an unrelated object and still leaves the implicitsuper()problem. Pass the label to the superclass constructor instead. - 8ExampleDelegate an optional default unit
Add this constructor inside
Sensor:Sensor(String label) { this(label, "unknown"); }new Sensor("Hall")delegates to the existing two-argument constructor, which invokessuper(label). The resulting object has label "Hall" and unit "unknown". Do not also writesuper(...)in the delegating constructor: one constructor has at most one explicit constructor invocation. - 9ConnectionValidate a parameter before construction
Standard Java 25 permits restricted work before an explicit constructor invocation. For example, a sensor constructor can reject an empty incoming label before calling
super(label).That early code may use parameters, but cannot read the object's fields or call its instance methods. Restricted simple assignments to current-class fields without declaration initializers are also allowed. For standard older language levels, keep the invocation first.
- 10ChallengePredict a constructor message
In a separate version,
Deviceprints "device" after its own superclass call.Sensorprints "sensor" aftersuper(label). Which message appears first when creating a sensor, and why?---reveal---
"device" appears first. The sensor constructor invokes the device constructor before continuing to its own print statement. The object is a sensor throughout construction; these messages show the order of constructor work, not changes to its class.
- 11ChallengeRepair an inherited-field assignment
A new subclass tries to initialize the private device label with
this.label = label;and omitssuper(label). What two related problems does this create?---reveal---
The private field cannot be assigned directly by the subclass. The implicit
super()also has no matching device constructor. Usesuper(label)soDeviceassigns its own private state, then assign only fields declared in the subclass. - 12RecapLocate a construction failure
When a subtype cannot be built, locate the first constructor call without a matching accessible target. Then check that each class initializes the fields it owns.
Keep the relationship test alongside that trace: a subtype is usable as its base type; a contained object belongs in a field. These checks address different kinds of design and compiler errors.
2Print and compare catalog entries correctly
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionChoose what a catalog entry should mean
- 2ExampleExtend a basic description
- 3TipAvoid calling the override from itself
- 4ChallengeCheck an apparent replacement
- 5ExampleGive printing an exact method to call
- 6Key InsightRead the actual class without changing it
- 7ChallengeDiagnose default-looking printed text
- 8ExampleDeclare the equality data
- 9ExampleReject the wrong object before comparing fields
- 10ExampleHash the same equality fields
- 11ChallengeSpot the overload that a collection misses
- 12Key InsightProtect comparisons in both directions
- 13ChallengeSeparate equal text from equal values
- 14RecapCheck a value class at its public boundary
3Build a contract and share its implementation
18 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionSeparate the common data from the common capability
- 2ExampleStart an abstract message source
- 3ExampleComplete the missing operation
- 4ChallengeDecide whether a subclass can be concrete
- 5ExampleName reading as a capability
- 6ConnectionMake the existing base fulfill the interface
- 7ExampleWrite a caller against the contract
- 8ChallengeRepair an implementation with weaker access
- 9ExampleAdd a useful default operation
- 10ExplanationKeep static and private helpers in their roles
- 11ExampleCreate a genuine default conflict
- 12ExampleChoose a default explicitly
- 13ChallengeGive the class implementation its priority
- 14Key InsightOffer a base without requiring every implementation to use it
- 15ChallengePlace a label and a constructor in the right type
- 16TipCheck new defaults against inherited methods
- 17ChallengeAdd a new reader without changing its existing superclass
- 18RecapEvaluate a proposed common type
4Trace a report call through general types
17 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow what the variable promises and what the object does
- 2ExampleGive a specialized report a general reference
- 3ExplanationCheck what the reference exposes
- 4ChallengeChoose types for a mixed report list
- 5ExampleResolve the outer call first
- 6ExampleResolve the inner call on the same receiver
- 7ExampleChoose an overload from the declared argument
- 8ChallengeSeparate two decisions in one call
- 9TipMark fields and static calls separately in a trace
- 10ExampleAsk safely for the sales-only operation
- 11ChallengePredict a cast when the object changes
- 12ExampleGather headings through the common operation
- 13ConnectionUse comparison as another shared operation
- 14ChallengeSort values through an implementation choice
- 15Key InsightKeep subtype checks for subtype work
- 16ChallengeSupport an audit heading without a new branch
- 17RecapTrace an unfamiliar call in order
5Build a fixed choice and a protected data value
15 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionChoose between named options and grouped data
- 2ExampleGive service choices their own type
- 3ExampleAttach useful numbers to the choices
- 4TipKeep the service data independent of declaration order
- 5ChallengePredict lookup and a useful field
- 6ConnectionKeep behavior with the fixed choice
- 7ExampleDeclare the components of a delivery request
- 8ExampleNormalize incoming data before assignment
- 9ChallengeCompare the normalized requests
- 10ExampleFind the mutable object inside a final component
- 11ExampleStore an unmodifiable copy
- 12TipReturn a new record when values need to change
- 13ChallengeChoose the right repair for a changing route
- 14ChallengeModel new service options and customer requests
- 15RecapInspect generated behavior before adding code
Java Inheritance and Polymorphism practice questions
Set 1: Build classes and trace inherited behavior10 Practice Questions
Question 1 of 10
Library stores several Shelf objects, and each shelf stores books. How should Library represent its relationship with Shelf?Show answerHide answer
Shelf would also claim that every library is a shelf and could be used wherever a shelf is expected. Reusing shelf methods does not make that is-a relationship true.Question 2 of 10
Account declares a private field balance and a public method getBalance(). SavingsAccount extends Account. How can a method in SavingsAccount read the inherited account state?Show answerHide answer
Account. Private state still belongs to the savings object, but subclass code cannot access that field directly. Adding super. does not bypass the private access rule.Question 3 of 10
What happens when this code is compiled?
class Machine {
Machine(int serial) { }
}
class Printer extends Machine {
Printer() { }
}
Show answerHide answer
super(). Machine declares only a constructor taking an integer, so no no-argument constructor exists. Java does not invent an argument value; Printer() can remain no-argument if its body explicitly supplies a serial to super.Question 4 of 10
What is printed by new Leaf();?
class Root {
Root() { System.out.print("R"); }
}
class Branch extends Root {
Branch() { System.out.print("B"); }
}
class Leaf extends Branch {
Leaf() { this(7); System.out.print("L"); }
Leaf(int n) { System.out.print(n); }
}
Show answerHide answer
Leaf() delegates to Leaf(int), whose implicit superclass call enters Branch and then Root. Their bodies print "R" and "B", then Leaf(int) prints 7 and the delegating constructor prints "L". Delegation does not construct the root part a second time.Question 5 of 10
Meter declares public int read(). A subclass writes @Override public int read(int offset). Why does compilation fail?Show answerHide answer
read instead of overriding it. @Override catches the mismatch; returning int is compatible with the original method.Question 6 of 10
What does the final statement print?
class Parcel {
public String toString() { return "parcel"; }
}
class ChilledParcel extends Parcel {
@Override public String toString() {
return super.toString() + " cold";
}
}
Parcel item = new ChilledParcel();
System.out.println("Send " + item);
Show answerHide answer
toString(), and the actual chilled-parcel object runs its override. That method obtains "parcel" from super.toString() and appends " cold". Declaring the reference as Parcel does not suppress the override.Question 7 of 10
Label class has only public boolean equals(Label other), which compares its text, and inherits equals(Object). Two distinct labels have the same text. Both references are stored in variables declared as Object. What does a.equals(b) return?Show answerHide answer
Object references, the call selects equals(Object), which Label has not overridden. The inherited implementation compares the two distinct objects by identity and returns false. The same-named method taking Label is an overload and is not selected from the runtime argument class.Question 8 of 10
abstract class Sensor declares public abstract double reading();. What must a concrete LightSensor extends Sensor provide or inherit?Show answerHide answer
Sensor. A static method is not an instance override, and a private method would reduce the required access.Question 9 of 10
Why does this class fail to compile?
interface Resettable { void reset(); }
class Counter implements Resettable {
void reset() { }
}
Show answerHide answer
reset() method is implicitly public. The implementation has package-private access, so it weakens the promised access and fails compilation. A class implements an interface; changing the relationship to extends would not fix the method declaration.Question 10 of 10
What does new TimedJob().cost() return?
class Job {
public int rate() { return 3; }
public int cost() { return 4 * rate(); }
}
class TimedJob extends Job {
@Override public int rate() { return 5; }
@Override public int cost() { return super.cost() + 2; }
}
Show answerHide answer
Job.cost() through super.cost(). Its ordinary rate() call still runs on the timed-job object, so it returns 5; the inherited calculation gives 20 and the outer override adds 2. Using 3 for the inner call would wrongly disable dynamic binding throughout the superclass method.Set 2: Use contracts and general reference types10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Robot implements Movable. In Movable unit = new Robot();, what is the declared type of unit?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
TextDocument and an unrelated Image class must both support exportText(). Each already extends a different class. What common type best expresses this capability?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
Does this code compile, and why?
interface Named { String name(); }
class Entity {
public String name() { return "entity"; }
}
class Asset extends Entity implements Named { }
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does new Screen().label() return?
interface Tagged {
default String label() { return "tagged"; }
}
class Panel {
public String label() { return "panel"; }
}
class Screen extends Panel implements Tagged { }
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Fast and Safe are unrelated interfaces, each with default String mode(). A concrete Service implements both and inherits no class implementation of mode(). What must Service do?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
Vehicle has move(), and only Bicycle has ringBell(). Bicycle extends Vehicle. What happens to v.ringBell() after Vehicle v = new Bicycle();?Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
interface Percent declares static int cap(int n), and Gauge implements Percent declares no cap method. How should code call the interface helper with 120?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
area(). It must accept either ArrayList<Shape> or LinkedList<Shape>. What parameter type exposes the needed list operations?Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What happens when compiling and running new Row().render()?
interface Renderable { String render(); }
abstract class Base implements Renderable {
public String render() { return "[" + body() + "]"; }
protected abstract String body();
}
class Row extends Base {
@Override protected String body() { return "row"; }
}
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Report with private title state, a constructor setting that state, and abstract body(). Reports and unrelated chart objects must also be printable. What structure preserves both requirements?Show answerHide answer
Save this lesson to see this answer.
Set 3: Follow calls, hidden members and checked casts10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Shape s = null; is followed by s instanceof Circle. Circle extends Shape. What is the result of the test?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What happens to the final statement?
class Animal { }
class Fox extends Animal { }
Animal a = new Animal();
Fox f = (Fox) a;
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What does pick(value) return?
static String pick(Object x) { return "object"; }
static String pick(CharSequence x) { return "sequence"; }
// Inside a method of the class containing these overloads:
Object value = "notes";
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What text is produced by p.label + ":" + p.show()?
class Parent {
String label = "P";
String show() { return label; }
}
class Child extends Parent {
String label = "C";
@Override String show() { return label; }
}
Parent p = new Child();
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
List<Vehicle> and may add a bus. Bicycle extends Vehicle and Bus extends Vehicle. Why can a List<Bicycle> not be passed to it?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What does b.test() return?
class Base {
String use(Object x) { return "BO"; }
String use(String x) { return "BS"; }
}
class Sub extends Base {
@Override String use(Object x) { return "SO"; }
@Override String use(String x) { return "SS"; }
String test() {
Object text = "x";
return super.use(text) + ":" + use(text);
}
}
Sub b = new Sub();
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What does new Sub(); print with standard Java 25?
class Base {
Base() { System.out.print(value()); }
int value() { return -1; }
}
class Sub extends Base {
int n = 8;
Sub() { super(); n = 11; }
@Override int value() { return n; }
}
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What is printed by this code? Tool declares no methods.
class Tool { }
class Drill extends Tool {
int speed() { return 6; }
}
Tool tool = new Drill();
Object copy = tool;
if (copy instanceof Drill d) {
System.out.print(d.speed());
}
System.out.print(":" + (copy == tool));
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
Creator has public Object create(). TextCreator extends Creator overrides it. What return-type and access combination is valid?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
Document subclasses. Its preview method uses instanceof branches to compute each document's word count. New document classes are often added. What change lets new classes support preview without editing that type-test chain?Show answerHide answer
Save this lesson to see this answer.
Set 4: Model values, compare them and sort them10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
enum Status { NEW, SENT, DONE }. What does Status.valueOf("sent") do?Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
record Reading(int count, String unit) { }, what generated call reads the count from a variable r?Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
enum Band { LOW(2), HIGH(9); ... } stores the constructor argument in an immutable code field. A new constant MID(5) is inserted between LOW and HIGH. How do HIGH's ordinal() and stored code change?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
Token objects return the same hash code. Their equals(Object) implementations correctly compare the fields defining each token. Does the shared hash code violate the equality and hashing contract?Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
record Result(int seconds) implements Comparable<Result> returns Integer.compare(seconds, other.seconds). What sign does new Result(418).compareTo(new Result(405)) have?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What does the final statement print?
record Tag(String text) {
Tag { text = text.trim(); }
}
Tag a = new Tag(" north ");
Tag b = new Tag("north");
System.out.println(a.equals(b));
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Ticket.equals accepts any Ticket with the same code. PriorityTicket extends Ticket accepts only PriorityTicket objects and also compares priority. A plain ticket t and a priority ticket p have the same code. What do t.equals(p) and p.equals(t) return, and does that respect symmetry?Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What is printed? List and ArrayList are imported.
record Batch(List<String> names) { }
var original = new ArrayList<String>(List.of("Rin"));
Batch first = new Batch(original);
Batch second = new Batch(new ArrayList<>(original));
original.add("Sol");
System.out.println(first.equals(second) + ":"
+ first.names().size() + ":" + second.names().size());
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What happens when Collections.sort(list) runs? The needed java.util types are imported.
record Rank(int score) implements Comparable<Rank> {
public int compareTo(Rank other) {
return Integer.compare(score, other.score);
}
}
List<Rank> list = List.of(new Rank(8), new Rank(3));
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What does Rule.DOUBLE.apply(Rule.NEXT.apply(4)) return?
enum Rule {
NEXT {
int apply(int n) {
return switch (n) { case 4 -> 6; default -> n + 1; };
}
},
DOUBLE { int apply(int n) { return 2 * n; } };
abstract int apply(int n);
}
Show answerHide answer
Save this lesson to see this answer.
Set 5: Combine Java rules and diagnose failures10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does new Terminal().name() return?
interface Basic {
default String name() { return "basic"; }
}
interface Detailed extends Basic {
default String name() { return "detail"; }
}
class Terminal implements Basic, Detailed { }
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
List<Notifier> contains objects of several unrelated classes implementing Notifier.send(). A loop calls n.send() on every element. A new PushNotifier implements the same contract correctly. What change is needed in the loop to support its instances?Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What is printed with standard Java 25?
class Base {
Base(int n) { System.out.print("B" + n); }
}
class Sub extends Base {
Sub(int n) {
if (n < 0) throw new IllegalArgumentException();
super(n + 1);
System.out.print("S" + n);
}
}
new Sub(2);
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
Code class defines equality and hashing using its text field. A HashSet<Code> contains c with text "red". The program must change its text to "blue", keep the same object, and preserve reliable set lookup. In what order should it update the object and set?Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What does x.call(arg) + ":" + x.call("x") produce?
class Alpha {
String call(Object x) { return "AO"; }
String call(String x) { return "AS"; }
}
class Beta extends Alpha {
@Override String call(Object x) { return "BO"; }
@Override String call(String x) { return "BS"; }
}
Alpha x = new Beta();
Object arg = "data";
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What does the final statement print? List and ArrayList are imported.
record Group(List<String> names) {
Group { names = List.copyOf(names); }
}
var original = new ArrayList<String>();
original.add("Uma");
Group group = new Group(original);
original.add("Zed");
System.out.println(group.names().size() + ":" + original.size());
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What happens when this code is compiled?
interface Ready {
default String state() { return "ready"; }
}
abstract class Base {
public abstract String state();
}
class Console extends Base implements Ready { }
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
record Player(String name, int points) objects have different names but the same points. Their custom compareTo compares only points using Integer.compare. What do equals and compareTo report for these two objects?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
interface Storable { String saveText(); }. Many new implementations share a private path field, constructor validation, and helper code, while some existing implementations extend other classes. How can the library offer reuse while preserving the common API?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
Abstract class
Shape can store a name while requiring concrete circles and rectangles to supply an area.Abstract method
area() prevents an unfinished concrete shape from silently using a zero-area placeholder.Accessor
count record component is count(), so getCount() is unavailable unless explicitly declared.Actual type
Person reference can point to a student whose actual type remains Student after the upcast.C
Canonical constructor
Point(int x, int y) accepts the x coordinate followed by the y coordinate.Capability
ClassCastException
Animal object is cast to its more specific Fox subclass.Compact constructor
Comparable
compareTo.Comparable<Runner> so a sorting method can arrange runners by finishing time.Composition
Customer hold an Address without making every customer usable as an address.Concrete class
Rectangle is a concrete class once it completes the area operation required by its abstract shape base.Concrete method
Shape.describe() can reuse the name and call each subclass's implementation of area().Constant-specific body
MULTIPLY implement an operation differently from the enum's ADD constant.Constructor chain
Covariant return type
String when the superclass method returns Object.D
Declared type
Person p = new Student(...), the declared type is Person, so student-only methods are not directly available.Default constructor
super() in its body.Person(String name) removes the default constructor that a subclass may have relied on.Default method
needsCharge() can use batteryPercent() without knowing any device's private field layout.Default-method conflict
SmartLamp to override the matching descriptions from Lamp and Speaker.Downcasting
Dynamic binding
hello() that calls who() reach B.who() when the receiver belongs to class B.E
Enum
NEW, SENT and DONE.Enum constant
CupSize.LARGE can store a capacity of 500 mL and be compared with identity equality.Equality contract
F
Field hiding
p.label read the parent field while p.show() runs a child override using a different label.Field initializer
Final method
Flexible constructor body
H
Hash code
Hash collision
I
Identity
a == b false.Implementation
Entity can fulfill Named.name() for an Asset subclass.Inheritance
Student reuse Person.getName() while adding its own credits and study behavior.instanceof
instanceof test lets code check whether a person has the student type before using a student-only method.Integer overflow
Integer.compare avoids that comparison error.Interface
Chargeable interface lets a phone and a scooter support the same calls while charging at different rates.Interface constant
Chargeable.FULL can hold 100 while each device stores its own changing battery field.Interface type
List as an interface type lets one method accept both array-backed lists and linked lists.M
Method hiding
Method signature
Object to String changes the method signature and creates an overload.More specific interface
Detailed supplies the chosen name() when it extends Basic and replaces its default.N
Natural order
O
Object
Object variable can hold a student or an array, and both support the methods every object has.Ordering tie
compareTo returns zero, meaning two objects occupy the same comparison rank under the chosen ordering rule.Ordinal
LOW, HIGH, the ordinal of HIGH changes from 1 to 2 when MID is inserted before it.Overloading
print(String text) beside print(int number) without replacing either method.Override annotation
@Override, asks the compiler to verify that a method overrides or implements an inherited method.tostring() when the programmer intended to replace toString().Overriding
yearlyPay() lets a manager add a bonus while callers keep using the employee operation.P
Partial implementation
Storable can share path validation while concrete classes supply saved text.Pattern variable
student in person instanceof Student student safely exposes the matched object's ID method.Polymorphism
area() on circles, rectangles and triangles without separate type branches.Private interface method
Protected access
R
Receiver
shape.area(), the receiver is the shape object, even when the variable has an abstract declared type.Record
Point(int x, int y) groups two coordinates and supplies component-based equality without hand-written methods.Record component
seconds can be read from a runner using the generated method runner.seconds().Runtime class
getClass() independently of the declared type of its reference.Student even when a method receives it through a Person parameter.S
Shallow immutability
Shared state
Single inheritance
Student and Teacher as direct superclasses.Static interface method
Chargeable.clamp(130) can return 100 without creating a device object.Subclass
GraduateStudent subclass can build on Student and still be used wherever a Person is expected.Superclass
Person is the superclass of Student, so a method accepting a person can also receive a student.Superclass constructor invocation
super(...) to pass arguments to a direct superclass constructor for the current object.Person, which owns the private name field.Superinterface
Chargeable is a superinterface of SmartDevice when the latter extends it along with Connectable.Switch expression
Symmetry
book.equals(ebook) is true but ebook.equals(book) is false for those same objects.T
Text representation
toString(), used when the object is printed or joined to other text.Transitivity
U
Unmodifiable copy
List.copyOf protects a team's membership list from additions to the caller's mutable list.Upcasting
Chargeable keeps its battery data but exposes only the operations available through that type.V
Value equality
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.