Weeks
JavaWeek 5 · Generics, Wildcards, Reflection and Type Erasure
Week 5 Notes

Java,
Week 5

Programming Concepts using Java, Week 5: polymorphism, generic programming, generics and subtyping with wildcards, reflection, and generics at run time (type erasure and wrapper classes). Every Java example in these notes was really compiled and run.

7 chapters21 worked examples~67 min read
PolymorphismGenericsWildcardsReflectionType erasureWrapper classesInterview prep

Chapters

  1. 01PolymorphismLecture 1
  2. 02Generics Programming in JavaLecture 2
  3. 03Java Generics and SubtypingLecture 3
  4. 04ReflectionLecture 4
  5. 05Java Generics at Run TimeLecture 5
  6. 06Interview Question BankInterview
  7. 07Week 5 Summary SheetSummary
01
Lecture 1

Polymorphism

Week 3 met polymorphism as dynamic dispatch. This lecture widens the word: polymorphism is also about code that works for any type that has the right capability. It then shows why simulating that with the Object class works, but only half well, which is the reason Java added generics.

1.1Two meanings of polymorphism

Inheritance polymorphism
  • S is a subclass of T, and S overrides a method f() defined in T.
  • A variable v of type T is assigned an object of type S.
  • v.f() uses S's definition: every object "knows" what it needs to do.
Structural polymorphism
  • Behaviour that depends only on specific capabilities of the values it works on.
  • Reverse an array or list: works for any type. Search for an element: needs an equality check. Sort: needs to compare values.
  • Java uses its class hierarchy to simulate this.
dynamic dispatchT f()S extends Toverrides f()T v = new S(); v.f()runs S's f()structural polymorphism: needs only a capabilityreverse(arr)any arraynothing morefind(arr, x)equality checkequals()sort(arr)compare two valuescmp() / Comparable
Figure 1. Left: the Week 3 meaning. Right: functions that need only a capability, the new meaning of this week

1.2Polymorphic reverse

Every Java array of objects is an Object[] (arrays are subtypes in the same direction as their elements, more on that in the next lecture). So one function, written against Object[], reverses an array of anything:

Main.java
public class Main {
    public static void reverse(Object[] arr) {
        int n = arr.length;

        for (int i = 0; i < n / 2; ++i) {
            Object temp = arr[i];
            arr[i] = arr[(n - 1) - i];
            arr[(n - 1) - i] = temp;
        }
    }

    public static void main(String[] args) {
        String[] s = { "a", "b", "c", "d" };
        Integer[] n = { 1, 2, 3 };
        reverse(s);                       // a String[] is an Object[]
        reverse(n);                       // so is an Integer[]
        System.out.println(java.util.Arrays.toString(s));
        System.out.println(java.util.Arrays.toString(n));
    }
}
Output
[d, c, b, a]
[3, 2, 1]
Source slip (extra). The lecture's loop header is for(int i = 0, i < n/2; ++i) with a comma where the first semicolon belongs. The compiler reads the comma as the start of another declaration and gives up:
Main.java
public class Main {
    public static void reverse(Object[] arr) {
        int n = arr.length;

        for (int i = 0, i < n / 2; ++i) {
            Object temp = arr[i];
            arr[i] = arr[(n - 1) - i];
            arr[(n - 1) - i] = temp;
        }
    }

    public static void main(String[] args) { }
}
Error
Main.java:5: error: ';' expected
        for (int i = 0, i < n / 2; ++i) {
                         ^
Main.java:5: error: > or ',' expected
        for (int i = 0, i < n / 2; ++i) {
                              ^
2 errors

One limit: a function over Object[] takes arrays of objects only. An int[] is not an Object[], because int is a basic type, not an object (the wrapper classes of Lecture 5 are the way round that):

Main.java
public class Main {
    public static void reverse(Object[] arr) { }

    public static void main(String[] args) {
        int[] a = { 1, 2, 3 };
        reverse(a);
    }
}
Error
Main.java:6: error: incompatible types: int[] cannot be converted to Object[]
        reverse(a);
                ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

1.3Polymorphic find: == or equals?

To find an element we need an equality check. The lecture writes arr[i] == o and comments that "== translates to Object.equals()". That is not what Java does. On objects, == compares references: are these the very same object? Only equals() asks whether two objects are equal in state (Week 3, once a class overrides it). The difference matters as soon as the key is a separate object:

Main.java
public class Main {
    public static int find(Object[] arr, Object o) {
        for (int i = 0; i < arr.length; ++i) {
            if (arr[i] == o) return i;          // the lecture's test: == on references
        }
        return -1;
    }

    public static int findEq(Object[] arr, Object o) {
        for (int i = 0; i < arr.length; ++i) {
            if (arr[i].equals(o)) return i;     // the capability we really need
        }
        return -1;
    }

    public static void main(String[] args) {
        String[] names = { "Asha", "Ravi", new String("Meena") };
        Object key = new String("Ravi");        // equal to "Ravi", but a different object

        System.out.println(find(names, key));
        System.out.println(findEq(names, key));
    }
}
Output
-1
1

So the lecture's find misses "Ravi" (it returns -1), while the version that calls equals finds it at index 1. The lecture's version seems to work on string literals only because identical literals are stored once and shared, so the two sides really are the same object (extra):

Main.java
public class Main {
    public static int find(Object[] arr, Object o) {
        for (int i = 0; i < arr.length; ++i) {
            if (arr[i] == o) return i;
        }
        return -1;
    }

    public static void main(String[] args) {
        String[] names = { "Asha", "Ravi", "Meena" };
        System.out.println(find(names, "Ravi"));     // both literals: the very same object
    }
}
Output
1
Rule

In polymorphic search code, test with arr[i].equals(o), not ==. (Check arr[i] is not null first, or use java.util.Objects.equals.) The capability being used is equals.

1.4Polymorphic sort: use an interface

For sorting, interfaces capture the capability: the Comparable interface from Week 4 with its cmp method, and one quicksort(Comparable[] a) that compares with a[i].cmp(a[j]). Any class that implements Comparable can be sorted:

Main.java
public class Main {
    public static void main(String[] args) {
        Person[] p = { new Person("Ravi", 41), new Person("Asha", 29), new Person("Meena", 35) };
        SortFunctions.quicksort(p);
        System.out.println(java.util.Arrays.toString(p));
    }
}
Output
[Asha (29), Meena (35), Ravi (41)]

1.5Type consistency: copying one array into another

A polymorphic function to copy an array. The lecture asks for two things: the target array must be type compatible with the source, and type errors should be flagged at compile time.

  • With parameters Object[] src, Object[] target, copying dates into an array of employees must not be allowed.
  • More generally the source can be a subtype of the target (an ETicket is a Ticket) but the converse is illegal.
Ticket[]ETicket[]subtypearraycopy(etktarr, tktarr)source may be a subtype ✓arraycopy(tktarr, etktarr)the converse is illegal ✗(a Ticket is not an ETicket)
Figure 2. Copy from a subtype array to a supertype array: allowed. The other way round: the lecture calls it illegal

What really happens with Object[] parameters is different, and worth seeing. The allowed direction works:

Copy.java
public class Copy {
    public static void arraycopy(Object[] src, Object[] target) {
        int limit = Math.min(src.length, target.length);
        for (int i = 0; i < limit; ++i) {
            target[i] = src[i];
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Ticket[] tktarr = new Ticket[3];
        ETicket[] etktarr = { new ETicket(), new ETicket(), new ETicket() };

        Copy.arraycopy(etktarr, tktarr);     // source is a subtype of the target: allowed
        System.out.println(java.util.Arrays.toString(tktarr));
    }
}
Output
[ETicket, ETicket, ETicket]

Copying dates into employees compiles (both are Object[]), and fails at run time with an ArrayStoreException, as soon as a real element has to be stored:

Main.java
public class Main {
    public static void main(String[] args) {
        Date[] datearr = { new Date(), new Date() };
        Employee[] emparr = new Employee[10];

        Copy.arraycopy(datearr, emparr);     // compiles: both are Object[]
    }
}
Error
Exception in thread "main" java.lang.ArrayStoreException: Date
	at Copy.arraycopy(Copy.java:5)
	at Main.main(Main.java:6)

Notice the word real. The lecture's picture uses freshly created arrays, full of null, and copying null into any array is always fine, so that exact code runs without any error:

Main.java
public class Main {
    public static void main(String[] args) {
        Date[] datearr = new Date[10];        // ten null slots
        Employee[] emparr = new Employee[10];

        Copy.arraycopy(datearr, emparr);      // as in the lecture's picture
        System.out.println("no error: only nulls were copied");
    }
}
Output
no error: only nulls were copied

And the "illegal" direction (supertype array into subtype array) is also accepted by the compiler for Object[] parameters; it fails at run time the moment a plain Ticket meets an ETicket[] slot:

Main.java
public class Main {
    public static void main(String[] args) {
        Ticket[] tktarr = { new Ticket(), new Ticket() };
        ETicket[] etktarr = new ETicket[2];

        Copy.arraycopy(tktarr, etktarr);     // the lecture calls this illegal
    }
}
Error
Exception in thread "main" java.lang.ArrayStoreException: Ticket
	at Copy.arraycopy(Copy.java:5)
	at Main.main(Main.java:6)
So what do we learn?

With Object[] as the parameter type the compiler cannot enforce what the lecture wants; the check is left to run time. That is the motivation for the next lecture: type parameters that let the compiler see the relation between the two arrays.

1.6Polymorphic data structures

Arrays, lists and the like should allow arbitrary elements. A polymorphic list stores values of type Object: insert(Object newdata) in, Object head() out.

LinkedList (holds Object)TicketDateanything goes ininsert(Object newdata)head()returns anObject(Ticket)castmay failtwo problems: type information is lost (casts), and the list need not be homogeneous
Figure 3. A list of Object takes anything in and gives only Objects back

Problem 1: type information is lost, so casts are needed

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        Ticket t1, t2;

        t1 = new Ticket();
        list.insert(t1);
        t2 = (Ticket) (list.head());          // head() returns an Object: a cast is needed
        System.out.println(t2);
    }
}
Output
Ticket

Leave the cast out and the compiler objects, because head() only promises an Object:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        list.insert(new Ticket());
        Ticket t = list.head();
    }
}
Error
Main.java:5: error: incompatible types: Object cannot be converted to Ticket
        Ticket t = list.head();
                            ^
1 error

Problem 2: the list need not be homogeneous

Nothing stops a Date from going into a list meant for tickets. The mistake stays hidden until someone casts the wrong thing:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList list = new LinkedList();
        Ticket t = new Ticket();
        Date d = new Date();
        list.insert(t);
        list.insert(d);                       // nothing stops a Date going into a list of tickets

        Ticket first = (Ticket) list.head();  // the Date comes out first
    }
}
Error
Exception in thread "main" java.lang.ClassCastException: class Date cannot be cast to class Ticket (Date and Ticket are in unnamed module of loader 'app')
	at Main.main(Main.java:9)

1.7Generic programming in Java: the way out

  • Java added generic programming to address both problems.
  • Classes and functions can have type parameters: class LinearList<T> holds values of type T; public T head() returns a value of the same type T as the enclosing class.
  • We can describe subclass relationships between type variables: public <S extends T, T> void arraycopy(S[] src, T[] target).

1.8Lecture summary

  • Polymorphism usually means dynamic dispatch; more generally it means behaviour that depends only on specific capabilities (structural polymorphism).
  • Java simulates it with the class hierarchy: Object[] for reverse and find, an interface (Comparable) for sort.
  • Arrays and lists of Object lose type information (casts) and may be heterogeneous.
  • Generics are Java's answer.
Next · Lecture 2Generics Programming in Java
02
Lecture 2

Generics Programming in Java

Lecture 1 ended with a promise: classes and functions can take type parameters. This lecture shows how. A type variable lets the compiler see the relation between types (for example "the target array has the same element type as the source"), which the Object trick could not express.

2.1Generic methods: the type quantifier

Type quantifierA type quantifier <T> written before the return type reads as "for every type T ...". Inside the method T is an ordinary type name.
Main.java
public class Main {
    public static <T> void reverse(T[] arr) {
        T temp;
        int n = arr.length;

        for (int i = 0; i < n / 2; ++i) {
            temp = arr[i];
            arr[i] = arr[(n - 1) - i];
            arr[(n - 1) - i] = temp;
        }
    }

    public static void main(String[] args) {
        String[] s = { "a", "b", "c" };
        Integer[] n = { 1, 2, 3, 4 };
        reverse(s);                        // T is String here
        reverse(n);                        // T is Integer here
        System.out.println(java.util.Arrays.toString(s));
        System.out.println(java.util.Arrays.toString(n));
    }
}
Output
[c, b, a]
[4, 3, 2, 1]

Nothing is declared for T at the call. The compiler infers it from the argument: String for the first call, Integer for the second.

class LinkedList<T>T head() insert(T x)LinkedList<Ticket>Ticket head()LinkedList<Date>Date head()LinkedList<String>String head()one definition, many instantiations: the type is fixed by the code that creates the list
Figure 4. The same generic definition, instantiated differently by different callers

2.2Polymorphic find, and a claim that needs a correction

The lecture writes public <T> int find(T[] arr, T o) and says: "searching for a value of incompatible type is now a compile-time error". For arrays that is not what happens. The compiler is allowed to pick T as a common supertype of the two arguments, and String and Integer share supertypes (Object, Comparable, Serializable), so the call is accepted and simply finds nothing:

Main.java
public class Main {
    public static <T> int find(T[] arr, T o) {
        for (int i = 0; i < arr.length; ++i) {
            if (arr[i].equals(o)) return i;
        }
        return -1;
    }

    public static void main(String[] args) {
        String[] names = { "Asha", "Ravi", "Meena" };
        System.out.println(find(names, "Ravi"));
        System.out.println(find(names, 42));       // the lecture says this is a compile error
    }
}
Output
1
-1

The error does appear when you fix T yourself, or when the container is invariant (a List<T>, see Lecture 3), because then T cannot widen:

Main.java
public class Main {
    public static <T> int find(T[] arr, T o) {
        return -1;
    }

    public static void main(String[] args) {
        String[] names = { "Asha", "Ravi", "Meena" };
        System.out.println(Main.<String>find(names, 42));   // say T = String yourself
    }
}
Error
Main.java:8: error: incompatible types: int cannot be converted to String
        System.out.println(Main.<String>find(names, 42));   // say T = String yourself
                                                    ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Main.java
import java.util.List;

public class Main {
    public static <T> int find(List<T> list, T o) {
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i).equals(o)) return i;
        }
        return -1;
    }

    public static void main(String[] args) {
        List<String> names = List.of("Asha", "Ravi", "Meena");
        System.out.println(find(names, "Ravi"));
        System.out.println(find(names, 42));
    }
}
Error
Main.java:14: error: method find in class Main cannot be applied to given types;
        System.out.println(find(names, 42));
                           ^
  required: List<T>,T
  found:    List<String>,int
  reason: inference variable T has incompatible bounds
    equality constraints: String
    lower bounds: Integer
  where T is a type-variable:
    T extends Object declared in method <T>find(List<T>,T)
1 error
Source slip. The slide's find again tests with arr[i] == o. As in Lecture 1, use equals() to compare by value.

2.3Polymorphic arraycopy

Version 1: identical types (the slide's claim)

The slide: <T> arraycopy(T[] src, T[] target), "source and target types must be identical". Two things to know. First, the slide writes public <T> static void, and Java insists the quantifier comes after the modifiers, right before the return type:

Main.java
public class Main {
    public <T> static void arraycopy(T[] src, T[] target) { }

    public static void main(String[] args) { }
}
Error
Main.java:2: error: illegal start of type
    public <T> static void arraycopy(T[] src, T[] target) { }
               ^
1 error

The correct order is public static <T> void. Second, the types need not be identical. Because of the inference we just saw, T can become Object, and dates copy into employees without a compile error, failing at run time exactly as in Lecture 1:

Main.java
public class Main {
    public static <T> void arraycopy(T[] src, T[] target) {
        int limit = Math.min(src.length, target.length);
        for (int i = 0; i < limit; ++i) {
            target[i] = src[i];
        }
    }

    public static void main(String[] args) {
        Date[] datearr = { new Date() };
        Employee[] emparr = new Employee[1];
        arraycopy(datearr, emparr);           // compiles: T is inferred as Object
    }
}
Error
Exception in thread "main" java.lang.ArrayStoreException: Date
	at Main.arraycopy(Main.java:5)
	at Main.main(Main.java:12)

Version 2: a more generous copy, source extends target

Use two type variables and say how they relate: <S extends T, T> means "for every pair of types S and T where S is a subtype of T". The source array is an S[], the target a T[]:

Main.java
public class Main {
    public static <S extends T, T> void arraycopy(S[] src, T[] target) {
        int limit = Math.min(src.length, target.length);
        for (int i = 0; i < limit; ++i) {
            target[i] = src[i];
        }
    }

    public static void main(String[] args) {
        Ticket[] tktarr = new Ticket[2];
        ETicket[] etktarr = { new ETicket(), new ETicket() };

        arraycopy(etktarr, tktarr);           // S = ETicket extends T = Ticket: allowed
        System.out.println(java.util.Arrays.toString(tktarr));
    }
}
Output
[ETicket, ETicket]
Source slip. The slide declares <S extends T, T> but then writes the first parameter as T[] src, so S is never used and constrains nothing. It compiles, and the wrong-way call still goes through:
Main.java
public class Main {
    public static <S extends T, T> void arraycopy(T[] src, T[] target) {   // the slide: S is never used
        int limit = Math.min(src.length, target.length);
        for (int i = 0; i < limit; ++i) {
            target[i] = src[i];
        }
    }

    public static void main(String[] args) {
        Ticket[] tktarr = { new Ticket() };
        ETicket[] etktarr = new ETicket[1];
        arraycopy(tktarr, etktarr);           // compiles: nothing relates S to the arrays
    }
}
Error
Exception in thread "main" java.lang.ArrayStoreException: Ticket
	at Main.arraycopy(Main.java:5)
	at Main.main(Main.java:12)

The corrected form (S[] src) is the one above. Even so, with arrays the compiler does not fully stop the converse, because an ETicket[] is also a Ticket[] (covariance, Lecture 3), so T can simply be chosen as Ticket for both and the call compiles:

Main.java
public class Main {
    public static <S extends T, T> void arraycopy(S[] src, T[] target) {
        int limit = Math.min(src.length, target.length);
        for (int i = 0; i < limit; ++i) {
            target[i] = src[i];
        }
    }

    public static void main(String[] args) {
        Ticket[] tktarr = { new Ticket(), new Ticket() };
        ETicket[] etktarr = new ETicket[2];

        arraycopy(tktarr, etktarr);           // the wrong direction: does it compile?
    }
}
Error
Exception in thread "main" java.lang.ArrayStoreException: Ticket
	at Main.arraycopy(Main.java:5)
	at Main.main(Main.java:13)

With generic collections, which do not have this covariance, the same constraint is enforced at compile time, as the lecture intends:

Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static <S extends T, T> void copy(List<S> src, List<T> target) {
        for (S x : src) {
            target.add(x);
        }
    }

    public static void main(String[] args) {
        List<ETicket> etickets = new ArrayList<>();
        etickets.add(new ETicket());
        List<Ticket> tickets = new ArrayList<>();

        copy(etickets, tickets);              // ETicket extends Ticket: fine
        System.out.println(tickets);
    }
}
Output
[ETicket]
Main.java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static <S extends T, T> void copy(List<S> src, List<T> target) { }

    public static void main(String[] args) {
        List<Ticket> tickets = new ArrayList<>();
        List<ETicket> etickets = new ArrayList<>();

        copy(tickets, etickets);              // the wrong direction, now with Lists
    }
}
Error
Main.java:11: error: method copy in class Main cannot be applied to given types;
        copy(tickets, etickets);              // the wrong direction, now with Lists
        ^
  required: List<S>,List<T>
  found:    List<Ticket>,List<ETicket>
  reason: inference variable T has incompatible bounds
    equality constraints: ETicket
    lower bounds: S,Ticket
  where S,T are type-variables:
    S extends T declared in method <S,T>copy(List<S>,List<T>)
    T extends Object declared in method <S,T>copy(List<S>,List<T>)
1 error
Takeaway

Type variables describe relations between types. They give the compile-time guarantee in full only when the container types are invariant (collections); with arrays, a leftover run-time check (ArrayStoreException) still exists.

2.4Generic classes: a polymorphic list

Put the parameter on the class: class LinkedList<T>. Then T applies to the whole class. The T in the inner Node is the same T, and so are the return value of head() and the argument of insert(). The code uses the Iterator<T> interface from Week 4, made generic too:

LinkedList.java
public class LinkedList<T> {
    private int size;
    private Node first;

    public T head() {
        T returnval = null;
        if (first != null) {
            returnval = first.data;
            first = first.next;
            size--;
        }
        return returnval;
    }

    public void insert(T newdata) {
        Node n = new Node(newdata);
        n.next = first;
        first = n;
        size++;
    }

    public int size() {
        return size;
    }

    public Iterator<T> get_iterator() {
        return new Iter();
    }

    private class Node {
        private T data;                      // the same T as the class's
        private Node next;

        private Node(T data) {
            this.data = data;
        }
    }

    private class Iter implements Iterator<T> {
        private Node position = first;

        public boolean has_next() {
            return position != null;
        }

        public T get_next() {
            T d = position.data;
            position = position.next;
            return d;
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<Ticket> ticketList = new LinkedList<Ticket>();
        LinkedList<Date> dateList = new LinkedList<Date>();

        Ticket t = new Ticket();
        Date d = new Date();

        ticketList.insert(t);
        dateList.insert(d);

        Ticket back = ticketList.head();      // no cast: head() returns a Ticket
        System.out.println(back);
        System.out.println(dateList.head());
    }
}
Output
Ticket
Date

Both problems of Lecture 1 are gone: head() returns a Ticket with no cast, and a list is tied to one element type at the point where it is created. The word instantiate means giving the class a concrete type, as in new LinkedList<Ticket>(). Putting the wrong thing in is now a compile-time error:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<Ticket> ticketList = new LinkedList<Ticket>();
        ticketList.insert(new Date());        // a Date into a list of tickets
    }
}
Error
Main.java:4: error: incompatible types: Date cannot be converted to Ticket
        ticketList.insert(new Date());        // a Date into a list of tickets
                          ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Extra: the diamond. Since Java 7 you can write new LinkedList<>() and let the compiler fill in the type argument from the left-hand side. The lecture's long form is equally valid:
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<String> a = new LinkedList<String>();   // the lecture's form
        LinkedList<String> b = new LinkedList<>();         // (extra) the compiler fills in the type
        a.insert("x");
        b.insert("y");
        System.out.println(a.head() + b.head());
    }
}
Output
xy

2.5Careful: do not hide a type variable

Inside class LinkedList<T>, writing public <T> void insert(T newData) introduces a brand new T for this method. The quantifier <T> masks the class's T. The element type of the list and the argument of insert are now unrelated, and the compiler refuses to put one into a Node:

class LinkedList<T>private class Node {T data; // class Tpublic <T> void insert(T x)// a NEW T (method T)new Node(x) ✗ T#1 vs T#2The method quantifier <T> masks the class parameter T:inside insert, T no longer means the list's element type.fix: drop the <T> and write public void insert(T x)
Figure 5. The method's own T hides the class T, so the two no longer match
LinkedList.java
public class LinkedList<T> {
    private int size;
    private Node first;

    public <T> void insert(T newdata) {      // a NEW T: it hides the class's T
        Node n = new Node(newdata);
        n.next = first;
        first = n;
        size++;
    }

    private class Node {
        private T data;
        private Node next;

        private Node(T data) {
            this.data = data;
        }
    }
}
Error
LinkedList.java:6: error: incompatible types: T#1 cannot be converted to T#2
        Node n = new Node(newdata);
                          ^
  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method <T#1>insert(T#1)
    T#2 extends Object declared in class LinkedList
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

Read the error: T#1 is the method's variable, T#2 the class's. If nothing inside the method needed the class's T, the code would compile and the list would quietly accept anything, which defeats the purpose of the type parameter (extra):

LinkedList.java
public class LinkedList<T> {
    private Object first;
    private int count;

    public <T> void insert(T newdata) {      // hides the class's T
        first = newdata;
        count++;
    }

    public int count() { return count; }
}
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<String> names = new LinkedList<String>();
        names.insert("fine");
        names.insert(42);                    // no longer protected
        System.out.println(names.count());
    }
}
Output
2
DeclarationWhat it means
public <T> static void arraycopy(T[] a, T[] b)(modifier order fixed: public static <T>) a static method with its own T: correct, there is no class T to hide
class LinkedList<T> { public <T> void insert(T x) }the method hides the class's T: a bug
class LinkedList<T> { public void insert(T x) }uses the class's T: correct

2.6Extra: bounds on a single variable

extends works on one variable too. If the body needs a capability, say so in the bound: <T extends Comparable<T>> means "any T that can compare itself with another T". Anything else is rejected at compile time:

Main.java
public class Main {
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) >= 0 ? a : b;       // T must be able to compare itself
    }

    public static void main(String[] args) {
        System.out.println(max(3, 9));
        System.out.println(max("pear", "apple"));
        System.out.println(max(2.5, 1.5));
    }
}
Output
9
pear
2.5
Main.java
public class Main {
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) >= 0 ? a : b;
    }

    public static void main(String[] args) {
        Object x = max(new Object(), new Object());
    }
}
Error
Main.java:7: error: method max in class Main cannot be applied to given types;
        Object x = max(new Object(), new Object());
                   ^
  required: T,T
  found:    Object,Object
  reason: inference variable T has incompatible bounds
    upper bounds: Comparable<T>
    lower bounds: Object
  where T is a type-variable:
    T extends Comparable<T> declared in method <T>max(T,T)
1 error

2.7Lecture summary

  • Generics bring structural polymorphism into Java through type variables.
  • Classes and functions take type parameters: class LinearList<T> holds values of any one type T; T head() returns that same T.
  • Relations between type variables: <S extends T, T>.
  • Do not accidentally hide a class type variable with a method's <T>.
Next · Lecture 3Java Generics and Subtyping
03
Lecture 3

Java Generics and Subtyping

If String is a subtype of Object, is LinkedList<String> a subtype of LinkedList<Object>? Arrays say yes; generics say no. This lecture explains why, and the tools (generic methods and wildcards) that let one function accept lists of many element types safely.

3.1Arrays are covariant

CovarianceIf S extends T, then S[] extends T[]. This is called covariance: the array types vary in the same direction as their element types.

The assignment is fine, but it makes a hole. The variable ticketarr promises "an array of tickets", while the object behind it only accepts ETickets. Storing a plain Ticket passes the compiler and fails when the program runs:

Main.java
public class Main {
    public static void main(String[] args) {
        ETicket[] elecarr = new ETicket[10];
        Ticket[] ticketarr = elecarr;           // OK: ETicket[] is a subtype of Ticket[]
        System.out.println(ticketarr.length);

        ticketarr[5] = new Ticket();            // the compiler is happy ...
    }
}
Error
10
Exception in thread "main" java.lang.ArrayStoreException: Ticket
	at Main.main(Main.java:7)
Why Java arrays allow this

Every array object remembers its real element type, so Java can check each store at run time (that check throws ArrayStoreException). Covariance was kept because it makes code like reverse(Object[]) and arraycopy of Lecture 1 possible.

3.2Generic classes are not covariant

Generics close that hole by refusing the assignment altogether. LinkedList<String> is not compatible with LinkedList<Object>. Types with different arguments are unrelated ("invariant"):

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<String> strings = new LinkedList<String>();
        LinkedList<Object> objects = strings;
    }
}
Error
Main.java:4: error: incompatible types: LinkedList<String> cannot be converted to LinkedList<Object>
        LinkedList<Object> objects = strings;
                                     ^
1 error
arrays are covariantTicket[]ETicket[]is a Ticket[]generics are NOT covariantLinkedList<Object>LinkedList<String>not a subtype ✗String extends Object, but LinkedList<String> does not extend LinkedList<Object>
Figure 6. Arrays follow their element types; generic classes do not

Why is that sensible? If the assignment were allowed, you could write objects.insert(new Date()) through the LinkedList<Object> name while the real list holds only strings: the same hole as for arrays, but now with no run-time check to catch it (Lecture 5 explains why: the element type is erased).

3.3The problem: printing any list

We want one printList that works for a list of anything. Since everything is an Object, the obvious try is a parameter of type LinkedList<Object>. It does not accept a list of strings:

Main.java
public class Main {
    public static void printList(LinkedList<Object> l) {
        Iterator<Object> i = l.get_iterator();
        while (i.has_next()) {
            System.out.println(i.get_next());
        }
    }

    public static void main(String[] args) {
        LinkedList<String> names = new LinkedList<String>();
        names.insert("Asha");
        printList(names);
    }
}
Error
Main.java:12: error: incompatible types: LinkedList<String> cannot be converted to LinkedList<Object>
        printList(names);
                  ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Source note. The slides write the iterator as a raw Iterator i = l.get_iterator(). The examples here use Iterator<T> from the generic list of Lecture 2, which is the safer form of the same idea.

3.4Fix 1: a generic method

Introduce a type variable: <T> void printList(LinkedList<T> l) means "for every type T, take a LinkedList<T>". Every concrete list type now fits. The body never needs T itself: a variable of type Object is enough to walk the list.

Main.java
public class Main {
    public static <T> void printList(LinkedList<T> l) {
        Object o;
        Iterator<T> i = l.get_iterator();

        while (i.has_next()) {
            o = i.get_next();                    // T itself is never needed: Object is enough
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        LinkedList<String> names = new LinkedList<String>();
        names.insert("Asha");
        names.insert("Ravi");
        LinkedList<Integer> nums = new LinkedList<Integer>();
        nums.insert(7);

        printList(names);                        // T = String
        printList(nums);                         // T = Integer
    }
}
Output
Ravi
Asha
7

The output order is Ravi then Asha: this list inserts at the front, so the last inserted comes out first (Week 4 code).

3.5Fix 2: the wildcard ?

Wildcard? is a wildcard: "an arbitrary unknown type". Use it instead of a type variable whenever the type is not needed elsewhere in the method (not for the return type, not to declare locals).
Main.java
public class Main {
    public static void printList(LinkedList<?> l) {
        Object o;
        Iterator<?> i = l.get_iterator();

        while (i.has_next()) {
            o = i.get_next();
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        LinkedList<String> names = new LinkedList<String>();
        names.insert("Asha");
        LinkedList<Integer> nums = new LinkedList<Integer>();
        nums.insert(7);

        printList(names);
        printList(nums);
    }
}
Output
Asha
7

Same result as the generic method, with no quantifier to write. The compiler even knows that anything coming out of a ? list is at least an Object, which is why Object o = i.get_next() is legal.

Variables of a wildcard type, and the one restriction

You may declare a variable LinkedList<?> l and point it at a list of strings. But you must be careful about putting things in: the compiler cannot guarantee that the thing matches the unknown element type.

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<?> l = new LinkedList<String>();   // allowed: a variable of a wildcard type
        l.insert(new Object());                       // but we may not put things in
    }
}
Error
Main.java:4: error: incompatible types: Object cannot be converted to CAP#1
        l.insert(new Object());                       // but we may not put things in
                 ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
Source slip. The slide calls l.add(new Object()) but the class has insert (the slide's own list has no add either). The error is the same for either name: the "CAP#1" in the message is the compiler's name for the unknown type behind ?.

Two things are still allowed: inserting null (it belongs to every type) and reading, which gives you an Object:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<?> l = new LinkedList<String>();
        l.insert(null);                               // null belongs to every type
        Object o = l.head();                          // reading gives an Object
        System.out.println(o);
    }
}
Output
null

3.6Bounded wildcards

Suppose Circle, Square and Rectangle all extend Shape, which has draw() that every subclass overrides. We want drawAll to take a list of anything Shape-compatible. Put a bound on the wildcard: LinkedList<? extends Shape>.

LinkedList<?>LinkedList<? extends Shape>LinkedList<? super Circle>LinkedList<Circle>LinkedList<Square>LinkedList<Shape>LinkedList<Object>extends: read it as a Shape (producer) super: put a Circle in (consumer)
Figure 7. Which lists fit under which wildcard type
Source slip. The slide's drawAll has no parameter name (LinkedList<? extends Shape>)) and then declares Object o and calls o.draw(). An Object has no draw method, so this does not compile:
Main.java
public class Main {
    public static void drawAll(LinkedList<? extends Shape> l) {
        Object o;
        Iterator<? extends Shape> i = l.get_iterator();

        while (i.has_next()) {
            o = i.get_next();
            o.draw();                                 // the lecture's body
        }
    }

    public static void main(String[] args) { }
}
Error
Main.java:8: error: cannot find symbol
            o.draw();                                 // the lecture's body
             ^
  symbol:   method draw()
  location: variable o of type Object
1 error

That is exactly the point of the bound: with ? extends Shape the compiler knows every element is at least a Shape, so the local variable should be a Shape:

Main.java
public class Main {
    public static void drawAll(LinkedList<? extends Shape> l) {
        Iterator<? extends Shape> i = l.get_iterator();

        while (i.has_next()) {
            Shape s = i.get_next();                   // anything in the list is at least a Shape
            s.draw();
        }
    }

    public static void main(String[] args) {
        LinkedList<Circle> circles = new LinkedList<Circle>();
        circles.insert(new Circle());
        LinkedList<Square> squares = new LinkedList<Square>();
        squares.insert(new Square());

        drawAll(circles);
        drawAll(squares);
    }
}
Output
circle
square

3.7Copying a list: extends and super

The slide's goal is listcopy: copy every element of one list into another. The source must hold things that fit into the target. As written, the slide's header <? extends T, T> is not legal Java: a wildcard cannot be declared in the quantifier list.

Main.java
public class Main {
    public static <? extends T, T> void listcopy(LinkedList<?> src, LinkedList<T> tgt) { }

    public static void main(String[] args) { }
}
Error
Main.java:2: error: <identifier> expected
    public static <? extends T, T> void listcopy(LinkedList<?> src, LinkedList<T> tgt) { }
                   ^
Main.java:2: error: <identifier> expected
    public static <? extends T, T> void listcopy(LinkedList<?> src, LinkedList<T> tgt) { }
                              ^
Main.java:2: error: ';' expected
    public static <? extends T, T> void listcopy(LinkedList<?> src, LinkedList<T> tgt) { }
                                 ^
3 errors

Wildcards go inside the parameter types, and the variable they bound is declared normally. Here the bound is on the source list, and the loop inserts what it reads into the target. The second version reverses the constraint with super: the target may be a list of any supertype of T:

Main.java
public class Main {
    // reads from src (a producer of T's), writes to tgt
    public static <T> void listcopy(LinkedList<? extends T> src, LinkedList<T> tgt) {
        Iterator<? extends T> i = src.get_iterator();
        while (i.has_next()) {
            tgt.insert(i.get_next());
        }
    }

    // the constraint reversed with super: tgt may be a list of any supertype of T
    public static <T> void listcopy2(LinkedList<T> src, LinkedList<? super T> tgt) {
        Iterator<T> i = src.get_iterator();
        while (i.has_next()) {
            tgt.insert(i.get_next());
        }
    }

    public static void main(String[] args) {
        LinkedList<Circle> circles = new LinkedList<Circle>();
        circles.insert(new Circle());
        circles.insert(new Circle());

        LinkedList<Shape> shapes = new LinkedList<Shape>();
        listcopy(circles, shapes);                    // Circle extends Shape
        System.out.println(shapes.size());

        LinkedList<Object> things = new LinkedList<Object>();
        listcopy2(circles, things);                   // Object is a supertype of Circle
        System.out.println(things.size());
    }
}
Output
2
2

The wrong direction (shapes into a list of circles) is refused:

Main.java
public class Main {
    public static <T> void listcopy(LinkedList<? extends T> src, LinkedList<T> tgt) { }

    public static void main(String[] args) {
        LinkedList<Shape> shapes = new LinkedList<Shape>();
        LinkedList<Circle> circles = new LinkedList<Circle>();

        listcopy(shapes, circles);                    // a list of Shapes into a list of Circles
    }
}
Error
Main.java:8: error: method listcopy in class Main cannot be applied to given types;
        listcopy(shapes, circles);                    // a list of Shapes into a list of Circles
        ^
  required: LinkedList<? extends T>,LinkedList<T>
  found:    LinkedList<Shape>,LinkedList<Circle>
  reason: inference variable T has incompatible bounds
    equality constraints: Circle
    lower bounds: Shape
  where T is a type-variable:
    T extends Object declared in method <T>listcopy(LinkedList<? extends T>,LinkedList<T>)
1 error
Source slip. The slides' bodies call tgt.add(o) with an Object o; an Object cannot be inserted into a LinkedList<T>, and the second version writes into a LinkedList<?>, which Lecture 3.5 showed is not allowed. The versions above are the working forms.

3.8Extra: which wildcard when? "producer extends, consumer super"

You want to...UseWhy
read items out of the list? extends Tevery item is at least a T (producer)
put items into the list? super Ta T fits any supertype list (consumer)
only walk it, or call Object methods?no bound needed
Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<Circle> circles = new LinkedList<Circle>();
        circles.insert(new Circle());

        LinkedList<? extends Shape> producer = circles;   // good for reading
        Shape s = producer.head();                        // ok: it is at least a Shape
        s.draw();
        // producer.insert(new Circle());                  // not allowed: which kind of Shape list is it?

        LinkedList<? super Circle> consumer = new LinkedList<Shape>();   // good for writing
        consumer.insert(new Circle());                    // ok: a Circle fits any supertype list
        Object o = consumer.head();                       // reading only gives an Object
        System.out.println(o instanceof Circle);
    }
}
Output
circle
true

And you cannot write into a producer:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<? extends Shape> producer = new LinkedList<Circle>();
        producer.insert(new Circle());
    }
}
Error
Main.java:4: error: incompatible types: Circle cannot be converted to CAP#1
        producer.insert(new Circle());
                        ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Shape from capture of ? extends Shape
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

3.9Lecture summary

  • Java generics are not covariant, unlike arrays.
  • You cannot substitute Object for T to get the most general type.
  • Instead use a type quantifier <T> or the wildcard ?.
  • A wildcard fits wherever T is not required inside the function: not for the return type, not to declare local variables.
  • Wildcards can be bounded: LinkedList<? extends T> and LinkedList<? super T>.
Next · Lecture 4Reflection
04
Lecture 4

Reflection

So far a program only uses its classes. Reflection lets a program look at its own structure while it runs: which class is this object, which methods does the class have, call that one. Tools such as IDEs and debuggers are built on it.

4.1What reflection is

Wikipedia, as the lecture quotes it: reflection is "the ability of a process to examine, introspect, and modify its own structure". Two components:

Introspection
  • A program can observe, and so reason about, its own state.
  • Example: ask an object what class it belongs to.
Intercession
  • A program can modify its own execution state or change its own meaning.
  • Example: call a method or set a field chosen at run time.

4.2Introspection: which class is this object?

When the class is known in advance, instanceof is enough:

Main.java
public class Main {
    public static void main(String[] args) {
        Employee e = new Manager("Ravi", 2000, "Meena");
        if (e instanceof Manager) {                       // we knew the class in advance
            System.out.println("e is a Manager");
        }
        System.out.println(e instanceof Employee);         // a Manager is also an Employee
    }
}
Output
e is a Manager
true

Now the harder question: write classequal(o1, o2), true exactly when both objects belong to the same class. Two ideas fail:

  • instanceof: we would have to test against every class that exists, and that set is not even fixed.
  • A generic type variable: o1 instanceof T is syntactically disallowed (Lecture 5 explains why).

getClass()

Every object can say what its class is: getClass() returns an object of type Class that encodes the class information. Compare two such objects with ==:

Main.java
public class Main {
    public static boolean classequal(Object o1, Object o2) {
        return o1.getClass() == o2.getClass();            // java.lang: no import needed
    }

    public static void main(String[] args) {
        Employee a = new Employee("Asha", 1000);
        Employee b = new Employee("Meena", 1500);
        Employee m = new Manager("Ravi", 2000, "Meena");

        System.out.println(classequal(a, b));
        System.out.println(classequal(a, m));              // an Employee and a Manager
        System.out.println(classequal("x", "y"));
        System.out.println(m instanceof Employee);         // instanceof is true for subclasses ...
        System.out.println(m.getClass() == Employee.class); // ... getClass() is the exact class
    }
}
Output
true
false
true
true
false
instanceof vs getClass()

m instanceof Employee is true for a Manager (a Manager is an Employee), but m.getClass() == Employee.class is false: getClass() reports the exact class. Use instanceof for "is it this kind of thing", getClass() for "is it exactly this class".

Source slip. The first version of the slide writes import java.lang.reflect;, which is not a valid import (there is no class called reflect), and says "import package java.lang.reflect". Packages are imported with a star: import java.lang.reflect.*; (as the lecture's second version does), or by naming the class. For getClass() itself no import is needed, since it comes from java.lang.
Main.java
import java.lang.reflect;

public class Main {
    public static void main(String[] args) { }
}
Error
Main.java:1: error: cannot find symbol
import java.lang.reflect;
                ^
  symbol:   class reflect
  location: package java.lang
1 error

4.3The class Class

For each class the JVM has loaded, Java creates one object of type Class with the information about it. This is reification: representing an abstract idea (the class) in a concrete form (an object you can hold, pass and query). It is also why == works on class objects: there is exactly one per class.

Main.java
public class Main {
    public static void main(String[] args) {
        Employee e = new Manager("Ravi", 2000, "Meena");
        Class c = e.getClass();                            // an object of the class Class
        System.out.println(c.getName());
        System.out.println(c.getSuperclass().getName());
        System.out.println(c.getSuperclass().getSuperclass().getName());

        Employee other = new Manager("Asha", 1000, "Sam");
        System.out.println(c == other.getClass());         // one Class object per class
    }
}
Output
Manager
Employee
java.lang.Object
true
obja ManagergetClass()Class objectfor Manager(reified)Constructor[]getConstructors()Method[]getMethods()Field[]getFields()then: newInstance() invoke(obj, args) get(obj) / set(obj, v)Declared versions (getDeclaredFields...) also list private members.
Figure 8. An object leads to its Class object, which leads to constructors, methods and fields

Creating objects at run time

From a Class object we can build a new instance of that class: "make another object like this one". The lecture calls c.newInstance(). That method works, but has been deprecated since Java 9 (the compiler warns); the replacement goes through the no-argument constructor:

Main.java
public class Main {
    public static void main(String[] args) throws Exception {
        Plain p = new Plain();
        Class c = p.getClass();
        Object o = c.newInstance();                        // the lecture's call
        System.out.println(o);
    }
}
Output
a Plain made at run time
Compiler says
Note: Main.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Main.java
public class Main {
    public static void main(String[] args) throws Exception {
        Plain p = new Plain();
        Class<?> c = p.getClass();
        Object o = c.getDeclaredConstructor().newInstance();       // the modern spelling
        System.out.println(o);

        Object o2 = Class.forName("Plain").getDeclaredConstructor().newInstance();   // from a name
        System.out.println(o2);
    }
}
Output
a Plain made at run time
a Plain made at run time

Also the second form: a class can be found by its name as a string, so the name can come from a file, a menu, or the network. That is how plug-in systems load classes they never saw at compile time. (The lecture's one-liner is Class.forName("Manager").newInstance(); either way the class needs a constructor with no arguments, which is why the example class here is a plain one.)

If the name matches no class, the program stops with ClassNotFoundException:

Main.java
public class Main {
    public static void main(String[] args) throws Exception {
        Object o = Class.forName("NoSuchClass").getDeclaredConstructor().newInstance();
    }
}
Error
Exception in thread "main" java.lang.ClassNotFoundException: NoSuchClass
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	at java.base/java.lang.Class.forName0(Native Method)
	at java.base/java.lang.Class.forName(Class.java:423)
	at java.base/java.lang.Class.forName(Class.java:414)
	at Main.main(Main.java:3)
Extra: these calls throw checked exceptions. The slide's one-liners leave this out. forName and newInstance can throw checked exceptions, so a method that calls them must catch them or declare throws (Week 4's exceptions). Without that, the compiler refuses:
Main.java
public class Main {
    public static void main(String[] args) {
        Object o = Class.forName("Plain").newInstance();
    }
}
Error
Main.java:3: error: unreported exception ClassNotFoundException; must be caught or declared to be thrown
        Object o = Class.forName("Plain").newInstance();
                                ^
Main.java:3: error: unreported exception InstantiationException; must be caught or declared to be thrown
        Object o = Class.forName("Plain").newInstance();
                                                     ^
Note: Main.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
2 errors

4.4Constructors, methods and fields

From the Class object we can extract details about the class's constructors, methods and fields. Each of these has structure of its own:

KindClassDetails availableGet them with
ConstructorsConstructorargumentsgetConstructors()
MethodsMethodarguments and return typegetMethods()
FieldsFieldtypegetFields()
All threemodifiers: static, private ...

Each returns an array. This listing prints the constructor parameters, then the methods, then the fields of Employee (which has a private name, a protected salary and a public level):

Main.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Class<?> c = Employee.class;

        Constructor<?>[] constructors = c.getConstructors();      // public constructors
        for (int i = 0; i < constructors.length; ++i) {
            Class<?>[] params = constructors[i].getParameterTypes();
            System.out.println("constructor takes " + Arrays.toString(params));
        }

        Method[] methods = c.getDeclaredMethods();               // declared in Employee itself
        Arrays.sort(methods, (a, b) -> a.getName().compareTo(b.getName()));
        for (Method m : methods) {
            System.out.println("method " + m.getName() + " returns " + m.getReturnType() + " takes " + Arrays.toString(m.getParameterTypes()));
        }

        Field[] fields = c.getFields();                          // public fields only
        for (Field f : fields) {
            System.out.println("field " + f.getName() + " : " + f.getType());
        }
    }
}
Output
constructor takes [class java.lang.String, double]
method bonus returns double takes [float]
method getName returns class java.lang.String takes []
method secret returns void takes []
field level : int
  • The constructor list shows only the public constructors (getConstructors()).
  • The method list uses getDeclaredMethods() here, so it includes the private secret (more in 4.6). The lecture's getMethods() would list only public methods, but including those inherited from Object (toString, hashCode, wait ...).
  • The field list shows only the public field level (getFields()).
Main.java
public class Main {
    public static void main(String[] args) {
        Class<?> c = Employee.class;
        System.out.println(c.getDeclaredMethods().length);   // only what Employee itself declares
        System.out.println(c.getMethods().length);           // public ones, including those inherited from Object
    }
}
Output
3
11

That is 3 methods declared in Employee versus 11 public methods including the inherited ones. So never rely on a fixed index like the slide's methods[3]: the order is not specified. Look methods up by name and parameter types instead (next section).

4.5Intercession: invoke methods, read and write fields

  • method.invoke(obj, args): call that method on obj with the argument array args.
  • field.get(obj): read the field's value from obj.
  • field.set(obj, value): write it.
Main.java
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Employee e = new Employee("Asha", 1000);
        Employee m = new Manager("Ravi", 2000, "Meena");

        Method bonus = Employee.class.getMethod("bonus", float.class);   // find it by name and types
        Object[] args5 = { 5.0f };
        System.out.println(bonus.invoke(e, args5));          // Employee's version
        System.out.println(bonus.invoke(m, args5));          // dispatch still sees a Manager: 1.5 times

        Field level = Employee.class.getField("level");
        System.out.println(level.get(e));                    // read the field
        level.set(e, 3);                                     // write the field
        System.out.println(e.level);
    }
}
Output
50.0
150.0
1
3

Notice that invoking the Employee version of bonus on a Manager object still ran the Manager's code: 5% of 2000 is 100, times the Manager's 1.5, gives 150 (the Employee object gives 50 from salary 1000). invoke does ordinary dynamic dispatch.

4.6Reflection and security: private components

Can reflection see private methods and fields? The plain getters (getConstructors, getMethods, getFields) return only public members. Separate functions include the private ones:

  • getDeclaredConstructors(), getDeclaredMethods(), getDeclaredFields()

Seeing a private member and using it are two different things. The listing counts the fields both ways, then tries to read the private name:

Main.java
import java.lang.reflect.Field;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws Exception {
        Employee e = new Employee("Asha", 1000);
        Class<?> c = Employee.class;

        System.out.println(c.getFields().length);            // public fields: just level
        System.out.println(c.getDeclaredFields().length);    // all declared fields, private too

        Field name = c.getDeclaredField("name");
        System.out.println(name.get(e));
    }
}
Error
1
3
Exception in thread "main" java.lang.IllegalAccessException: class Main cannot access a member of class Employee with modifiers "private"
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:714)
	at java.base/java.lang.reflect.Field.checkAccess(Field.java:1156)
	at java.base/java.lang.reflect.Field.get(Field.java:441)
	at Main.main(Main.java:13)

Reading it is refused with IllegalAccessException. Access to private components can be restricted by external security policies (the lecture's point: this should not be open to every program). In ordinary code though, a program may explicitly lift the check on its own classes with setAccessible(true) (extra):

Main.java
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Employee e = new Employee("Asha", 1000);
        Field name = Employee.class.getDeclaredField("name");

        name.setAccessible(true);                            // ask the JVM to lift the access check
        System.out.println(name.get(e));
        name.set(e, "Changed");
        System.out.println(e.getName());
    }
}
Output
Asha
Changed

That works for your own classes. The JDK's internal classes are protected by the module system, which refuses even this:

Main.java
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Field f = String.class.getDeclaredField("value");     // the char storage inside java.lang.String
        f.setAccessible(true);                                 // allowed for your own classes, not for the JDK's
    }
}
Error
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessible: module java.base does not "opens java.lang" to unnamed module @15db9742
	at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:177)
	at Main.main(Main.java:6)

4.7Using reflection: BlueJ

The lecture's example is BlueJ, a beginner's Java environment. It defines and compiles classes, then lets you create objects, invoke methods and examine state for the compiled code. It does this through Java's reflective capabilities, so BlueJ need not keep its own "debugging" information about each class.

4.8Limitations of Java reflection

Java reflection can inspect and use classes but cannot create or modify classes at run time. This is not possible:

Main.java
public class Main {
    public static void main(String[] args) {
        Class c = new Class();
    }
}
Error
Main.java:3: error: constructor Class in class Class<T> cannot be applied to given types;
        Class c = new Class();
                  ^
  required: ClassLoader,Class
  found:    no arguments
  reason: actual and formal argument lists differ in length
  where T is a type-variable:
    T extends Object declared in class Class
1 error
  • An environment like BlueJ must invoke the Java compiler before it can use a new class.
  • Contrast with Python: a class XYZ: statement can be executed at run time. Other object-oriented languages such as Smalltalk let you redefine methods at run time.
Extra: precision. Libraries do exist that generate classes at run time (by writing the bytecode directly), but that is outside the standard reflection API the lecture is describing.

4.9Lecture summary

  • Reflection = introspection (observe own state) + intercession (modify behaviour).
  • getClass() gives the Class object; reification turns class information into data.
  • Class lets you create instances (newInstance, forName) and list constructors, methods and fields.
  • invoke, get and set use what was found.
  • getDeclared... includes private members; access may be restricted by policy.
  • Java cannot create or modify classes at run time.
Next · Lecture 5Java Generics at Run Time
05
Lecture 5

Java Generics at Run Time

Generics are checked by the compiler, but the running program does not see them: the type arguments are erased. That one fact explains a list of restrictions that look arbitrary until you see the cause. The lecture closes with the other consequence: generics work only with objects, so basic types need wrapper classes.

5.1Type erasure

Type erasureType erasure: Java does not keep every version of LinkedList<T> as a separate type. At run time all type variables are replaced by Object, or by the upper bound if there is one. There is a single class LinkedList.
LinkedList<Employee>LinkedList<Date>LinkedList<Shape>source code (the compiler erases the arguments)class LinkedListT becomes Object(or the upper bound)class LinkedListT becomes Object(or the upper bound)class LinkedListT becomes Object(or the upper bound)at run time: ONE classso getClass() == getClass() is true, and the type argument cannot be tested
Figure 9. Three source-level types, one run-time class

The lecture writes the result as "LinkedList<T> becomes LinkedList<Object>" and "LinkedList<? extends Shape> becomes LinkedList<Shape>". That is a handy picture, but the precise statement is that the class itself loses its arguments (it is just LinkedList) and the type variable inside it is replaced by its bound: Object for a plain T, Shape for T extends Shape. A look through reflection confirms the bound:

Box.java
public class Box<T extends Shape> {
    private T item;
    public void put(T x) { item = x; }
    public T get() { return item; }
}
Main.java
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Method m = Box.class.getMethod("get");
        System.out.println(m.getReturnType().getName());          // erased to the upper bound
        Method p = Box.class.getMethod("put", Shape.class);
        System.out.println(p.getParameterTypes()[0].getName());
    }
}
Output
Shape
Shape

5.2What erasure forbids

1. instanceof with a parameterised type, or with T

Since no information about T is preserved, the program cannot ask whether an object is a list of strings:

Main.java
public class Main {
    public static void main(String[] args) {
        Object s = new LinkedList<String>();
        if (s instanceof LinkedList<String>) { }
    }
}
Error
Main.java:4: error: Object cannot be safely cast to LinkedList<String>
        if (s instanceof LinkedList<String>) { }
            ^
1 error
Main.java
public class Main {
    public static <T> void check(Object o) {
        if (o instanceof T) { }
    }

    public static void main(String[] args) { }
}
Error
Main.java:3: error: Object cannot be safely cast to T
        if (o instanceof T) { }
            ^
  where T is a type-variable:
    T extends Object declared in method <T>check(Object)
1 error

What is allowed (extra): ask about the class alone, with the wildcard or the raw name, which claims nothing about the element type:

Main.java
public class Main {
    public static void main(String[] args) {
        Object o = new LinkedList<String>();
        System.out.println(o instanceof LinkedList<?>);       // allowed: nothing is claimed about the element type
        System.out.println(o instanceof LinkedList);          // the raw class works too
    }
}
Output
true
true

2. Erasure and overloading

The comparison below is true: the two lists have the same run-time class (this is the lecture's test, with real classes).

Main.java
public class Main {
    public static void main(String[] args) {
        Object o1 = new LinkedList<Employee>();
        Object o2 = new LinkedList<Date>();

        if (o1.getClass() == o2.getClass()) {
            System.out.println("same class at run time");
        }
        System.out.println(o1.getClass().getName());
    }
}
Output
same class at run time
LinkedList

A consequence: two methods whose signatures differ only in the type argument have the same signature after erasure, so they cannot overload each other:

Example.java
public class Example {
    public void printlist(LinkedList<String> strList) { }
    public void printlist(LinkedList<Date> dateList) { }
}
Error
Example.java:3: error: name clash: printlist(LinkedList<Date>) and printlist(LinkedList<String>) have the same erasure
    public void printlist(LinkedList<Date> dateList) { }
                ^
1 error

3. Arrays and generics

Remember the array covariance hole from Lecture 3. To avoid something similar, Java lets you declare a generic array T[] newarray but not create one:

Box.java
public class Box<T> {
    private T[] items;

    public Box() {
        items = new T[100];                  // not allowed
    }
}
Error
Box.java:5: error: generic array creation
        items = new T[100];                  // not allowed
                ^
1 error

Why? An array must know its real element type to run the ArrayStoreException check, but T is gone at run time, so there is nothing to put in the array object.

The ugly workaround

Create an Object[] and cast it: (T[]) new Object[100]. It compiles with a warning, and works as long as the array stays private to the class:

Box.java
public class Box<T> {
    private T[] items;

    public Box() {
        items = (T[]) new Object[100];
    }
}
Compiler says
Note: Box.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Box.java
public class Box<T> {
    private T[] items;

    @SuppressWarnings("unchecked")
    public Box() {
        items = (T[]) new Object[100];       // the workaround: a cast, checked by nobody
    }

    public void put(int i, T x) { items[i] = x; }
    public T get(int i) { return items[i]; }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Box<String> b = new Box<String>();
        b.put(0, "hello");
        System.out.println(b.get(0));
    }
}
Output
hello

The warning is real. The cast is not checked at run time, so if the class ever hands the array out typed as T[], the caller's Integer[] view meets an Object[] and fails (extra):

Main.java
public class Main {
    public static void main(String[] args) {
        Box<Integer> b = new Box<Integer>();
        Integer[] a = b.raw();               // the compiler trusts T = Integer; at run time the array is Object[]
    }
}
Error
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Integer; ([Ljava.lang.Object; and [Ljava.lang.Integer; are in module java.base of loader 'bootstrap')
	at Main.main(Main.java:4)
Source slip. The slide's workaround starts with the garbled line T] newarray;, missing the opening bracket: it should read T[] newarray;.

5.3Wrapper classes

Because of erasure every type variable becomes Object at run time, and the basic types int, float, ... are not objects. So a type argument cannot be a basic type: no LinkedList<int>, no LinkedList<double>.

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<int> nums = new LinkedList<int>();
    }
}
Error
Main.java:3: error: unexpected type
        LinkedList<int> nums = new LinkedList<int>();
                   ^
  required: reference
  found:    int
Main.java:3: error: unexpected type
        LinkedList<int> nums = new LinkedList<int>();
                                              ^
  required: reference
  found:    int
2 errors

The fix: each basic type has a wrapper class that holds one value of that type as an object (Integer for int, Double for double, Boolean, Character, ...). All wrappers other than Boolean and Character extend the class Number:

int x = 5IntegerboxunboxNumberIntegerDoubleLongByte...BooleanCharacternot Numbersa generic type argument must be a reference type: LinkedList<Integer>, never LinkedList<int>
Figure 10. Boxing and unboxing, and the Number family
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Number.class.isAssignableFrom(Integer.class));
        System.out.println(Number.class.isAssignableFrom(Double.class));
        System.out.println(Number.class.isAssignableFrom(Boolean.class));
        System.out.println(Number.class.isAssignableFrom(Character.class));
    }
}
Output
true
true
false
false

Converting by hand

The slide shows Integer myx = Integer(x); to wrap and myx.intValue() to unwrap. The unwrap half is right (and so are byteValue(), doubleValue(), ...). The wrap half is not valid Java, because Integer(x) is not a method:

Main.java
public class Main {
    public static void main(String[] args) {
        int x = 5;
        Integer myx = Integer(x);
    }
}
Error
Main.java:4: error: cannot find symbol
        Integer myx = Integer(x);
                      ^
  symbol:   method Integer(int)
  location: class Main
1 error

The working spellings are Integer.valueOf(x) (preferred). new Integer(x) also compiles, but the constructor is deprecated for removal and the compiler warns:

Main.java
public class Main {
    public static void main(String[] args) {
        int x = 5;
        Integer myx = new Integer(x);
    }
}
Compiler says
Main.java:4: warning: [removal] Integer(int) in Integer has been deprecated and marked for removal
        Integer myx = new Integer(x);
                      ^
1 warning

Autoboxing

AutoboxingAutoboxing is the implicit conversion between a basic type and its wrapper: Integer myx = x; boxes, int y = myx; unboxes. The compiler inserts the valueOf and intValue calls for you.
Main.java
public class Main {
    public static void main(String[] args) {
        int x = 5;
        Integer myx = Integer.valueOf(x);       // basic type -> wrapper
        int y = myx.intValue();                 // wrapper -> basic type
        System.out.println(y + " " + myx.doubleValue());

        Integer a = x;                          // autoboxing: the compiler writes valueOf for us
        int b = a;                              // unboxing: and intValue here
        System.out.println(a + b);

        Number n = 3.5;                         // Double extends Number
        System.out.println(n.intValue());
    }
}
Output
5 5.0
10
3

So wrapper types are what you use in generic data structures, and autoboxing hides the conversion at the call site:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<Integer> nums = new LinkedList<Integer>();
        nums.insert(5);                 // 5 is boxed to an Integer
        nums.insert(7);
        nums.insert(9);

        int total = 0;
        Iterator<Integer> i = nums.get_iterator();
        while (i.has_next()) {
            total += i.get_next();      // each Integer is unboxed
        }
        System.out.println(total);
    }
}
Output
21

5.4Extra: two boxing traps

== on wrappers compares references

Main.java
public class Main {
    public static void main(String[] args) {
        Integer a = 127, b = 127;
        Integer c = 1000, d = 1000;
        System.out.println(a == b);             // small values are cached: the same object
        System.out.println(c == d);             // large ones are not: two objects
        System.out.println(c.equals(d));        // compare wrappers with equals
    }
}
Output
true
false
true

The first comparison is true only because small values (about -128 to 127) are cached and shared; larger ones are separate objects. Compare wrappers with equals(), exactly as in Lecture 1.

Unboxing null

Main.java
public class Main {
    public static void main(String[] args) {
        Integer n = null;
        int x = n;                              // unboxing null
    }
}
Error
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "<local1>" is null
	at Main.main(Main.java:4)

A wrapper variable can be null; an int cannot. Unboxing a null throws a NullPointerException.

5.5Extra: raw types, the back door

Erasure also explains how old, pre-generics code still works: the raw name LinkedList (no argument) is still legal. Through it you can bypass the element check; the compiler only warns. The mistake then surfaces later, at an innocent line where the compiler had inserted a cast:

Main.java
public class Main {
    public static void main(String[] args) {
        LinkedList<String> names = new LinkedList<String>();
        LinkedList raw = names;                  // a raw type: the type parameter is dropped
        raw.insert(42);                          // compiles (with a warning): nobody checks

        String s = names.head();                 // the compiler inserted a cast to String
    }
}
Error
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
	at Main.main(Main.java:7)
Compiler says
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

5.6Lecture summary

  • Information about type variables is erased at run time: LinkedList<T> becomes LinkedList<Object>, or the bound.
  • That limits reflection on generic types: you cannot write o instanceof LinkedList<String> or o instanceof T.
  • You cannot overload using different instantiations of one generic type.
  • You cannot instantiate arrays of a generic type.
  • Built-in types must be boxed in wrapper types to be used with generics.
Next · InterviewInterview Question Bank
06
Interview

Interview Question Bank

What is likely to be asked from Week 5, with 30-second answers, deeper explanations, a lecture-or-extra tag on each question, and code you can run.

Week 5 is the generics week, and interviewers love it: why generics exist, erasure, arrays versus generics, wildcards (extends and super), the "no generic arrays" rule, and the boxing traps. Every answer starts with a 30-second version. Each question is tagged From the lecture or Extra.

1Definition

One clear sentence: what it is.

2Tiny example

Three or four lines of code.

3The failure it prevents

What goes wrong without it.

6.1Interview heat-map: what is most likely

Very likely7 topics
What are generics and why were they added"What problem do generics solve?"Notes 1.6, 2.4
Type erasure"What is type erasure and what does it forbid?"Notes 5.1, 5.2
Arrays covariant, generics invariant"Why can I assign String[] to Object[] but not List<String> to List<Object>?"Notes 3.1, 3.2
extends vs super wildcards"When do you use ? extends T and when ? super T?"Notes 3.6, 3.7, 3.8
Why new T[] is not allowed"Why can't you create a generic array, and how do you work around it?"Notes 5.2
Wrapper classes and autoboxing"Why do generics need wrapper classes? What is autoboxing?"Notes 5.3
Reflection: what and why"What is reflection? Give a use."Notes 4.1-4.5
Likely8 topics
Generic method vs wildcard"Generic method or wildcard: which?"Notes 3.4, 3.5
Hiding a type variable"What is wrong with public <T> void insert(T x) inside class LinkedList<T>?"Notes 2.5
instanceof with generics"Can you write list instanceof List<String>?"Notes 5.2
Overloading and erasure"Why can't two methods differ only in List<String> and List<Date>?"Notes 5.2
Bounded type parameters"What does <T extends Comparable<T>> mean?"Notes 2.6
getClass() vs instanceof"What is the difference between getClass() == and instanceof?"Notes 4.2
Private members through reflection"Can reflection read a private field?"Notes 4.6
Structural vs inheritance polymorphism"What does polymorphism mean beyond dynamic dispatch?"Notes 1.1
Sometimes6 topics
== versus equals"Why does my search not find an equal string?"Notes 1.3
Raw types"What is a raw type and why avoid it?"Notes 5.5
Wrapper cache and =="Why is Integer 127 == 127 true but 1000 == 1000 false?"Notes 5.4
remove(int) vs remove(Object)"What does list.remove(1) do on a List<Integer>?"Notes 5.3
ArrayStoreException"When do you get ArrayStoreException?"Notes 1.5, 3.1
Class.forName and newInstance"How do you create an object from a class name in a string?"Notes 4.3

6.2Generics and subtyping

Interview 1What problem do generics solve?
Lectures 1-2Very likelyNotes 1.6, 2.4From the lecture

Think of a list of Object. What are the two problems?

Main.java
LinkedList list = new LinkedList();   // holds Object
list.insert(new Ticket());
list.insert(new Date());          // nothing stops this
Ticket t = (Ticket) list.head();  // ClassCastException here
Run time says
Exception in thread "main" java.lang.ClassCastException: class Date cannot be cast to class Ticket (Date and Ticket are in unnamed module of loader 'app')
Answer
Say this first (30 seconds)

Without generics a reusable container has to hold Object, which means casts on the way out and no control over what goes in: a Date can enter a list of tickets and the mistake explodes later as a ClassCastException. Generics add a type parameter (LinkedList<Ticket>), so the compiler rejects the wrong insert and no cast is needed.

Go deeper
  • The mistake moves from run time to compile time, where it is cheapest to fix.
  • It also lets one class or method describe a relation between types: <S extends T, T> arraycopy(S[], T[]).
Follow-up they may askDoes the type parameter exist at run time?
Interview 2Why can you assign a String[] to an Object[] but not a List<String> to a List<Object>?
Lecture 3Very likelyNotes 3.1, 3.2From the lecture

Think about what could then be stored through the wider name.

Run time says (array)
Exception in thread "main" java.lang.ArrayStoreException: Ticket
Compiler says (generic)
Main.java:4: error: incompatible types: LinkedList<String> cannot be converted to LinkedList<Object>
Answer
Say this first (30 seconds)

Arrays are covariant: S extends T makes S[] a T[]. That is unsafe (store a Ticket into what is really an ETicket[]), so Java patches it with a run-time check, ArrayStoreException. Generics are invariant: List<String> is not a List<Object>. Since erasure removes the element type at run time there is no such check, so the compiler must refuse at compile time.

Go deeper
  • Practical rule: prefer generic collections over arrays in new code; arrays defer the error to run time.
  • To accept lists of several element types use a wildcard: List<?> or List<? extends Shape>.
Follow-up they may askWhat is the wildcard for?
Interview 3When do you use ? extends T and when ? super T?
Lecture 3Very likelyNotes 3.6-3.8From the lecture

Think producer and consumer.

Main.java
static <T> void listcopy(LinkedList<? extends T> src,   // reads from src: extends
                          LinkedList<T> tgt) {
    ...
}
Compiler says (writing into an extends list)
Main.java:4: error: incompatible types: Circle cannot be converted to CAP#1
Answer
Say this first (30 seconds)

? extends T for a list you only read from (a producer of Ts: every element is at least a T). ? super T for a list you only write into (a consumer: it accepts a T because it is a list of some supertype). Plain ? when the element type does not matter. Short form: producer extends, consumer super.

Go deeper
  • You cannot add (except null) to an extends list: the compiler does not know which subtype list it really is.
  • Reading from a super list only gives Object.
  • The slides' headers like <? extends T, T> are not legal Java; the wildcard goes inside the parameter type.
Follow-up they may askWhere in the JDK have you seen this?
Interview 4Generic method or wildcard: how do you choose?
Lecture 3LikelyNotes 3.4, 3.5From the lecture

Is the type needed anywhere else in the method?

Main.java
static <T> void printList(LinkedList<T> l)   // T never used inside
static void printList(LinkedList<?> l)     // same, shorter
Answer
Say this first (30 seconds)

If the method needs to name the type (to relate two parameters, return it, or declare a local of that type) use a type variable <T>. If the element type is not needed elsewhere, use ?.

Go deeper
  • Example: <T> void copy(List<T> src, List<T> dst) needs T to say the two lists match; printList does not.
  • A wildcard is also what lets you declare a variable LinkedList<?> l of "some list".
Follow-up they may askWhy can't you insert into a LinkedList<?>?
Interview 5What is wrong with public <T> void insert(T x) inside class LinkedList<T>?
Lecture 2LikelyNotes 2.5From the lecture

How many different Ts are there?

LinkedList.java
public class LinkedList<T> {
    public <T> void insert(T newdata) {   // a NEW T
        Node n = new Node(newdata);        // Node wants the class T
    }
}
Compiler says
LinkedList.java:6: error: incompatible types: T#1 cannot be converted to T#2
Answer
Say this first (30 seconds)

The method's <T> declares a new type variable that hides the class's T. The argument type and the list's element type are now unrelated, so passing it to Node fails (T#1 cannot be converted to T#2). Just write public void insert(T x) and use the class's T.

Go deeper
  • Contrast with a static method, which has no class T in scope, so its own <T> is correct.
  • Worse case: if nothing inside needs the class's T, it compiles and quietly accepts anything.
Follow-up they may askWhy does a static generic method need its own <T>?
Interview 6What does <T extends Comparable<T>> mean, and why use it?
Lectures 1-2LikelyNotes 1.4, 2.6Extra

Lecture 1 sorted using an interface; this is the generic version.

Main.java
public static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}
Prints (max(3, 9), max("pear", "apple"), max(2.5, 1.5))
9  pear  2.5
Answer
Say this first (30 seconds)

It limits T to types that implement Comparable<T>, so the body may call compareTo. Anything without that capability is rejected at compile time. It is the generic way to say "sort or compare anything that can be compared" (structural polymorphism).

Go deeper
  • Erasure turns T into its bound Comparable, not Object (see 5.1).
Follow-up they may askWhy not just take Comparable parameters?

6.3Erasure and arrays

Interview 7What is type erasure, and what does it stop you from doing?
Lecture 5Very likelyNotes 5.1, 5.2From the lecture

Compare LinkedList<Employee> and LinkedList<Date> at run time.

Prints (a List<Employee> and a List<Date>)
same class at run time
LinkedList
Answer
Say this first (30 seconds)

The compiler checks generic types and then erases them: at run time there is only one class LinkedList, and a type variable becomes Object (or its upper bound). So the running program cannot tell a list of employees from a list of dates.

Go deeper
  • Cannot write o instanceof LinkedList<String> or o instanceof T.
  • Cannot overload f(LinkedList<String>) and f(LinkedList<Date>): same erasure.
  • Cannot create new T[n].
  • Type arguments must be reference types (no int).
  • Why erase at all? Compatibility with the huge amount of Java code written before generics existed (extra).
Follow-up they may askHow do you create a generic array then?
Interview 8Why can't you write new T[100], and what do you do instead?
Lecture 5Very likelyNotes 5.2From the lecture

An array has to know its element type at run time.

Compiler says
Box.java:5: error: generic array creation
Box.java
items = (T[]) new Object[100];   // warning: unchecked cast
Answer
Say this first (30 seconds)

An array carries its real element type at run time to enforce ArrayStoreException. T is erased, so there is nothing to put in the array object, and the compiler refuses (generic array creation). The workaround is (T[]) new Object[n]: it compiles with an unchecked warning, and is safe as long as the array never leaves the class.

Go deeper
  • If you hand the array out as T[], the caller's real Integer[] sees an Object[] and throws ClassCastException (try it in 5.2).
  • Alternative: use an ArrayList<T> inside, or take a Class<T> / array from the caller (extra).
Follow-up they may askWhy is declaring a T[] variable fine?
Interview 9Can you write o instanceof LinkedList<String>? And what can you write?
Lecture 5LikelyNotes 5.2From the lecture

What does the running program still know?

Compiler says
Main.java:4: error: Object cannot be safely cast to LinkedList<String>
Prints (instanceof LinkedList<?> and the raw name)
true  true
Answer
Say this first (30 seconds)

No: the compiler says Object cannot be safely cast to LinkedList<String>, because the String is erased and the check could not be done. You may test the class alone: o instanceof LinkedList<?> (or the raw LinkedList).

Go deeper
  • Same reason o instanceof T is rejected inside a generic method.
  • To check elements, test each one: for (Object x : list) if (x instanceof String).
Follow-up they may askIs there a way to keep the type at run time?
Interview 10Why can't you overload printlist(LinkedList<String>) and printlist(LinkedList<Date>)?
Lecture 5LikelyNotes 5.2From the lecture

Erase the type arguments and compare the signatures.

Example.java
public class Example {
    public void printlist(LinkedList<String> strList) { }
    public void printlist(LinkedList<Date> dateList) { }
}
Compiler says
error: name clash: printlist(LinkedList<Date>) and printlist(LinkedList<String>) have the same erasure
Answer
Say this first (30 seconds)

After erasure both signatures are printlist(LinkedList): the same. Overloading needs different signatures, so the compiler reports a name clash. Rename the methods, or make them one generic method.

Go deeper
  • The check happens at compile time, so you never reach a run-time ambiguity.
Follow-up they may askWould printlist(LinkedList<String>) and printlist(ArrayList<String>) clash?
Interview 11What is a raw type, and why should you avoid it?
Lecture 5SometimesNotes 5.5Extra

Erasure left the old name legal.

Main.java
LinkedList<String> names = new LinkedList<String>();
LinkedList raw = names;                  // a raw type: the type parameter is dropped
raw.insert(42);                          // compiles (with a warning): nobody checks

String s = names.head();                 // the compiler inserted a cast to String
Run time says
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
Answer
Say this first (30 seconds)

A raw type is a generic class used without its type argument (LinkedList instead of LinkedList<String>). It exists for old code. The compiler stops checking, so a wrong element goes in with only a warning, and the failure shows up later at a harmless-looking line.

Go deeper
  • Never ignore the "unchecked" compiler note: it is the compiler telling you a run-time check is missing.
Follow-up they may askHow does the compiler know to insert a cast at names.head()?
Interview 12When do you get an ArrayStoreException?
Lectures 1, 3SometimesNotes 1.5, 3.1From the lecture

The compiler allowed it. What did it not check?

Run time says (Date[] into Employee[])
Exception in thread "main" java.lang.ArrayStoreException: Date
Prints (the same, but with only nulls)
no error: only nulls were copied
Answer
Say this first (30 seconds)

When you store an object into an array whose real element type does not accept it, through a wider array type (e.g. a Date into an Employee[] reached as Object[]). The compiler sees only Object[]; the JVM checks at run time.

Go deeper
  • Only a real store triggers it: copying nulls is fine, so the lecture's picture runs cleanly.
  • Generic collections avoid it by refusing at compile time.
Follow-up they may askHow do arrays differ from generic collections here?

6.4Boxing and equality

Interview 13Why do generics need wrapper classes, and what is autoboxing?
Lecture 5Very likelyNotes 5.3From the lecture

Which types can a type argument be?

Compiler says (LinkedList<int>)
Main.java:3: error: unexpected type
Main.java
Integer a = 5;     // autoboxing: Integer.valueOf(5)
int b = a;         // unboxing:   a.intValue()
Answer
Say this first (30 seconds)

A type argument must be a reference type (erasure makes every type variable an Object), and int, double, ... are not objects. Each basic type has a wrapper class (Integer, Double, ...). Autoboxing is the automatic conversion between the basic value and its wrapper, so list.insert(5) just works.

Go deeper
  • All wrappers except Boolean and Character extend Number (intValue(), doubleValue() ...).
  • Cost: boxing allocates objects, so a List<Integer> is heavier than an int[] (extra).
  • The lecture's Integer(x) is not valid Java; use Integer.valueOf(x).
Follow-up they may askWhy is Integer c = 1000, d = 1000; c == d false?
Interview 14A search function with arr[i] == o misses an equal string. Why?
Lecture 1SometimesNotes 1.3From the lecture

The lecture says == translates to equals().

Prints (a == b, a == c, a.equals(c))
true  false  true
Prints (find with ==, find with equals)
-1  1
Answer
Say this first (30 seconds)

On objects == compares references (same object?). Two equal strings can be different objects. Use equals(). It seems to work for literals only because identical literals are shared.

Go deeper
  • The lecture's remark that == translates to Object.equals() is not what Java does.
Follow-up they may askWhat does the default Object.equals do?
Interview 15Why is Integer a = 127, b = 127; a == b true but the same with 1000 false?
Lecture 5SometimesNotes 5.4Extra

== on objects compares references.

Prints (127 == 127, 1000 == 1000, equals)
true  false  true
Answer
Say this first (30 seconds)

Autoboxing calls Integer.valueOf, which caches small values (-128 to 127) and hands out the same object; bigger values are new objects. == on wrappers compares references, so always compare wrappers with equals().

Go deeper
  • Unboxing a null wrapper throws NullPointerException.
Follow-up they may askWhat is the output of mixing int and Integer in ==?
Interview 16What does nums.remove(1) do on a List<Integer>?
Lecture 5SometimesNotes 5.3Extra

Two overloads: one takes an int, one an Object.

Main.java
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1);                     // index 1
nums.remove(Integer.valueOf(30));   // the value 30
Prints (after each remove)
[10, 30]  [10]
Answer
Say this first (30 seconds)

The compiler prefers the exact match, remove(int index): it removes the element at index 1 (here 20), not the value 1. To remove a value, pass an object: remove(Integer.valueOf(30)). Boxing happens only when no unboxed overload fits.

Go deeper
  • A classic trap where autoboxing changes which method is chosen.
Follow-up they may askHow would you remove all elements equal to 20?

6.5Reflection and polymorphism

Interview 17What is reflection in Java? Give an example of where it is used.
Lecture 4Very likelyNotes 4.1-4.5From the lecture

Introspection and intercession.

Prints (classequal examples)
true  false  true  true  false
Answer
Say this first (30 seconds)

Reflection lets a running program examine and use its own structure: obj.getClass() returns a Class object from which you can list constructors, methods and fields, create instances (newInstance), call methods (invoke) and read/write fields (get/set), even for classes unknown at compile time.

Go deeper
  • Uses: IDEs and tools like BlueJ, debuggers, serialisation and framework code that loads classes by name (extra: dependency injection, test runners).
  • Costs: slower than a direct call, bypasses compile-time checks (a wrong method name fails at run time), and can break encapsulation.
  • Limit: Java reflection cannot create or modify classes at run time.
Follow-up they may askDoes reflection respect private?
Interview 18What is the difference between a.getClass() == b.getClass() and a instanceof B?
Lecture 4LikelyNotes 4.2From the lecture

A Manager is an Employee.

Prints (true false true true false)
true  false  true  true  false
Answer
Say this first (30 seconds)

instanceof is true for the class and its subclasses. getClass() returns the exact class, so comparing two of them asks "same class, no subclasses". instanceof needs the class named in the source; getClass() works without knowing it in advance.

Go deeper
  • When writing equals() the choice matters: a getClass() test demands the exact same class, an instanceof test also accepts subclasses (extra).
Follow-up they may askWhat does Class.forName do?
Interview 19Can reflection read a private field? Is that a problem?
Lecture 4LikelyNotes 4.6From the lecture

Seeing a member is different from using it.

Prints (getFields().length, getDeclaredFields().length)
1  3
Run time says (reading name)
Exception in thread "main" java.lang.IllegalAccessException: class Main cannot access a member of class Employee with modifiers "private"
Prints (after setAccessible(true))
Asha  Changed
Answer
Say this first (30 seconds)

getFields() lists only public fields; getDeclaredFields() also lists private ones. Reading a private one fails with IllegalAccessException unless the code calls setAccessible(true), which is why access can be restricted by security policy. The module system in newer Java stops it for JDK internals.

Go deeper
  • So reflection can weaken encapsulation; frameworks and test tools use it deliberately, application code rarely should.
Follow-up they may askHow does the module system change this?
Interview 20Polymorphism usually means dynamic dispatch. What other meaning did this week add?
Lecture 1LikelyNotes 1.1From the lecture

Reverse, find and sort need different things from their elements.

Answer
Say this first (30 seconds)

Structural polymorphism: code whose behaviour depends only on specific capabilities of its inputs. Reverse needs nothing, find needs equality, sort needs comparison. Java expresses it with the class hierarchy (Object[]), with interfaces (Comparable) and, best, with generics.

Go deeper
  • Inheritance polymorphism: v.f() runs the definition from the actual object.
  • Search must compare with equals(), not == (the lecture slips here).
Follow-up they may askWhich interface expresses "can be compared"?
Interview 21How do you create an object when you only have the class name as a string?
Lecture 4SometimesNotes 4.3From the lecture

The class is not known at compile time.

Main.java
Object o = Class.forName("Plain").getDeclaredConstructor().newInstance();
Prints
a Plain made at run time
Run time says (unknown name)
Exception in thread "main" java.lang.ClassNotFoundException: NoSuchClass
Answer
Say this first (30 seconds)

Class.forName(name) finds the Class object; then create an instance with getDeclaredConstructor().newInstance() (the lecture uses the older newInstance(), deprecated since Java 9). The class needs a constructor with no arguments. An unknown name throws ClassNotFoundException.

Go deeper
  • These are checked exceptions: catch them or declare throws.
Follow-up they may askWhere does a plug-in system use this?
Next · SummaryWeek 5 Summary Sheet
07
Summary

Week 5 Summary Sheet

Week 5 on one page: a lecture-by-lecture glance, key terms, the pairs to tell apart, the errors you can now read, a checklist and corrections to the source notes.

7.1Week 5 at a glance

L1Polymorphism

Dynamic dispatch, and structural polymorphism (needs only a capability). Object[] for reverse and find (use equals), an interface for sort. Lists of Object lose types and may be mixed.

L2Generics

<T> before the return type; class LinkedList<T>; <S extends T, T>. A method's <T> hides the class's T.

L3Subtyping

Arrays are covariant (run-time check); generics are invariant. Use <T> or wildcards: ?, ? extends T (read), ? super T (write).

L4Reflection

getClass() gives a Class object (reification): constructors, methods, fields; newInstance, invoke, get/set. getDeclared... includes private. No class creation at run time.

L5Generics at run time

Erasure: T becomes Object or its bound. No instanceof with <String> or T, no overloading by instantiation, no new T[n], no <int>: use wrappers and autoboxing.

7.2Key terms, lecture by lecture

L1 · Polymorphism
structural polymorphism
Behaviour that depends only on specific capabilities.
Object[]
Any array of objects; used to simulate generic code.
ArrayStoreException
Run-time error: storing an object the array's real type refuses.
homogeneous
All elements of one type.
L2 · Generics
type parameter
A type given as a parameter: class LinkedList<T>.
type quantifier
<T> before a return type: "for every T".
bound
S extends T: a limit on a type variable.
instantiate
Give a generic class a concrete type argument.
L3 · Subtyping
covariant
Varies with the element type (arrays).
invariant
Different arguments, unrelated types (generics).
wildcard ?
An unknown type; reading gives Object.
bounded wildcard
? extends T, ? super T
L4 · Reflection
introspection
A program observes its own state.
intercession
A program modifies its own behaviour.
Class
The object that describes a class.
reification
Turning an idea (a class) into data.
L5 · Generics at run time
type erasure
Type arguments removed after compile-time checking.
wrapper class
Integer for int, and so on.
autoboxing
Automatic basic <-> wrapper conversion.
raw type
A generic class without type arguments (extra).

7.3The pairs you must tell apart

Arrayscovariant, checked at run time
VS
Genericsinvariant, checked at compile time
? extends Tread from it (producer)
VS
? super Twrite into it (consumer)
<T> void f(List<T>)type needed elsewhere
VS
void f(List<?>)type not needed
<T> insert(T x)hides the class T
VS
insert(T x)uses the class T
instanceofclass and subclasses
VS
getClass() ==exactly this class
getFields()public only
VS
getDeclaredFields()all declared, private too
==on objects: same object?
VS
equals()equal state?
new T[n]not allowed
VS
(T[]) new Object[n]unchecked workaround
LinkedList<int>not allowed
VS
LinkedList<Integer>boxing does the rest
LinkedList<String>compile-time type
VS
LinkedListthe only run-time class

7.4Messages and errors you can now read

What went wrongWhat you will seeWhen
basic array passed as Object[]error: incompatible types: int[] cannot be converted to Object[]compile time
missing cast on an Object listerror: incompatible types: Object cannot be converted to Ticketcompile time
wrong element into a generic listerror: incompatible types: Date cannot be converted to Ticketcompile time
generic list assigned to a wider generic listerror: incompatible types: LinkedList<String> cannot be converted to LinkedList<Object>compile time
arguments that cannot share one Treason: inference variable T has incompatible boundscompile time
type variable in the wrong placeerror: illegal start of typecompile time
method T hiding the class Terror: incompatible types: T#1 cannot be converted to T#2compile time
inserting into a wildcard listerror: incompatible types: Object cannot be converted to CAP#1compile time
calling draw() on an Objecterror: cannot find symbolcompile time
wildcard in the quantifier listerror: <identifier> expectedcompile time
instanceof with a generic type or Terror: Object cannot be safely cast to LinkedList<String>compile time
two methods with the same erasureerror: name clash: printlist(LinkedList<Date>) and printlist(LinkedList<String>) have the same erasurecompile time
creating a generic arrayerror: generic array creationcompile time
basic type as a type argumenterror: unexpected type required: reference found: intcompile time
the slide's Integer(x)error: cannot find symbol symbol: method Integer(int)compile time
reflection call without a try or throwserror: unreported exception ClassNotFoundException; must be caught or declared to be throwncompile time
importing a package nameerror: cannot find symbol symbol: class reflectcompile time
Date stored into an Employee arrayjava.lang.ArrayStoreException: Daterun time
wrong object cast out of an Object listjava.lang.ClassCastException: class Date cannot be cast to class Ticketrun time
raw type let a wrong element injava.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Stringrun time
unknown class namejava.lang.ClassNotFoundException: NoSuchClassrun time
reading a private field by reflectionjava.lang.IllegalAccessException: class Main cannot access a member of class Employee with modifiers "private"run time
opening up a JDK private fieldjava.lang.reflect.InaccessibleObjectException: Unable to make field private final byte[] java.lang.String.value accessiblerun time
handing out a (T[]) new Object[n]java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Integer;run time
unboxing nulljava.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()"run time

7.5Last-minute checklist

L1 · Polymorphism
  • reverse needs nothing, find equals, sort Comparable
  • compare with equals(), not ==
  • Object[] parameters leave the check to run time
  • Object lists: casts and mixed contents
L2 · Generics
  • quantifier after modifiers: public static <T>
  • class parameter <T> covers the whole class
  • do not hide it with a method <T>
  • S extends T relates two variables
L3 · Subtyping
  • arrays covariant, generics not
  • wildcard: type not needed elsewhere
  • extends to read, super to write
  • cannot insert into <?> except null
L4 · Reflection
  • getClass() returns the exact class
  • look up methods by name, not by index
  • checked exceptions on reflective calls
  • no new classes at run time
L5 · Generics at run time
  • one run-time class per generic class
  • no instanceof / overload / new T[] with types
  • wrapper types for basic types
  • compare wrappers with equals

7.6Corrections made to the source notes

WhereThe source saysCorrect
Lecture 1, reversefor(int i = 0, i < n/2; ++i)semicolon, not comma (checked by compiling)
Lectures 1 and 2, findarr[i] == o "translates to equals()"compares references; use arr[i].equals(o) (checked by running)
Lecture 1, arraycopydates into employees "Runtime error"; the converse "illegal"both compile; ArrayStoreException only when a non-null element is copied (checked by running)
Lecture 2, find on arraysa value of incompatible type is a compile-time errorfor arrays T widens and the call compiles; the error appears with List<T> or an explicit <String>
Lecture 2, modifier orderpublic <T> static voidpublic static <T> void
Lecture 2, generous arraycopy<S extends T, T> with T[] srcfirst parameter S[] src (otherwise S is unused)
Lecture 3, drawAllno parameter name, Object o; o.draw()name the parameter; the local is a Shape
Lecture 3, listcopy<? extends T, T>, <T, ? super T>, tgt.add(o)wildcards go inside parameter types; the class has insert (checked by compiling)
Lecture 4, importimport java.lang.reflect;import java.lang.reflect.*;
Lecture 4, newInstancec.newInstance()deprecated since Java 9: c.getDeclaredConstructor().newInstance()
Lecture 5, array workaroundT] newarray;T[] newarray;
Lecture 5, wrapperInteger(x)Integer.valueOf(x) (checked by compiling)
Revision overview
Polymorphismdynamic dispatch
structural: capabilities
Object[] simulation
Genericstype parameters
<S extends T, T>
do not hide T
Subtypingarrays covariant
generics invariant
? extends / ? super
Week 5Generics in Java
ReflectiongetClass, Class
constructors, methods, fields
invoke, get, set
Erasureone run-time class
no instanceof, new T[]
no overload clash
WrappersInteger for int
autoboxing
equals, not ==
Revision: all of Week 5 on one page
Back to top