INFO1113 Semester 1

Student Questions

From time to time, students may ask me questions that I’m not sure about. If that is the case, I’ll write the question in here, and find an answer. Students can then refer to this to get an answer. Alternatively, students can also request answers to questions on ed.

Tom’s Ed Public Workspaces

My Ed public workspaces are where I will generally write the code during the labs so that you can follow along.

General Assessment Feedback

Optional Explanation Videos

Blog Posts


Miscellaneous

Quick Introduction to Generics by Claude!

Generics let you write a class or method that works with any type, while still being type-safe.

**The Problem Without Generics:**

// Without generics — you'd need a separate class for each type
public class IntBox {
    private int value;
    public int get() { return value; }
}

public class StringBox {
    private String value;
    public String get() { return value; }
}

That’s a lot of repetition for the same idea!

**The Solution — Generics**

// One class that works for ANY type
public class Box<T> {
//              ↑ T is a placeholder for whatever type you want
    private T value;
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

Now you can use it with any type:

Box<Integer> intBox    = new Box<>();
Box<String>  stringBox = new Box<>();
Box<Alien>   alienBox  = new Box<>();

**Why Not Just Use `Object`?**

// You could do this...
public class Box {
    private Object value;
    public Object get() { return value; }
}

Box box = new Box();
box.set("hello");
String s = (String) box.get(); // ❌ need to cast manually
                               // ❌ could crash at runtime if wrong type

Generics catch type errors at compile time instead of crashing at runtime.

Note: The T is just a convention, but it can be any letter.



Quick Introduction to Modulo by Claude!

The modulo operator (%) gives you the remainder after division.

For example, 10 % 3 = 1 because 10 ÷ 3 = 3 with 1 left over.

Think of it like splitting items into groups: if you have 10 cookies and want to share them equally among 3 friends, each gets 3 cookies — and you’re left holding 1. That leftover is your modulo result.

A few quick examples:

8 % 2 = 0 — 8 divides evenly, nothing left over 7 % 4 = 3 — 4 goes into 7 once, with 3 remaining 5 % 10 = 5 — 10 is bigger than 5, so the whole thing is the remainder Where you’ll actually use it:

Checking even/odd: if n % 2 == 0, the number is even

Wrapping around: keeping a value within a range (e.g., clock hours cycling 0–23)

Every nth item: running code every 5 iterations with if i % 5 == 0

The key insight: when the result is 0, the numbers divide perfectly. Any other result tells you what’s left over.



Quick Introduction to Wildcards in Java Generics by Claude!

In Java, even though Integer extends Number, a List<Integer> is not a List<Number>. Generic types do not inherit the same subtype relationships as their type parameters. Wildcards are how we get flexibility back.

**Upper-bounded: `? extends B`**

B is the ceiling. The unknown type must be B or a subtype of B. For example, if B is Number:

  • Object ❌ — above the ceiling
  • Number ✅ — the ceiling itself
  • Integer ✅ — Integer extends Number
  • Float ✅ — Float extends Number
  • Double ✅ — Double extends Number
List<? extends Number> l;
l = new ArrayList<Integer>(); // ✅ Integer extends Number
l = new ArrayList<Double>();  // ✅ Double extends Number
l = new ArrayList<Object>();  // ❌ Object is above the ceiling

Use this when you want to read from a structure — you know everything in it is at least a B.

**Lower-bounded: `? super A`**

A is the floor. The unknown type must be A or a supertype of A. For example, if A is Integer:

  • Object ✅ — Object is a supertype of Integer
  • Number ✅ — Number is a supertype of Integer
  • Integer ✅ — Integer is a supertype of itself (the floor)
  • Double ❌ — Double is a sibling, not a supertype
List<? super Integer> l;
l = new ArrayList<Integer>(); // ✅ Integer is the floor
l = new ArrayList<Number>();  // ✅ Number is a supertype of Integer
l = new ArrayList<Object>();  // ✅ Object is a supertype of Integer
l = new ArrayList<Double>();  // ❌ Double is a sibling, not a supertype

Use this when you want to write into a structure — you know it can hold at least an A.

**A real example — `Collections.sort()`**

public static <T> void sort(List<T> list, Comparator<? super T> c)

If you sort a List<Integer>, then T = Integer. The comparator just needs to be capable of comparing Integers — which any of these can do:

Comparator<Integer> c1 = ...; // ✅ Integer is the floor
Comparator<Number>  c2 = ...; // ✅ Number is a supertype of Integer
Comparator<Object>  c3 = ...; // ✅ Object is a supertype of Integer

Restricting to only Comparator<Integer> would be unnecessarily strict — c2 and c3 are perfectly capable of comparing Integers, so ? super Integer lets all three through.



Why ? extends is Read-Only and ? super is Restricted-Write (Claude Explanation)

⚠️ Updated: earlier versions of this note oversimplified ? super as “write-only / writing is safe.” That’s misleading — writes are only safe for the bound type (Integer) and its subtypes, not for arbitrary supertypes. See the ? super Integer section for the corrected reasoning.

The setup: Java generics are invariant

List<Integer> is not a subtype of List<Number>, even though Integer is a subtype of Number:

List<Integer> ints = new ArrayList<>();
List<Number> nums = ints;   // ✗ compile error!

Wildcards (? extends and ? super) are the escape hatch. They let one method work with whole families of related types — but each comes with a restriction. Those restrictions aren’t arbitrary; they’re the only rules under which the compiler can keep your code type-safe.

The class hierarchy that drives everything

        Object
          ↑
        Number              ← abstract class
       ↑  ↑  ↑  ↑
  Integer Double Long Float ...
  • An Integer is-a Number (and is-an Object).
  • A Number is not necessarily an Integer — it might be a Double.

The “is-a” relationship only goes one way: up the hierarchy.

⚠️ Small wrinkle: the primitive int is not a Number — primitives aren’t objects, and you can’t write List<int> at all. But the wrapper class Integer is a Number. When you write list.add(42), Java auto-boxes the int into an Integer at the call site.

? extends Number — reads return the bound, writes are forbidden

Read this as: “a list of some specific subtype of Number, but I don’t know which one.”

The actual list might really be a List<Integer>, a List<Double>, a List<Long>, or just a List<Number> (every type is a subtype of itself). The compiler doesn’t know which.

Reading as the bound is safe ✓

Whatever the actual list is, every element is guaranteed to be at least a Number:

List<? extends Number> nums = someList;   // could be List<Integer>, List<Double>, ...
Number n = nums.get(0);                   // ✓ definitely a Number
Integer i = nums.get(0);                  // ✗ compile error — might be a Double

Number is the strongest type you get. Even if the underlying list is really a List<Integer>, the compiler will only let you read elements as Number — because from the wildcard alone it can’t tell.

Writing is forbidden ✗

List<? extends Number> nums = new ArrayList<Integer>();
nums.add(3.14);   // ✗ compile error — might corrupt a List<Integer>!

nums.add(42);     // ✗ rejected — compiler doesn't know the actual subtype;
                  //   could be List<Double>, List<Long>, etc., where Integer wouldn't fit

Even adding an Integer is rejected. The compiler doesn’t know what subtype is actually there — it just sees “some subtype of Number” — so it has to forbid all writes (except null, which fits everywhere).

Mental model: ? extends Number is open on the bottom — the element type could be any subtype of Number. Reads give you the bound (Number). Writes are forbidden because no specific value is guaranteed to fit every possible subtype.

? super Integer — writes accept the bound, reads return Object

Read this as: “a list of Integer or some supertype of Integer.”

The actual list might really be a List<Integer>, a List<Number>, or a List<Object>. Again, the compiler doesn’t know which.

Writing the bound (or a subtype) is safe ✓

An Integer is-an Integer, is-a Number, is-an Object — so an Integer fits into every possible underlying list type:

List<? super Integer> sink = someList;   // could be List<Integer>, List<Number>, List<Object>, ...
sink.add(42);   // ✓ Integer fits in any of those possibilities

Writing a supertype is forbidden ✗

This is the part it’s easy to get wrong. ? super Integer does not mean “you can write anything.” You can only write Integer (or a subtype of Integer, of which there are essentially none in practice).

List<? super Integer> sink = someList;
sink.add(1.5);              // ✗ compile error — Double doesn't fit if list is List<Integer>
sink.add(new Object());     // ✗ compile error — Object doesn't fit if list is List<Integer> or List<Number>
Number n = 3.14;
sink.add(n);                // ✗ compile error — same reason

You might think: “but if the underlying list is really a List<Number>, then 1.5 would be fine!” True — but the compiler doesn’t know whether the underlying list is List<Number> or List<Integer>. It has to assume the most restrictive possibility, which is List<Integer>. A Double doesn’t fit there, so the write is rejected.

The rule: for List<? super T>, you can write T or subtypes of T. Supertypes of T are rejected even when they’d happen to work for some underlying list types.

Reading is limited ✗

List<? super Integer> sink = new ArrayList<Object>();
Integer i = sink.get(0);   // ✗ compile error — best the compiler can promise is Object
Object  o = sink.get(0);   // ✓ everything is an Object

The compiler knows the element type is some supertype of IntegerInteger, Number, Object, etc. — but not which one. The most specific type it can guarantee for every possibility is Object, so sink.get(0) has static type Object. Assigning Object to Integer without a cast fails.

(Separately: if the underlying list really is a List<Object>, the value pulled out might genuinely not be an Integer — somebody could have put a String in there. But that’s a consequence of the same uncertainty, not the cause of the compile error.)

Mental model: ? super Integer is open on the top — the element type could be any supertype of Integer. Writes accept the bound (Integer) and below. Reads give you Object (the one type guaranteed to be a supertype of every possible element type).

The real symmetry

The “read-only vs write-only” slogan for PECS is catchy but loose. The accurate symmetry is:

  Reads Writes
? extends Number ✓ as Number (the bound) — anything more specific rejected ✗ forbidden (except null)
? super Integer ✓ as Object only Integer (the bound) and below — anything more general rejected

In both cases, the bound is the safe type, and the compiler refuses anything off the bound (more specific for ? extends reads, more general for ? super writes) because it would only work for some of the possible underlying list types — not all.

Why the restrictions aren’t arbitrary

The whole point of generics is compile-time type safety — catching bad assignments before runtime. The restrictions are exactly what the compiler needs to keep that guarantee:

  • If ? extends Number allowed writes → you could insert a Double into a List<Integer>.
  • If ? super Integer allowed writing supertypes → you could insert a Number into a List<Integer>.
  • If ? super Integer allowed reads as the bound → you could pull a Number out and assign it to an Integer without a cast.

The restrictions aren’t the language designers being awkward — they’re the only rules under which wildcards can be safe.