Objects, References and Equality in Java

Learn what a Java variable really holds: a primitive value or a reference to an object. Trace aliases and null, pass objects to methods and return them, and build classes from other objects and from lists of objects. Then write equals and hashCode so that lists, sets and maps compare your objects by content, and keep a program's text interface separate from its logic.

BeginnerLesson 6 of 11 in Object-Oriented Programming with Java Course

What you will learn

  1. Explain what a Java variable holds for primitive and reference types, predict what assignment does to each, and handle variables that refer to no object.
    • Compare primitive and reference variables
      Name Java's primitive types, explain that a reference variable holds a reference to an object, and predict the effect of copying each kind of variable.
    • Trace aliases, reassignment and unreachable objects
      Follow several variables that refer to one object, tell changing an object apart from pointing a variable at another object, and explain what happens to objects that nothing refers to.
    • Work safely with the null reference
      Explain what null means, recognize where it appears, prevent NullPointerException and decide when null is a reasonable value.
  2. Explain how Java passes arguments, write methods that receive and change objects or compare them with another object of the same class, and return new or existing objects safely.
    • Use objects as method parameters
      Predict what a method can and cannot change when it receives a primitive or a reference, and write methods that take objects of another class or of their own class.
    • Return objects from methods
      Write methods that create and return new objects, return references to objects that already exist, and avoid exposing an object's internal state by accident.
  3. Design classes whose fields are other objects or lists of objects, work with lists that hold objects, and decide when contained objects should be shared or copied.
    • Compose a class from other objects
      Model has-a relationships with object fields, delegate work to the contained objects, and decide whether a contained object should be shared or copied.
    • Store and process objects in lists
      Add, find, change and remove objects in an ArrayList, and explain why the list holds references rather than copies.
    • Build a class around a list of objects
      Write a class that keeps a list of objects as a field, initializes it, offers methods for working with its contents and keeps the list itself hidden.
  4. Tell identity apart from equal content, write correct equals and hashCode methods, and predict how lists, sets and maps use them.
    • Distinguish identity from equal content
      Explain what == and equals compare for primitives, strings, wrappers and your own classes, and predict the result of each comparison.
    • Write an equals method
      Override equals with the correct signature and steps, compare each kind of field correctly, and keep the method consistent with its general contract.
    • Pair hashCode with equals
      Explain the hashCode contract, write hashCode for a class that overrides equals, and predict how HashSet and HashMap behave with and without it.
  5. Structure a text-based program so that one class talks to the user and other classes hold the data and rules, then extend and check it without mixing the two.
    • Split a program into a user interface and logic classes
      Decide which code belongs in the user interface and which in the logic, and connect them through constructor parameters and a start method.
    • Extend and check a separated program
      Add features by extending the logic and calling it from the interface, check the logic directly with known values, and keep each class focused on one job.

Java Objects and References study guide

5 chapters

1

Follow values and references

A variable and an object are different parts of a Java program. Understanding what each variable holds lets you explain why one assignment creates independent numbers while another gives two names access to the same object. Start with the values being copied, then trace changes one statement at a time. The same picture will help you find missing objects, read null errors and decide whether a change should be shared.

1.1 What a variable holds

A Java variable holds a value. The important question is whether that value is ordinary data or a reference that leads to an object. Assignment copies that value in both cases.

Primitive data and object references

Java has eight primitive types: byte, short, int, long, float, double, char and boolean. A primitive variable holds its data directly. A reference variable holds a reference to an object or the special value null. Classes such as String, Scanner and ArrayList, your own classes, and arrays are reference types.

DeclarationWhat the variable holds
int count = 5;The integer value 5
Counter c = new Counter();A reference to a new counter
Counter c = null;No object reference

In new Counter(), Java creates an object, runs its constructor and produces a reference. The assignment stores that reference in c. Draw the variable as a box with an arrow leading to a separate object. Each evaluation of new creates a separate object, even when the arguments match.

Copy the value, then follow the result

With primitives, changing the copied variable leaves the first variable alone:

int a = 5;
int b = a;
b = b + 1;

The last line reads 5 from b, adds 1 and stores 6 in b. The value in a remains 5.

For a reference example, use this small class:

class Counter {
    private int value;

    Counter() { this.value = 0; }
    Counter(int value) { this.value = value; }
    void increase() { value++; }
    int value() { return value; }
}

Now trace these statements:

Counter first = new Counter();
Counter second = first;
second.increase();
System.out.println(first.value()); // 1

Only the first line creates a counter. The second line copies the reference, so the method call changes the one counter that both variables reach. To make an independent counter with matching data, write new Counter(first.value()). Copying a variable does not call a constructor.

Wrappers and immutable strings

Every primitive type has a wrapper class, such as Integer, Double, Boolean or Character. Wrapper variables hold references or null. Java can convert automatically between a primitive and its wrapper; these conversions are autoboxing and unboxing. Lists hold references, so a list of integers is ArrayList<Integer>.

String is also a reference type, but its objects are immutable: their characters cannot change after creation.

String s = "java";
String t = s;
t = t.toUpperCase();

Here s still refers to "java" and t refers to "JAVA". The last line assigns the result to t; it does not change the shared original string. String methods may reuse the original object when no change is needed.

Printing an object uses its toString() method. Without an override, the inherited result has a form such as Counter@7e9e5f8a: class name, @ and a hexadecimal hash code. Define toString() when printed output should show the object's data.

Key Takeaways

  • Assignment copies a value: primitive data or an object reference.
  • Every evaluation of new creates a separate object.
  • Copying a reference shares the object; it does not copy its fields.
  • Wrappers and strings are reference types; strings are immutable.
  • Printing an object uses toString().

1.2 Shared objects and changing arrows

Two variables that refer to the same object are aliases. An alias gives another route to existing data. To trace a program, keep the variables separate from the objects they reach.

Mutation changes data; reassignment changes a variable

A mutation changes an object's state, such as its stored weight. A reassignment puts a different value into a variable. For a reference variable, that changes its arrow.

Use a box whose weight is in kilograms:

class Box {
    private double weight;
    Box(double weight) { this.weight = weight; }
    double getWeight() { return weight; }
    void setWeight(double weight) { this.weight = weight; }
}

Follow this sequence:

Box a = new Box(2);
Box b = a;
b.setWeight(5);
b = new Box(7);
  1. Draw variable a pointing to object A, with weight 2.
  2. Draw b pointing to that same object A. There is still one box.
  3. Change the weight inside object A to 5. Both routes now reach weight 5.
  4. Create object B with weight 7 and redirect only b to it.

At the end, a.getWeight() is 5 and b.getWeight() is 7. The fourth statement neither resets object A nor moves a to object B.

ActionChange in the diagramEffect through other aliases
b.setWeight(5)Change a field inside the objectThey see the new weight
b = new Box(7)Redirect the arrow from bThey keep their existing arrows
b = nullRemove the arrow from bThey still reach their objects
Reachability controls an object's lifetime

An object is reachable while the running program can follow some usable chain of references to it. That chain may run through a variable, an object field or a list element. A local variable disappearing is therefore not enough to make an object unreachable if a list still holds it.

Continue the trace with a = null;. Object A, weighing 5 kg, now has no reachable path in this example. It is eligible for garbage collection. Java automatically reclaims memory from unreachable objects, but the collector chooses when; the assignment does not promise immediate collection. Object B remains reachable through b.

You do not free Java objects by hand. When a program loses its last usable reference to an object, it also loses the ability to inspect or change that object's data.

Find the hidden aliases

Aliases arise when an object is added to a list, passed to a method, returned from a method or stored in another object's field. Each operation can pass on a reference. Before mutating an object received from elsewhere, identify which other code can still reach it.

A common bug starts with the thought, "I copied the variable, so I have my own box." Use a fresh object when changes should be independent. To diagnose the bug, count object creations separately from assignments, label each object once, and process every statement in order. Do not redraw an object just because a second variable now points to it.

Key Takeaways

  • Aliases lead to one object, so they share its mutations.
  • Reassignment redirects one variable and leaves other aliases alone.
  • Trace fields and reference arrows separately.
  • An unreachable object is eligible for garbage collection; timing is unspecified.

1.3 No object: null and safe checks

The value null means that a reference leads to no object. It is useful for an absent result, but a program must handle that absence before it tries to use an object.

Missing is different from empty

String text = ""; refers to a real string with length 0. String text = null; refers to no string, so there is no object on which to call length().

A primitive cannot hold null: int x = null; does not compile. A wrapper can hold it: Integer x = null; compiles. Converting that wrapper to a primitive with int y = x; throws a NullPointerException, because unboxing needs a wrapper object.

Fields get initial default values when an object is created. Reference fields start as null, numeric fields as 0 and boolean fields as false, until initialization assigns other values. Local variables have no default. Reading a local before assigning it causes a compile error, such as "variable might not have been initialized".

Find the missing reference

Calling an instance method through null, accessing an instance field through it or reading an array's length through it throws NullPointerException at runtime. For example:

String name = null;
System.out.println(name.length());

Read the exception's line number, then identify which reference on that line was missing. Helpful messages are enabled by default from Java 15. A message may say Cannot invoke "SimpleDate.getYear()" because "this.birthday" is null. Local names may appear as placeholders such as <local4>, depending on compilation details. Trace back to the constructor, assignment or search that should have supplied the object.

Printing a String variable whose value is null prints "null"; concatenating it produces text such as "Hi null". These operations do not need to call an instance method through that variable. They can reveal missing data before a later method call fails.

Check before using

The operator && short-circuits: it evaluates the right side only if the left side is true.

if (name != null && name.length() > 0) {
    System.out.println(name);
}

When name is null, the first test is false and length() is skipped. Reversing the two tests loses that protection: the method call would run first.

PurposeSafe expression
Check absencename == null
Check presencename != null
Match a fixed command"quit".equals(command)

The fixed string is a known object, so its equals can receive null and return false. command.equals("quit") fails when command is null. Likewise, x.equals(null) is no way to check whether x exists: it already requires an object.

Make absence a clear design choice

Initialize a list field to new ArrayList<>() so that an empty collection is available immediately. If a search returns null for "not found", document that result and check it at the call site. Avoid filling every field with arbitrary objects merely to silence an exception: supply the object the program needs, or give absence a clear meaning that the calling code handles.

Key Takeaways

  • null means no object; an empty string is a usable object.
  • Fields receive defaults, but locals must be assigned before reading.
  • Null dereferencing and unboxing a null wrapper fail at runtime.
  • Put the null check before the member access in &&.
  • Call fixed-text comparisons on the known string.
  • Initialize collections and handle absent search results deliberately.
2

Use objects across method calls

Methods let objects work together. A call can read or change a shared object, create an independent result, or return an object already held elsewhere. These choices affect what the caller sees after the call. Follow the parameter and return references explicitly, and put comparisons in the class that owns the data. Then decide which methods should share existing state and which should protect it with a copy.

2.1 Parameters receive copied values

Java passes every argument by value. A parameter is a new variable that receives a copy of the argument's value. For an object argument, the copied value is a reference.

Change the parameter or change the object?

A primitive parameter has its own data:

static void addTen(int n) {
    n = n + 10;
}

If x is 5, calling addTen(x) leaves x at 5. Only the local parameter n becomes 15.

A reference parameter can reach the same object as the caller. Suppose Person.growOlder() increases the stored age:

static void celebrate(Person p) {
    p.growOlder();
}

static void replace(Person p) {
    p = new Person("Bo", 30);
}

During celebrate(ana), the parameter p and variable ana are aliases. The changed age remains visible afterward. During replace(ana), assigning p redirects only that parameter. The caller's ana still refers to the original person. A method that swaps two reference parameters has the same limit: it swaps its own copies, not the caller's variables.

Inside a methodCaller observes
Assign a primitive parameterNo change to the caller's variable
Mutate the parameter's objectChanged state of the shared object
Reassign a reference parameterNo change to the caller's variable
Receive another class's object

A gate can read a person's height and keep its own count of checks. This class expects a non-null person with a getHeight() method returning centimeters:

class HeightGate {
    private int minHeight;
    private int checks;

    HeightGate(int minHeight) {
        this.minHeight = minHeight;
    }

    boolean allowIn(Person p) {
        checks++;
        return p.getHeight() >= minHeight;
    }

    int checks() { return checks; }
}

For a gate set to 120 cm, checking people of 132 cm and 115 cm returns true and false. The gate then reports 2 checks. It reads the person's height through a public method and changes its own counter.

Receive another object of the same class

Inside SimpleDate.before(SimpleDate other), this is the date receiving the call and other is the argument. A class can read the private fields of any object of that same class.

For fields day, month and year, compare in order:

boolean before(SimpleDate other) {
    if (year != other.year) {
        return year < other.year;
    }
    if (month != other.month) {
        return month < other.month;
    }
    return day < other.day;
}

For 14 March 2001 and 2 November 2001, years match and month 3 is less than month 11, so the first date is earlier. Equal dates return false because neither is before the other.

Person.olderThan(other) can delegate to birthday.before(other.birthday). The date comparison belongs with the date data. Methods that call instance methods on a parameter need a non-null argument; either check it or make that requirement part of the method's documented use.

Key Takeaways

  • Every parameter receives a copied value, including reference parameters.
  • Mutation can affect a shared object; parameter reassignment cannot replace the caller's variable.
  • this names the receiver; an object parameter names the argument.
  • A class can access private fields in another object of the same class.
  • Delegate comparisons to the class that owns the relevant data.

2.2 Returning a new object or an existing one

An object-returning method returns a reference. Its type tells you what kind of object can be returned, but it does not tell you whether the object is new, shared or absent. The method's purpose must make that choice clear.

Create a result

A factory method supplies an object. It can create one or reuse an existing one. Here each call deliberately creates a separate ticket:

class TicketMachine {
    private int nextNumber = 1;

    Ticket issue() {
        Ticket t = new Ticket(nextNumber);
        nextNumber++;
        return t;
    }
}

The machine expects a Ticket constructor that stores its number. Three calls create three tickets numbered 1, 2 and 3. Returning a local variable is safe: its reference is copied to the caller, which can keep the object reachable after the local variable goes out of scope.

A copy method promises an independent object. In the Counter class:

Counter copy() {
    return new Counter(this.value);
}

After Counter a = new Counter(4); Counter b = a.copy(); b.increase();, a.value() is 4 and b.value() is 5. Replacing a.copy() with a would share the counter.

A method can also build a computed object. For a Point with double fields x and y:

Point midpoint(Point other) {
    return new Point((x + other.x) / 2.0,
                     (y + other.y) / 2.0);
}

Points (2, 4) and (6, 10) give (4.0, 7.0). The method computes each coordinate and passes both to a new point; neither original point changes.

Return an object already stored

A register's findByName should return the matching stored person if callers need to work with that entry. Creating new Person(name, 0) would return invented replacement data and disconnect later changes from the register.

A search may return null when no match exists:

Person p = register.findByName("Lin");
if (p != null) {
    System.out.println(p);
} else {
    System.out.println("Not found");
}

If p is the stored person, changes through p also change the person reached by the register. A return statement does not copy the object.

Keep mutable internal state under control

Suppose Person.getBirthday() returns its SimpleDate field and that date has setters. A caller can then change the person's birthday without calling a Person method. This is an exposure of mutable state.

To protect the field, return a new date built from its values:

SimpleDate getBirthday() {
    return new SimpleDate(birthday.getDay(),
                          birthday.getMonth(),
                          birthday.getYear());
}

This version requires a birthday object to have been initialized. Other designs use an immutable date or offer specific queries without a date getter. Choose the behavior for each method: find normally shares a stored entry, while copy creates independence. A getter's short name alone is no promise of protection.

Key Takeaways

  • An object return passes a reference to the caller.
  • Factories may create or reuse objects; a copy method creates a separate object.
  • Search methods can share existing entries and must define an absent result.
  • Returning a mutable field can let callers bypass the owner's methods.
3

Connect objects and lists

A larger model is a network of objects: a person has a date, a playlist has a list, and the list holds songs. Each connection is a reference with consequences for sharing and change. Build those connections deliberately, delegate work to the object that understands it, and keep collection rules behind useful methods. A copied container can protect membership while still sharing every object inside it.

3.1 Objects with object fields

Composition builds a class using fields whose types are other classes. It expresses a "has a" relationship: a person has a birthday; a course has a teacher. The outer object stores a reference to the contained object.

Create and connect the objects

A constructor can receive an existing date:

class Person {
    private String name;
    private SimpleDate birthday;

    Person(String name, SimpleDate birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    boolean olderThan(Person other) {
        return birthday.before(other.birthday);
    }

    public String toString() {
        return name + ", born " + birthday;
    }
}

Calling new Person("Ana", new SimpleDate(14, 3, 2001)) first creates a date and then gives its reference to the person. Alternatively, a person constructor can receive day, month and year and create the date inside its body. With that approach, the caller does not retain a reference to that newly created date.

Delegate work to the contained object

The call birthday.before(other.birthday) asks the date object to compare dates. Person does not need to repeat the year-month-day comparisons. Concatenation in toString() also delegates: Java calls the birthday object's toString() to obtain its text.

A date object can help compute age. Start with the current year minus the birth year. Then form this year's birthday and subtract one if the current date comes before it. A person born on 14 March 2001 is 24 on 1 January 2026: the year difference is 25, but the birthday has not arrived. On 14 March 2026, the birthday has arrived and the age is 25. Keeping the date comparison in SimpleDate avoids reproducing its rules in every person method.

Decide whether the inner object is shared

The constructor above stores its argument directly. If two people receive one mutable SimpleDate, both birthday fields point to the same date. Changing that date affects both people.

Constructor choiceReference consequence
Store the incoming date referenceCaller and person may share the date
Build a new date from incoming valuesPerson begins with its own date
Store an immutable dateSharing cannot change its date values

A defensive constructor can copy the incoming date's day, month and year. An immutable date has no state-changing methods and keeps its values fixed; simple primitive fields can be final. Copying on input matters even if a getter later returns a copy, because the original caller may still hold the incoming object.

Handle optional connections

A Course might have no teacher yet. If that is a meaningful state, teacherName() can check the field and return "no teacher yet". Otherwise, require a teacher at construction.

In course.getTeacher().getName(), each instance call needs an object: a null course fails at getTeacher(), and a null teacher fails at getName(). A method on the course can keep that absence check in one place and avoid making every caller know the same chain.

Key Takeaways

  • Composition connects an outer object to other objects through fields.
  • Delegate operations to the object that owns the relevant data.
  • Storing an incoming mutable reference can share state with the caller.
  • Copies or immutable inner objects can protect independent state.
  • Optional object fields need a clear null-handling policy.

3.2 Lists hold references to their elements

An ArrayList<Person> is an object whose elements hold references to people. The list controls its sequence of entries; each person controls its own fields. These are separate kinds of change.

Add an object and reach it again
ArrayList<Person> people = new ArrayList<>();
people.add(ana);
people.get(0).growOlder();

This code needs import java.util.ArrayList; and an existing non-null person ana. add copies the reference. The growOlder() call reaches the same person as ana, so the age change is visible through either route. The list never creates a second person here.

You can create an object while adding it: people.add(new Person("Lin", 1998));, using a person constructor that accepts a name and birth year. The list then gives the program a route to that new person through get or a loop. List type arguments must be reference types, so use ArrayList<Integer> for whole numbers.

Compute from objects and select an object

Suppose a list holds parcels with weights 2.5 kg, 4.0 kg and 1.5 kg. A loop adds each parcel's weight:

double total = 0;
for (Parcel p : parcels) {
    total += p.getWeight();
}

The running totals are 2.5, 6.5 and 8.0 kg. Selecting the heaviest parcel needs a reference as well as a comparison:

Parcel heaviest = parcels.get(0);
for (Parcel p : parcels) {
    if (p.getWeight() > heaviest.getWeight()) {
        heaviest = p;
    }
}

This selection expects a nonempty list. The result is the 4.0 kg parcel object, so the caller can use its other methods too. For an empty list, decide on an absent result before reading element 0.

Search every entry

A name search in a class that stores people can use:

Person findByName(String name) {
    for (Person p : people) {
        if (p.getName().equals(name)) {
            return p;
        }
    }
    return null;
}

This version expects non-null people with non-null names. The first match is returned immediately. The final return null belongs after the loop: a failed comparison on the first person does not rule out later matches.

contains, indexOf and remove(Object) search using equals. If Person inherits identity-based equality, a newly constructed person will not match an existing one just because their fields match.

Remove membership without destroying an object

One object can appear twice in one list or in several lists. Removing an entry does not destroy the object or remove other references to it.

Do not remove entries directly from the same list during a for-each traversal. It can trigger ConcurrentModificationException. Collect candidates first, then remove them:

ArrayList<Person> toRemove = new ArrayList<>();
for (Person p : people) {
    if (p.getName().equals("Lin")) {
        toRemove.add(p);
    }
}
people.removeAll(toRemove);

The traversal finishes before the list's membership changes. removeAll uses equality, so it removes entries equal to the collected candidates.

Key Takeaways

  • Adding to a list shares an object reference.
  • A loop can compute a total or retain a reference to a selected object.
  • A failed search returns only after all candidates have been checked.
  • List membership operations depend on equals.
  • Removing an entry leaves other references intact.
  • Collect removals first and change the list after traversal.

3.3 A class that owns a list

A class can keep a list in a private field and offer operations that describe its purpose. A playlist stores songs, counts them and calculates duration. Callers should not need to manage its list directly.

Initialize and wrap the collection

This playlist expects non-null Song objects with a seconds() method:

import java.util.ArrayList;

class Playlist {
    private String name;
    private ArrayList<Song> songs;

    Playlist(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    void add(Song song) { songs.add(song); }
    int count() { return songs.size(); }

    int totalSeconds() {
        int total = 0;
        for (Song song : songs) {
            total += song.seconds();
        }
        return total;
    }
}

Without new ArrayList<>(), the reference field remains null; the first call to add then fails. An empty list is already a usable object, with size 0.

For songs lasting 210 s, 185 s and 247 s, count() returns 3. The total grows from 210 to 395 to 642 s. Integer division by 60 gives 10 minutes, with remainder 42 seconds. A longest() method can select and return the 247 s song, using the same reference-selection pattern as the heaviest parcel. Define an absent result for an empty playlist.

Protect membership through methods

Keeping songs private blocks direct field access, but return songs; exposes that very list. A caller could call clear() and bypass every playlist rule.

Specific operations such as add, count and totalSeconds keep responsibility with the playlist. Its add method could enforce a maximum size or refuse duplicates with songs.contains(song). Content-based duplicate checks need the song class's equals to define which fields make two songs equal.

When a constructor receives an initial list, copy its structure with this.songs = new ArrayList<>(initialSongs);. Storing the parameter directly would let the caller keep an alias to the playlist's internal list.

A new list can still share songs

A query can return new ArrayList<>(songs). This is a shallow copy: a new list object, containing the existing song references.

Caller action on the returned copyEffect on the playlist
Remove an entryPlaylist membership stays the same
Clear the copyPlaylist membership stays the same
Mutate a shared songPlaylist sees that song's changed state

A songsLongerThan(200) query creates a fresh result list, loops over the stored songs and adds those whose duration exceeds 200 s. For the three songs above it returns the 210 s and 247 s songs. When song values must also be independent, copy the mutable songs or make Song immutable.

Key Takeaways

  • An owner object, its list and its elements are separate objects.
  • Initialize the list before using it.
  • Offer operations that enforce the owner's collection rules.
  • Copy incoming lists to prevent an outside alias to internal membership.
  • A shallow list copy shares its element objects.
4

Define what makes objects equal

Two objects can describe the same thing while remaining separate objects. Java gives you different tests for identity and content, and collections depend on the choice your class makes. Start by choosing the fields that define equality, then implement equals and hashCode as a pair. Their contracts explain why a list can find an equal object, why a hash collision is allowed, and why changing a stored key can break a lookup.

4.1 Identity and matching content

Identity asks whether two references lead to the same object. Content equality asks whether objects have the same relevant data, according to the class's rules. Two separate book objects can describe the same title and publication year without being identical.

Choose the right comparison
Values being comparedMeaning of ==Meaning of equals
PrimitivesCompare primitive valuesNo instance method on a primitive
ReferencesSame object, or both nullThe class's equality rule
StringsSame string objectMatching character content
Class inheriting Object.equalsSame objectSame object

Every class inherits equals(Object) from Object. That default checks identity. Java does not inspect all the fields of a new class automatically.

String a = "java";
String b = new String("java");
System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

new String creates a separate object, while String.equals compares its characters. Text comparison is case-sensitive: "Java".equals("java") is false. equalsIgnoreCase gives a case-insensitive comparison when that is the intended rule.

Why identity sometimes seems to compare content

Equal string literals share a string object, so "java" == "java" is true. This can hide a bug in a small test. Text obtained from input or constructed while a program runs need not share that object. Use equals for content regardless of how the text was obtained.

Wrapper comparisons have a similar trap:

Integer a = 127, b = 127;
Integer c = 1000, d = 1000;
System.out.println(a == b);      // true
System.out.println(c.equals(d)); // true

Autoboxing reuses Integer objects for values from -128 through 127. Whether larger values share an object is not a portable numeric comparison rule. On a standard setup the two 1000 wrappers are separate, but a larger cache is permitted. Use wrapper equals for matching wrapper values or compare primitives. If one side of == is an int, Java unboxes the wrapper and compares numeric values; unboxing a null wrapper fails.

Decide which fields define equality

For a book catalog, title and publication year might define equality while the current borrower is ignored. Those selected fields must drive the equality method. Another program could choose a unique identifier instead; the useful rule depends on what its objects represent.

An ArrayList uses equals in contains, indexOf and remove(Object). Suppose Book only stores fields and inherits equals. Adding one new Book("Dune", 1965) does not make contains(new Book("Dune", 1965)) true: the second expression creates a different object. Overriding equality by title and year lets a search find that equal content.

HashSet and HashMap also use hashCode to narrow the candidate objects before checking equality. Defining content equality therefore requires a matching hash-code rule as well.

Key Takeaways

  • For references, == tests identity, while equals follows the class's rule.
  • Object.equals compares identity until overridden.
  • Use String.equals for text content.
  • Shared literals and wrapper caching can make identity tests misleading.
  • Choose equality fields deliberately; collections depend on that choice.

4.2 Implementing equals correctly

An equality method must replace the inherited equals(Object) and compare the fields that define your object's meaning. The correct signature matters as much as the comparisons inside it.

A complete equality pattern

This class uses title and year, allows a null title, and keeps its equality fields fixed:

import java.util.Objects;

final class Book {
    private final String title;
    private final int year;

    Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof Book)) {
            return false;
        }
        Book book = (Book) other;
        return year == book.year
            && Objects.equals(title, book.title);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, year);
    }
}

Read equals in four steps. The identity check accepts the same object immediately. The type check rejects anything that is not a book, including null. The cast gives a Book variable through which the fields can be read. The final expression checks both equality fields.

The parameter type is Object, not Book. Writing equals(Book other) overloads the name with another method and leaves the inherited equals(Object) unchanged. A direct call can then appear to work while ArrayList.contains still uses identity. @Override makes the compiler catch that signature mistake.

From Java 16, the type check and cast can be combined: if (!(other instanceof Book book)) { return false; }. After the check succeeds, book is available for field comparisons.

Match comparisons to field types

Use == for integral, character and boolean fields. Use an object's equals for its content. For a nullable object field, Objects.equals(a, b) returns true when both are null, false when only one is null, and otherwise uses equality.

For double fields, Double.compare(x, other.x) == 0 supplies consistent special-value handling: two NaNs compare as equal, and positive and negative zero are distinguished. Pair that choice with a compatible hash rule. Raw floating-point == treats NaN differently.

Check the equality contract

For non-null references, equality must be:

  • Reflexive: x.equals(x) is true.
  • Symmetric: x.equals(y) and y.equals(x) agree.
  • Transitive: if x equals y and y equals z, x equals z.
  • Consistent: repeated calls agree while the compared data stays unchanged.
  • False for null: x.equals(null) is false.

Two books for "Dune", 1965 compare equal; changing one year to 1984 makes them unequal. A book must not compare equal to a string containing its title: the string would reject the book, breaking symmetry.

Some implementations use other == null || getClass() != other.getClass() for the type check. It rejects different runtime classes. instanceof can accept subclasses; for the final class above, no subclass exists and the two checks agree.

Key Takeaways

  • Override with public boolean equals(Object other) and @Override.
  • Check identity, check type, convert the reference and compare equality fields.
  • Use null-safe object comparisons when fields can be absent.
  • Respect reflexivity, symmetry, transitivity, consistency and rejection of null.
  • equals(Book) overloads the method and does not replace equals(Object).
  • Type-check choices matter when subclasses are allowed.

4.3 Matching hash codes and stable keys

A hash code is an integer used to narrow a search. HashSet and HashMap use it to find candidate objects, then use equals to confirm a match. It is not a unique object identifier.

The contract goes in one direction

Equal objects must return the same hash code. Unequal objects may also share a code; that is a collision. A collision does not make them equal, because equality is still checked.

A hash code must stay consistent while the information used by equals stays unchanged. For a correct implementation, different hash codes rule out equality, but equal hash codes do not establish it.

When a class overrides equals but inherits Object.hashCode, separate equal objects commonly receive different codes. A set may then retain both, and a map lookup using a fresh equal key may miss. The exact failure is not guaranteed, because different objects can have colliding inherited codes. Fix the contract instead of relying on a particular failed lookup.

Compute from the equality fields

For the book class that compares title and year:

@Override
public int hashCode() {
    return Objects.hash(title, year);
}

Import java.util.Objects. The same field values now produce the same result. A book set stores one entry after adding two equal books, and a map can retrieve a value using a fresh equal book key.

Objects.hash combines the field hashes using 31. For "Dune", the string hash is 2141736. Combining that title and year 1965 gives 31 multiplied by the sum of 31 and 2141736, then adds 1965: the result is 66396742.

String hashing also uses 31. For "ab", character codes 97 and 98 give 97 multiplied by 31 plus 98, or 3105. Longer calculations can overflow the int range and wrap, so a negative hash code is normal.

Hash ruleContract consequence
Use the same equality fieldsEqual objects get matching codes
Add a field that equality ignoresEqual objects can get different codes
Use fewer equality fieldsLegal, but may create more collisions
Always return 1Legal, but gives poor search performance
Do not change the key during storage

Suppose a book's year is mutable and affects its hash code. After insertion in a set, changing the year can make contains(book) fail even with that same reference: the lookup now searches using a different code. The set can still report size 1.

Keep equality fields stable while an object is a set element or a map key. If they must change, remove the object while its old values still apply, change it and insert it again. Trying removal only after the change can encounter the same lookup problem.

Generated equality still needs a sound data choice

Java 16 and later support records, such as record Book(String title, int year) {}. A record generates accessors title() and year(), plus equality, hashing and a readable string form from all its components. Component fields are final. This example is a useful immutable value because its components are a string and an integer; a record holding a mutable list would still share that list's state.

For ordinary classes, an IDE can generate equals and hashCode together. Select the fields that define the intended equality and keep both methods aligned when that choice changes.

Key Takeaways

  • Equal objects require equal hash codes; collisions between unequal objects are allowed.
  • Override hashCode whenever you override equals.
  • Hash only data compatible with the equality rule.
  • Keep hash-relevant fields stable during set membership or use as a map key.
  • Remove before changing a key, then reinsert.
  • Records generate equality from their components; mutable components still need care.
5

Give each class a clear job

A text program has two kinds of work: talking to a user and managing the data and rules. Keeping those jobs in separate classes makes the rules easier to check and reuse. Connect the classes through constructor parameters and small public methods. Then add a feature first where its rule belongs, verify the result with known values, and give the interface a command that calls it and displays the answer.

5.1 Input, output and program rules

The user interface reads input and displays output. The program logic stores data and applies rules. A separated design passes values into the logic and gets results back; its logic classes do not use Scanner or System.out.

Connect the objects in main
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    TemperatureLog log = new TemperatureLog();
    UserInterface ui = new UserInterface(scanner, log);
    ui.start();
}

With import java.util.Scanner;, this wiring creates one input reader, one log and one interface. It belongs inside the program's entry class. UserInterface stores the supplied objects in fields so that every command works with that same log.

WorkClass responsible
Read command textUser interface
Store a readingTemperature log
Calculate average or maximumTemperature log
Round and print a resultUser interface
Reject an impossible temperatureTemperature log
Explain invalid input to the userUser interface
Return values that any caller can use

A log might provide add(double celsius), count(), average() and max(). Readings of 18.5, 21.0 and 19.5 °C have sum 59.0, count 3, maximum 21.0 and average about 19.6667 °C. The UI can round the returned average with Math.round(average * 10) / 10.0 and show 19.7 °C.

Returning the number makes direct checks easy. A method named printAverage() that does only printing couples a calculation to console output, which makes reuse more awkward.

For a simple command loop, suppose add accepts valid temperatures and count returns their number:

class UserInterface {
    private Scanner scanner;
    private TemperatureLog log;

    UserInterface(Scanner scanner, TemperatureLog log) {
        this.scanner = scanner;
        this.log = log;
    }

    void start() {
        while (true) {
            String command = scanner.nextLine();
            if ("quit".equals(command)) {
                break;
            }
            if ("count".equals(command)) {
                System.out.println(log.count());
            }
        }
    }
}

The loop handles interaction. It asks the log for a result and prints it without knowing how readings are stored. An average command should handle an empty log according to the logic's documented result or check count() before calling a method that requires readings.

Validate at the right boundary

The logic can make add return false without storing a reading below -273.15 °C. Every caller then gets the same data rule. The UI decides how to report that rejection and ask again. Converting nonnumeric input into a useful error message is also interface work.

Use one Scanner for System.in and pass it to readers. Multiple scanners can compete for buffered input, and closing one closes the underlying input stream. Keep the lifetime of the shared scanner with the code that owns the program's input.

The logic can later support another interface through the same public methods. Reuse comes from keeping calculations and validity rules available as method results, independently of a particular command loop.

Key Takeaways

  • The UI reads and displays; the logic stores data, enforces rules and returns results.
  • main constructs the shared objects and starts the interface.
  • Pass one scanner and the logic object into the UI constructor.
  • Data rules belong in the logic; input messages and display formatting belong in the UI.
  • Returning results makes direct checks and interface reuse easier.

5.2 Add a feature and check its behavior

A separated program can grow one small change at a time. Give the logic a useful method, check it directly, then connect a command that gathers its input and displays its result.

Add a query to the logic

A TemperatureLog storing an initialized ArrayList<Double> readings can count readings above a limit:

int countAbove(double limit) {
    int count = 0;
    for (double reading : readings) {
        if (reading > limit) {
            count++;
        }
    }
    return count;
}

The strict comparison excludes readings exactly equal to the limit. For 18.5, 21.0, 19.5 and 23.0 °C, countAbove(20.0) returns 2. Put the comparison in the log so every caller gets the same rule.

The UI command reads a limit, calls log.countAbove(limit) and prints the count. If each command grows long, split start() into private helpers such as addReading() and showStatistics(). The helpers organize interaction; they do not move the data rules out of the log.

Check without typing commands

Create the logic object and call its methods with values whose answers you know:

TemperatureLog log = new TemperatureLog();
log.add(18.5);
log.add(21.0);
log.add(19.5);
log.add(23.0);
System.out.println(log.count() == 4);
System.out.println(log.average() == 20.5);
System.out.println(log.max() == 23.0);
System.out.println(log.countAbove(20.0) == 2);

The sum is 82.0, so the expected average is 20.5. These particular values have exact binary representations, making these direct equality checks suitable for the example. Also check an empty log according to its documented behavior, a limit matching a reading, and rejection of invalid input without changing the count. Automated test tools build on the same idea of a known input and an expected result.

Change storage only when it still supports the queries

An interface that calls only average, count and max need not know whether the log holds a list. Running sum, count and maximum fields can answer those three queries without retaining every reading.

An arbitrary countAbove(limit) query needs more information. The samples 18, 22, 23 and 20, 20, 23 have the same sum 63, count 3 and maximum 23, but their counts above 20 are 2 and 1. Keeping only those three aggregates loses the distinction. The public methods describe what must still work after a storage change.

Combine the design ideas

A recipe book can own a private list of recipes. Each recipe can contain ingredients measured in grams and a cooking time in minutes. Equality can support duplicate checks, while a UI adds, lists and searches recipes through the book's methods. A copied result list can protect membership while still sharing recipe objects; decide whether callers may change those recipes.

Work in small steps: implement one method, check it and then add its command. If a change breaks an existing result, return to a working state and isolate that change before adding another feature.

Key Takeaways

  • Add and check the logic method before connecting its UI command.
  • Use known inputs and expected results to check logic directly.
  • Include boundary cases such as an empty collection or an exact threshold.
  • A storage change must preserve enough information for every public query.

Java Objects and References examples

5 Walkthroughs

1

Trace variables without losing the objects

15 steps
Follow primitive copies, shared references, reassignment and null through a short program.
  1. 1
    IntroductionDraw variables and objects

    A variable can hold a number directly or hold a reference leading to an object. That difference explains why copying a variable sometimes gives independent data and sometimes shares a change.

    We will draw variables separately from objects, then follow each statement. Keep the number of arrows separate from the number of objects: several arrows can end at one object.

  2. 2
    ExplanationA value can be data or a reference

    Java has eight primitive types: byte, short, int, long, float, double, char and boolean. Their variables hold data directly. Classes and arrays are reference types; their variables hold references or null.

    In int count = 13;, the value is 13. In Tally t = new Tally(13);, the value in t is a reference to an object holding its own data.

  3. 3
    ExampleCopy a primitive value

    Follow the assignments in order:

    int start = 13;
    int next = start;
    next += 2;
    

    The second statement copies 13 into next. The third reads that copy and replaces it with 15. start remains 13 because no statement assigns a new value to it. Draw two boxes containing separate numbers; no shared object is involved.

  4. 4
    ExampleCopy a reference value

    Suppose Tally stores an integer, addOne() increases it and value() reads it.

    Tally first = new Tally(13);
    Tally next = first;
    next.addOne();
    

    One new creates one tally. The assignment gives next another route to it. Calling addOne() changes its stored value to 14, which first.value() now reads too. The second variable is not a second tally.

  5. 5
    ChallengeSeparate an object from its alias

    With the same Tally methods, what does a.value() return?

    Tally a = new Tally(20);
    Tally b = new Tally(a.value());
    b.addOne();
    

    ---reveal---

    It returns 20. The second new creates an independent tally initialized with 20; its increment changes only that second object to 21. Reading a field value and using it in a constructor differs from copying the original object reference.

  6. 6
    ConnectionSome shared objects cannot be mutated

    Strings and wrappers such as Integer are reference types. Autoboxing converts primitive values to wrappers for uses such as ArrayList<Integer>.

    Strings are immutable: if left and right share "cedar", assigning right = right.toUpperCase() does not change the original characters. left stays "cedar". The returned uppercase result is assigned only to right.

  7. 7
    AnalogyUse arrows to follow change

    Think of each variable as a card showing a route to an object. Copying the card gives another route to the same destination. Writing a different route on one card does not rewrite the others.

    In a memory diagram, draw an arrow from each reference variable to its object. A setter changes the object's field values; an assignment changes one variable's arrow. Keep those two edits visually distinct.

  8. 8
    ExampleChange shared state first

    Let Crate store a mass in kilograms, with setMass and mass methods.

    Crate a = new Crate(3);
    Crate b = a;
    b.setMass(6);
    

    Draw a and b pointing to one crate. The setter updates the mass inside that crate from 3 to 6. Both a.mass() and b.mass() now return 6; the setter has not redirected either variable.

  9. 9
    ExampleThen redirect one variable

    Continue the crate trace:

    b = new Crate(9);
    

    Create a second object with mass 9 and redirect only b. Variable a still points to the original crate, whose mass remains 6. There are now two reachable crate objects. This is a reassignment, so nothing about the first crate's stored mass is reset.

  10. 10
    ChallengeKeep the remaining route

    Continue with Crate c = a; a = null;. Through which variable can the 6 kg crate still be reached, and is it eligible for collection?

    ---reveal---

    It is still reachable through c, so it is not eligible for collection. The earlier assignment copied the reference into c; nulling a removed only one route. The separate 9 kg crate remains reachable through b.

  11. 11
    Key InsightA field or list can preserve a route

    When the last usable path to an object disappears, it becomes unreachable and eligible for garbage collection. Java chooses the collection time; losing a reference does not promise immediate reclamation.

    A path may run through another object or list. Before calling an object unreachable, check those links as well as local variables. A list entry can preserve an object after its original variable has gone out of scope.

  12. 12
    ExplanationNull means the destination is missing

    null is not an empty object. A null String has no object on which to call length(), while "" is a usable string of length zero. Reference fields default to null; local variables need an assignment before reading.

    Wrappers can also be null. Integer n = null; int count = n; throws NullPointerException when unboxing tries to obtain a primitive from the missing wrapper.

  13. 13
    TipRead the error at the failing call

    For a null error, locate the line number and then the instance call, field access or unboxing on that line. Helpful messages, enabled by default from Java 15, can identify the missing field.

    Trace backward to the constructor, search result or assignment that should have supplied an object. Seeing "null" in printed text is a clue: printing a null string can succeed even though a later instance call fails.

  14. 14
    ChallengeGuard the combined trace

    A search assigns Crate found = findCrate();, returning either a crate or null. What condition safely checks for mass above 5 kg? Why must its tests be in that order?

    ---reveal---

    Use found != null && found.mass() > 5. The null test runs first, and && skips mass() when no crate exists. Putting the method call first would dereference null before reaching the check. A returned reference follows the same rules as the aliases traced earlier.

  15. 15
    RecapCheck a reference trace

    For a fresh trace, mark each new, copy the value at each assignment and update only the field targeted by a mutation. Then follow every remaining path before deciding an object is unreachable.

    At a failing line, identify the exact operation that needs an object. Check null before that operation, or initialize the field with the object the program actually needs, such as an empty list.

2

Follow an object into and out of a method

12 steps
Trace parameter mutation and reassignment, then choose between shared and independent return values.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionFollow a call and its result
  2. 2
    ExplanationA parameter gets its own value
  3. 3
    ExampleMutate the shared object
  4. 4
    ExampleReassign the local route
  5. 5
    ChallengeCan a method swap caller variables?
  6. 6
    ConnectionCompare the receiver with its argument
  7. 7
    ExplanationThe return type does not promise a copy
  8. 8
    ExampleBuild an independent returned dial
  9. 9
    ChallengeFind an entry that can be updated
  10. 10
    TipA getter can reveal a hidden alias
  11. 11
    ChallengeCombine a return with a parameter call
  12. 12
    RecapInspect a method at both boundaries
3

Build and protect a collection of objects

16 steps
Connect object fields and lists, search stored entries and distinguish container copies from element copies.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionSeparate the layers of state
  2. 2
    ExplanationModel a has-a relationship
  3. 3
    ExampleDelegate the date rule
  4. 4
    ChallengeProtect the incoming date
  5. 5
    ExplanationAn entry is another route
  6. 6
    ExampleCompute a total through a loop
  7. 7
    ExampleKeep the selected object
  8. 8
    ChallengeReturn absence at the right time
  9. 9
    TipSeparate removal from traversal
  10. 10
    ExplanationGive the owner an initialized list
  11. 11
    ExampleOffer useful operations
  12. 12
    ChallengeDistinguish a list copy from its elements
  13. 13
    ConnectionProtect both directions
  14. 14
    ExampleFilter into a new result
  15. 15
    ChallengeFollow a copy through another object
  16. 16
    RecapReview the collection boundaries
4

Build equality that collections can trust

16 steps
Choose identity or content comparison, implement equals and hashCode together, and reason about stable keys.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionDecide what a match means
  2. 2
    ExampleEqual text in separate objects
  3. 3
    TipDo not let caching choose the rule
  4. 4
    ChallengeGive a class content equality
  5. 5
    ExplanationChoose fields and the signature
  6. 6
    ExampleAccept identity and reject wrong types
  7. 7
    ExampleCompare the selected fields
  8. 8
    ChallengeCatch a broken contract
  9. 9
    Key InsightCheck more than one matching pair
  10. 10
    ExplanationHash codes narrow the candidates
  11. 11
    ExampleGenerate the matching hash
  12. 12
    ChallengeKeep a collision separate from equality
  13. 13
    ExampleNotice what a changed key does
  14. 14
    ConnectionA simple record supplies both methods
  15. 15
    ChallengeTrace the set after generated equality
  16. 16
    RecapAudit a value class
5

Add a feature without mixing input and rules

11 steps
Wire a console interface to reusable logic, check a new query directly and preserve its storage requirements.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionConnect rules to a conversation
  2. 2
    ExplanationName the two jobs
  3. 3
    ExampleWire one set of shared objects
  4. 4
    ExampleReturn the result to the display
  5. 5
    ChallengePut a rejection in the right class
  6. 6
    ExplanationGive the new query a logic method
  7. 7
    ExampleCheck with known inputs
  8. 8
    ChallengeEvaluate the storage proposal
  9. 9
    TipGrow the program one checked step at a time
  10. 10
    ChallengeMove to a different interface
  11. 11
    RecapReview a new feature at its boundary

Java Objects and References practice questions

40 Practice Questions in 4 sets

Set 1: Follow values, calls and simple comparisons10 Practice Questions

Question 1 of 10

What does original hold after this code?

int original = 8;
int duplicate = original;
duplicate = 11;
  1. 11
  2. 8
  3. 19
  4. 0
Show answerHide answer
Answer: B
The first assignment stores 8 in each primitive variable. The later assignment changes only duplicate, so original still holds 8. Treating the variables as aliases would give 11, but an int assignment copies the number itself and creates no shared object.

Question 2 of 10

Lamp has a boolean field initially false; turnOn() sets it to true and isOn() reads it. What do a.isOn() and b.isOn() return, in that order?

Lamp a = new Lamp();
Lamp b = a;
b.turnOn();
b = new Lamp();
  1. false, false
  2. true, true
  3. false, true
  4. true, false
Show answerHide answer
Answer: D
The call turns on the lamp shared by a and b. Reassigning b then points it at a fresh lamp, whose field starts false, while a keeps the lit lamp. Predicting two true results misses the new object created by the final statement.

Question 3 of 10

label may be null. What condition safely checks that it has at least three characters?
  1. label != null && label.length() >= 3
  2. label.length() >= 3 && label != null
  3. label != null & label.length() >= 3
  4. label.equals(null) && label.length() >= 3
Show answerHide answer
Answer: A
The null check runs first, and && skips the length call when that check is false. Putting the length call first already dereferences the missing object. A single & also evaluates both sides, so it does not provide the needed short-circuit protection.

Question 4 of 10

A method receives a non-null Robot r. It executes only r = new Robot();. What happens to the caller's variable that was passed as the argument?
  1. It now refers to the new robot.
  2. It becomes null when the method returns.
  3. It still refers to the original robot.
  4. It refers to a copy of the original robot.
Show answerHide answer
Answer: C
The parameter receives a copy of the caller's reference. Assigning a new reference to that parameter changes the method's local variable only. Replacing the caller's reference would require an assignment in the caller; this method does not mutate the original robot either.

Question 5 of 10

Gauge.copy() returns new Gauge(value). A gauge starts at 6, and raise() adds 1. What values do first and second report?

Gauge first = new Gauge(6);
Gauge second = first.copy();
second.raise();
  1. 7 and 7
  2. 6 and 7
  3. 6 and 6
  4. 7 and 6
Show answerHide answer
Answer: B
The copy method creates a second gauge initialized from the first gauge's value. Raising that new object changes its value to 7 and leaves the first at 6. Two 7s would follow from sharing the original reference, but the method explicitly constructs a separate object.

Question 6 of 10

A Device constructor stores its mutable Settings argument directly. Its getter returns a copy of the stored settings. Two devices receive the same settings object, and the caller changes that original object afterward. What design change makes each device independent of that outside change and of the other device?
  1. Store settings directly; return the stored object from the getter.
  2. Keep each constructor; copy settings only when the getter runs.
  3. Copy settings in each constructor; keep the copying getter.
  4. Create one shared settings copy; give it to both constructors.
Show answerHide answer
Answer: C
Both devices currently share the incoming object, so the original caller can change their state without using a getter. Each constructor must copy that incoming state to give each device its own object. A copying getter protects outward access but cannot remove an alias retained during construction.

Question 7 of 10

A non-null Sensor s is added to two lists. One list removes its entry for s. What happens to the sensor in the other list?
  1. It remains the same sensor object.
  2. It is removed from the other list.
  3. It is replaced by a copied sensor.
  4. It becomes a null list element.
Show answerHide answer
Answer: A
Removing an entry changes membership in that one list. The other list still holds its own reference to the sensor, so it keeps the same object. Removal does not destroy the sensor or search all other containers for references to it.

Question 8 of 10

What do these two expressions return?

String word = new String("maple");
word == "maple"
word.equals("maple")
  1. true, true
  2. false, false
  3. true, false
  4. false, true
Show answerHide answer
Answer: D
The explicit construction creates a different string object from the literal, so the identity test is false. String.equals compares the characters and returns true. Equal text does not make separately created strings identical, even though equal literals can share an object.

Question 9 of 10

What declaration overrides the equality method inherited from Object?
  1. public boolean equals(Note other)
  2. public int equals(Object other)
  3. public boolean equals(Object other)
  4. public boolean equal(Object other)
Show answerHide answer
Answer: C
The inherited method has the name equals, return type boolean and parameter type Object. Matching that declaration overrides it; adding @Override lets the compiler check the match. A parameter of type Note creates an overload and leaves the inherited method in place.

Question 10 of 10

A console app tracks reading durations. The same logic will later support a web page. Where should the calculation of total seconds and the printing of "Total" belong?
  1. Calculation in the logic; printing in the console UI.
  2. Both calculation and printing in the console UI.
  3. Both calculation and printing in the logic class.
  4. Printing in the logic; calculation in the console UI.
Show answerHide answer
Answer: A
The total is a data calculation that both interfaces need, so the logic should return it. The console UI chooses its label and prints the result. Putting the calculation in the console UI would force the future web interface to duplicate it or depend on console-specific code.
Set 2: Apply sharing, collections and program boundaries10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What happens to a after the last line?

String a = "river";
String b = a;
b = b.toUpperCase();
  1. It now refers to the text "RIVER".
  2. It becomes null after the assignment.
  3. It still refers to the text "river".
  4. It now refers to an empty string.
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

What happens when this code runs?

Integer reading = null;
int value = reading;
  1. Unboxing throws NullPointerException.
  2. value receives the primitive value 0.
  3. reading becomes an integer wrapper for 0.
  4. Compilation fails because wrappers cannot hold null.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Meter stores an integer, with set(int) and get(). What does m.get() return after the call?

static void adjust(Meter x) {
    x.set(14);
    x = new Meter(22);
    x.set(25);
}
Meter m = new Meter(9);
adjust(m);
  1. 9
  2. 22
  3. 25
  4. 14
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Bin.items() returns its private list of mutable Part objects directly. A caller must be allowed to inspect and modify the returned list and parts without changing the bin's membership or stored part state. What must the result contain?
  1. A new list containing the stored part references.
  2. A new list containing independent copies of the parts.
  3. The stored list after copying each part in place.
  4. The stored list containing immutable numeric indexes.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A name search checks a list in order. With names "Iris", "Noel", "Sana", it returns null when asked for "Sana". It currently has return null in an else after the name test inside the loop. Where should that return go?
  1. Before the loop begins checking any entries.
  2. After the loop finishes without finding a match.
  3. Immediately after the first successful name test.
  4. Inside the loop before each new name is read.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

copy is created with new ArrayList<>(original). Both lists initially contain the same mutable Clip c. The caller changes c through copy.get(0), then clears copy. What remains in original?
  1. One entry for the changed clip.
  2. One entry for the unchanged clip.
  3. No entries; both lists are cleared.
  4. One null entry where the clip was.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Badge has fields and a constructor but does not override equals. What is the result?

new Badge("Q7").equals(new Badge("Q7"))
  1. true, because the field values match.
  2. true, because constructors define equality.
  3. false, because strings cannot be equality fields.
  4. false, because these are separate objects.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

Tag.equals compares only code. Tag.hashCode uses both code and mutable color. Two tags have the same code but different colors. A developer proposes returning a constant hash code to repair correctness. How should the two rules be assessed?
  1. The current rule can break the contract; a constant is legal but inefficient.
  2. The current rule is valid; a constant breaks distinct-object hashing.
  3. Both rules break the contract because hash codes must be unique.
  4. Both rules are equally efficient because equality confirms matches.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A duration tracker rejects negative durations. Where should rejection and the message asking the user to try again be implemented?
  1. Rejection in the UI; the message in the logic.
  2. Both rejection and the message in the UI only.
  3. Rejection in the logic; the message in the UI.
  4. Both rejection and the message in the logic only.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A log currently offers count(), average() and max(). Its developer adds countAbove(limit) for any caller-supplied limit, then replaces all readings with only a sum, count and maximum. Why can the new query no longer be implemented correctly for every log?
  1. The average becomes incorrect whenever the readings include a threshold value.
  2. Different reading lists can have the same aggregates but different threshold counts.
  3. A maximum can be computed only by retaining every original reading object.
  4. Changing a private field always requires changing every UI command as well.
Show answerHide answer

Save this lesson to see this answer.

Set 3: Debug aliases, equality and collection design10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

Only the references shown exist, and all variables remain in use. Cell has mutable value data. How many cell objects remain reachable?

Cell a = new Cell(3);
Cell b = new Cell(8);
Cell c = a;
a = b;
b = null;
c = a;
  1. Two cell objects.
  2. Three cell objects.
  3. No cell objects.
  4. One cell object.
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A class declares private ArrayList<Note> notes;. Its constructor never assigns notes, and add immediately calls notes.add(note). What change makes a newly constructed instance ready to store notes?
  1. Declare a local list inside add and leave the field alone.
  2. Assign null to notes explicitly in the constructor.
  3. Initialize notes with new ArrayList<>().
  4. Make notes public so the caller can access the field.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Box has an integer field read by get() and changed by set(int). What values do left.get() and right.get() report after this call?

static void revise(Box x, Box y) {
    x.set(10);
    x = y;
    x.set(16);
}
Box left = new Box(4);
Box right = new Box(7);
revise(left, right);
  1. 10 and 16
  2. 16 and 16
  3. 4 and 16
  4. 10 and 7
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A Member stores a non-null SimpleDate joined and needs to decide whether it joined before another member. Where should the year, month and day comparison be implemented?
  1. In every UI command that compares members.
  2. In SimpleDate.before, called by the member method.
  3. In Member.toString, read back as text by the caller.
  4. In each caller after exposing all date fields publicly.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A Queue constructor stores an incoming ArrayList<Job> directly, but its getter returns a new shallow list. The caller clears the original input list. The queue must keep its original membership, while job objects may remain shared. What repair meets that requirement?
  1. Copy the incoming list in the constructor as well.
  2. Keep the constructor and copy every job in the getter.
  3. Return the internal list so both lists stay synchronized.
  4. Change the getter to return a new empty list each time.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

Token declares public boolean equals(Token other) without @Override. Direct comparisons appear correct, but an ArrayList<Token> fails to find a fresh token with matching fields. What is the signature problem?
  1. The method overrides equals, but lists compare only hash codes.
  2. The method cannot read private fields in another token object.
  3. The method overloads equals and leaves equals(Object) unchanged.
  4. The method must return an integer for collection searches to work.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

Key.equals and hashCode both use serial. A mutable key is added to a HashSet, then its serial changes and a lookup fails. What sequence correctly supports a planned serial change while preserving membership?
  1. Change the serial, remove with the new serial, then add again.
  2. Change the serial, call contains, then keep the entry untouched.
  3. Add the key a second time, change the serial, then remove once.
  4. Remove with the old serial, change the serial, then add again.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A UI method needs the number of stored tasks. What should the logic method normally do?
  1. Print the count on the console.
  2. Return the count as an integer.
  3. Read a command, then print a label.
  4. Open a scanner to request a count.
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

countAbove(limit) counts readings strictly greater than its argument. A log contains 11.5, 15.0, 15.0 and 18.5 °C. What should a direct check expect for countAbove(15.0)?
  1. 0
  2. 1
  3. 2
  4. 3
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

Mark.equals compares code. A list holds marks with codes "A", "B", "A"; the two A marks are separate objects. A second list contains only a reference to the first A mark. What remains after marks.removeAll(second)?
  1. Only the mark with code "B".
  2. The B mark and the second A mark.
  3. The two marks with code "A".
  4. No marks remain in the list.
Show answerHide answer

Save this lesson to see this answer.

Set 4: Reason through copies, contracts and design changes10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A list needs whole-number entries that may be absent. What element type supports storing either a number or null?
  1. int
  2. Integer
  3. double
  4. boolean
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Node contains mutable data. Only the references shown exist. At the final line, how many node objects are reachable through first or the list?

Node first = new Node();
ArrayList<Node> nodes = new ArrayList<>();
nodes.add(first);
first = new Node();
nodes.add(first);
nodes.remove(0);
  1. One node object.
  2. Two node objects.
  3. Three node objects.
  4. No node objects.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

Locker.find(id) currently returns a newly constructed mutable Item with the same field values as the matching stored item. Callers use the result to update the stored item's label. What behavior must the method provide to make those updates affect the locker?
  1. Return a shallow copy of the locker list.
  2. Return a new item with the updated label.
  3. Return the matching stored item reference.
  4. Return another copy after each label change.
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

Two Schedule objects store one shared mutable SimpleDate field. A developer makes only the schedule fields final. What happens if the original date object is changed through its setter?
  1. Only the schedule whose getter was called sees it.
  2. Neither schedule observes the changed date.
  3. Each schedule keeps its own copy of the old date.
  4. Both schedules still observe the changed date.
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A library keeps a private list of mutable Document objects. matching(prefix) builds a fresh list containing existing matching documents. A caller removes one result and edits another result's title. What effect can this have on the library?
  1. Membership changes; every stored title stays the same.
  2. Both library membership and the stored title change.
  3. Neither library membership nor any stored title changes.
  4. Membership stays the same; a stored title changes.
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

For non-null object references x and y, what does x == y establish when it is true?
  1. Their toString() results define equality.
  2. They refer to the same object.
  3. Their classes override equals together.
  4. Their fields have never been changed.
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A Score.equals(Object) method accepts another score when the absolute difference between their integer values is at most 2. Scores x, y and z hold 10, 12 and 14. No values change. What equality-contract problem appears across these three objects?
  1. Transitivity fails: the first two pairs match, but the endpoints do not.
  2. Symmetry fails: reversing the same pair changes its comparison result.
  3. Reflexivity fails: a score cannot compare equal to its own reference.
  4. Consistency fails: repeating one pair gives a different result each time.
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

record Stamp(String code, int batch) {} uses generated equality and hashing. A set receives new Stamp("R", 4), a separate new Stamp("R", 4), then new Stamp("R", 5). How many entries does it contain?
  1. 1
  2. 3
  3. 2
  4. 0
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A task app needs a new command to return the longest stored duration. What implementation order supports direct checking before console interaction is added?
  1. Read and print durations in the UI, then copy that code into the logic.
  2. Add a logic query, check known durations, then connect the UI command.
  3. Add console prompts to the logic query, then call it from every command.
  4. Change the private list to public, then let the UI search it directly.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A value class uses Double.compare(level, other.level) == 0 in equals. A proposed hash method always returns 1. Another returns different codes for different objects whose levels are both NaN. How do these proposals compare with the equality contract?
  1. The constant is illegal; different codes for NaN objects are required.
  2. Both are legal because different objects may have any hash codes.
  3. The constant is legal; different codes for equal NaN values are illegal.
  4. Both are illegal because every double value needs a unique code.
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

61 Key Terms, each with its definition

A

Aggregate
An aggregate is a combined statistic such as sum, count or maximum that summarizes values while losing some information about them.
The same aggregates can describe lists with different counts above a threshold, so that query needs more data.
Alias
An alias is another variable or reference that reaches the same object, giving multiple routes to one shared state.
After Box second = first;, second is an alias and its setter changes the box reached by first.
Argument
An argument is a value supplied at a call site to initialize a matching parameter in the called method or constructor.
In gate.allowIn(ana), the argument is the reference value read from ana, which the parameter copies.
ArrayList
An ArrayList is a resizable list object with ordered entries; object entries are references, so adding one does not copy its fields.
An ArrayList<Parcel> can hold three parcel references and let a loop add their weights in kilograms.
Autoboxing
Autoboxing is Java's automatic conversion of a primitive value to the corresponding wrapper reference during an assignment or call.
Autoboxing lets numbers.add(12) pass an Integer value when numbers is an ArrayList<Integer>.

C

Collection query
A collection query selects or computes information from stored elements, often returning a value, an object or a new result list.
The collection query songsLongerThan(200) builds a new list while retaining the matching song references.
Composition
Composition models a has-a relationship by giving an object fields that refer to other objects, such as a person's birthday.
Composition lets a course store its teacher as a person object and obtain the name through a method.
ConcurrentModificationException
A ConcurrentModificationException can signal that a collection was structurally changed during an iterator-based traversal.
Direct list removal during a for-each loop can cause ConcurrentModificationException before traversal finishes.
Consistency
Consistency requires repeated equality checks to agree while the compared information stays unchanged, so results remain reliable.
Consistency allows a result to change after an equality field changes, but not while the compared data is fixed.
Constructor injection
Constructor injection supplies an object's collaborators as constructor arguments, allowing it to use shared existing objects.
Constructor injection gives a UI one scanner and one log instead of creating a separate log for each command.
Content equality
Content equality means that the fields chosen by a class's equality rule match, even when the objects have different identities.
A book catalog can define content equality using title and year while ignoring the current borrower.
Copy method
A copy method returns a new object initialized from an existing object's data, providing the independence promised by its design.
A counter's copy method uses new Counter(value) so increasing the copy leaves the original unchanged.

D

Data validation
Data validation checks whether a supplied value meets the application's rules before it is accepted into the stored state.
Data validation in a duration log can reject a negative value regardless of which interface calls it.
Default field value
A default field value is the initial value Java supplies to a field, such as null for references or false for booleans.
The default field value of an uninitialized ArrayList<Song> field is null, so no list exists yet.
Defensive copy
A defensive copy is a separate object created to prevent outside code from changing protected state through a shared mutable reference.
A birthday getter returns a defensive copy so a caller's date setter leaves the person's birthday intact.
Delegation
Delegation means asking another object to perform work through its methods, keeping a rule with the object that owns its data.
A person uses delegation when olderThan calls birthday.before(other.birthday) for its date comparison.
Direct check
A direct check calls logic methods with known inputs and compares their results with expected answers without going through a user interface.
A direct check can add four readings and verify the average without reading any command text.

E

Encapsulation
Encapsulation keeps state behind a class's methods so the class can control changes and enforce rules on its stored data.
Playlist encapsulation is weakened if a getter returns the private list and callers can clear it directly.
Equality fields
Equality fields are the data selected to define when two instances represent the same value for a program's purpose.
Title and year can be equality fields for books, so changing only a borrower leaves that equality unchanged.

F

Factory method
A factory method supplies an object to its caller, either by creating a new instance or by reusing one that already exists.
A ticket machine's factory method issue() constructs a ticket with its next number and returns it.
Final reference field
A final reference field cannot be assigned a different reference after initialization, though its referenced object may still be mutable.
A final reference field holding a list prevents replacing the list, but does not stop calls to add.
For-each loop
A for-each loop visits the elements of a collection in turn, assigning each element value to a loop variable for processing.
A for-each loop over parcels can add each getWeight() result without changing list membership.

G

Garbage collection
Garbage collection is Java's automatic reclamation of memory from unreachable objects, at a time chosen by its runtime.
A counter becomes eligible for garbage collection after the running program loses its last path to it.

H

Hash code
A hash code is an integer used to narrow candidates in a hash-based collection; equal objects must produce equal codes.
A matching book hash code can be computed with Objects.hash(title, year) when those fields define equality.
Hash collision
A hash collision occurs when unequal objects produce the same hash code, which is allowed because equality still confirms a match.
A hash collision does not merge distinct values in a set when equals correctly distinguishes them.
HashMap key
A HashMap key identifies an associated value through hashing and equality; its equality-related state should stay stable during storage.
A HashMap key built from title and year can retrieve a shelf label using a separate equal book object.
HashSet
A HashSet stores distinct elements by using their hash codes and equality, so adding an equal value does not add another entry.
A HashSet<Book> retains one entry for two separate equal books when their equality and hashes agree.

I

Identity
Identity means being the same object, tested by == for two references; matching field values alone do not establish it.
Two separately constructed books can have equal titles and years while still having different identities.
Immutability
Immutability means that an object's state cannot change after construction, so sharing that object does not allow shared mutation.
String immutability means toUpperCase() cannot change the characters in the original string.

L

List membership
List membership describes which entries a list holds; changing membership is separate from changing the fields of a stored object.
Removing a song changes one list's membership, while another playlist can still refer to that same song.

M

Mutable object
A mutable object permits changes to its state after construction, so aliases can observe changes made through another reference.
A date with setters is a mutable object; sharing it between two people can change both stored birthdays.
Mutation
A mutation changes an existing object's state, making the changed field values visible through all references to that object.
Calling parcel.setWeight(3.5) is a mutation that a list holding that parcel can also observe.

N

Null reference
A null reference is the value null, which leads to no object and can be held by a reference variable but not a primitive variable.
A null reference in String name cannot receive length(), while an empty string can return length 0.
NullPointerException
A NullPointerException is a runtime failure caused by operations needing a missing object, such as an instance call or unboxing through null.
The call name.length() throws NullPointerException when the name variable holds null.

O

Object field
An object field is a field of reference type that connects its owner to another object or holds null when no object is present.
The object field birthday in Person can lead to a SimpleDate that performs date comparisons.
Overload
An overload is another method with the same name but different parameter types; it does not replace the original declaration.
Writing equals(Book other) creates an overload while leaving equals(Object) available to collections.
Override
An override replaces an inherited instance method's behavior with a matching declaration, such as equals(Object) in a new class.
The @Override annotation lets the compiler check that a book's equality method is a valid override.
Owner object
An owner object manages state through its own methods, such as a playlist that controls additions to its private song list.
The playlist is the owner object responsible for enforcing a maximum count in its add method.

P

Parameter
A parameter is a variable declared by a method or constructor that receives the copied value of an argument when a call runs.
In allowIn(Person visitor), the parameter visitor receives the caller's supplied person reference.
Pass-by-value
Pass-by-value gives a method parameter its own copy of an argument's value, including a copied reference for an object argument.
Pass-by-value lets a method redirect its person parameter without redirecting the caller's variable.
Primitive type
A primitive type stores ordinary data directly, such as a number or truth value; Java has eight primitive types.
The primitive type int lets count hold 12 directly, so assigning it to another variable copies 12.
Program logic
Program logic stores data and applies the application's rules, exposing methods that can work independently of console interaction.
Program logic computes the temperature average as a number that a console or web interface can display.
Public contract
A public contract is the promised behavior callers rely on when using a class's public methods, independent of its private storage.
A storage change respects the public contract only if every supported temperature query still works.

R

Reachability
Reachability means that the running program can follow a usable chain of references to an object, possibly through fields or lists.
A list preserves a parcel's reachability even after the local variable that originally held it becomes null.
Reassignment
A reassignment stores a new value in an existing variable; for a reference variable, it changes which object the variable reaches.
The reassignment current = new Counter(); redirects current while other aliases keep the old counter.
Receiver
The receiver is the object on which an instance method is called; inside that method, this refers to that object.
In earlier.before(later), the receiver is earlier; the other date is supplied as the argument.
Record
A record is a Java data class with final component fields and generated accessors, equality, hashing and string representation.
The record Book(String title, int year) compares both components and provides title() and year() accessors.
Reference
A reference is a value that leads to an object; copying it gives another variable a route to that same object.
The reference assigned by Counter second = first; lets both variables reach the original counter.
Reference type
A reference type is a class, array or interface type whose variables can hold references to objects or the value null.
String is a reference type, so a variable can refer to a text object or hold no object reference.
Reflexivity
Reflexivity requires every non-null object to compare equal to itself, so a valid equality method returns true for x.equals(x).
An initial this == other check in equals accepts reflexivity before comparing any field values.
Return value
A return value is the value a method passes back to its caller; for an object result, it is a reference or null.
The return value of a register's findByName can lead to a stored person that the caller and the register then share.

S

Search result
A search result is the matching value returned by a query; an object search can return the stored reference or null for no match.
The search result for a name can be a person stored in a register, allowing updates through the returned reference.
Shallow copy
A shallow copy creates a separate outer container but copies its element references, leaving the contained objects shared.
A shallow copy of a song list can lose an entry independently while still sharing each mutable song.
Short-circuit evaluation
Short-circuit evaluation skips the right side of && when its left side is false, allowing a prior check to protect a later call.
Short-circuit evaluation makes name != null && name.length() > 0 skip the method call for null.
String interning
String interning shares string objects for matching literals, allowing two identical literal expressions to have the same reference.
String interning can make "oak" == "oak" true, hiding a bug that appears when equal text comes from input.
Symmetry
Symmetry requires that reversing two non-null objects in an equality comparison preserves the true or false result.
Symmetry fails if a book accepts its title string as equal but the string rejects the book object.

T

Transitivity
Transitivity requires that when one object equals a second and the second equals a third, the first also equals the third.
Transitivity makes three books with the same equality fields belong to the same group of equal values.

U

Unboxing
Unboxing converts a wrapper object's stored value to its primitive type; it fails with NullPointerException for a null wrapper.
Unboxing occurs in int count = boxed;, so boxed must refer to an actual Integer object.
User interface
A user interface is the program code that interacts with the user, such as a command loop that reads text and prints results.
A user interface reads a temperature, calls a temperature log's add method and displays any rejection message.

W

Wrapper cache
A wrapper cache reuses wrapper objects for selected values, making reference identity depend on reuse instead of numeric equality.
The Integer wrapper cache includes small values, so use equals instead of relying on wrapper ==.
Wrapper class
A wrapper class represents a primitive value as an object, allowing values such as integers to be used where reference types are needed.
The wrapper class Integer supplies the element type for ArrayList<Integer>; primitive int cannot.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started