Java OOP Fundamentals
Mid-tier Java drills on interfaces vs abstract classes, the equals / hashCode contract, generics type erasure, and Comparable / Comparator. Concrete code stems you can compile.
999 views
29
Find the bug in Point.equals. Why does a HashSet<Point> end up containing both new Point(1, 2) instances?
Examples
Example 1:
Input: HashSet<Point> with two new Point(1, 2) instances
Output (buggy version, equals overridden but hashCode not): set.size() == 2
Output (fixed version with Objects.hash(x, y)): set.size() == 1
Explanation: The contract is a.equals(b) implies a.hashCode() == b.hashCode(). The default hashCode is identity-based, so two distinct instances land in different buckets and the HashSet stores both.import java.util.HashSet;
import java.util.Objects;
class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}
// TODO: override hashCode so a.equals(b) implies a.hashCode() == b.hashCode().
}
public class Main {
public static void main(String[] args) {
HashSet<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println(set.size());
}
}Given Java's type erasure, what happens if you replace List<?> with List<String> on the instanceof line below? Explain in one sentence, and state the canonical idiom.
Examples
Example 1:
Input: Object o = new ArrayList<String>(); check o instanceof List<String> vs o instanceof List<?>
Output: o instanceof List<String> does not compile; o instanceof List<?> compiles and returns true
Explanation: Generics are erased at runtime; List<String> and List<Integer> are both just List. Java forbids instanceof against a parameterized type. Use a wildcard. Java 16+ pattern matching lets you bind via if (o instanceof List<?> list).import java.util.List;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Object o = new ArrayList<String>();
// TODO: rewrite the instanceof line using Java 16+ pattern matching
// so that `list` is bound when the test passes.
if (o instanceof List<?>) {
System.out.println("yes");
}
}
}When should you pick an interface over an abstract class in Java 17? Give the two strongest reasons in each direction.
Sort List<Person> by lastName then firstName, using Comparator.comparing(...) chaining. Show the one-liner and explain why this beats hand-rolled Comparable.
Examples
Example 1:
Input: [(Ada, Lovelace), (Alan, Turing), (Ada, Byron)] sorted via Comparator.comparing(Person::lastName).thenComparing(Person::firstName)
Output: [(Ada, Byron), (Ada, Lovelace), (Alan, Turing)]
Explanation: Comparator factory methods compose declaratively in priority order. Hand-rolled Comparable forces a single canonical ordering, which is rarely what production code wants because views often need different sort keys.import java.util.List;
import java.util.ArrayList;
record Person(String firstName, String lastName) {}
public class Main {
public static void main(String[] args) {
List<Person> people = new ArrayList<>(List.of(
new Person("Ada", "Lovelace"),
new Person("Alan", "Turing"),
new Person("Ada", "Byron")
));
// TODO: sort by lastName then firstName using Comparator.comparing(...).thenComparing(...).
people.forEach(System.out::println);
}
}