December Code

Java wrapper classes and generics interview questions, with answers

Wrapper classes and generics are where Java's two kinds of type meet: primitives such as int, which are fast and can never be null, and objects such as Integer, which collections and generic code require. Autoboxing converts between them silently, which is convenient until it is not — == stops comparing values, a null throws where no method call is visible, and a generic list forgets its type argument at run time. Every answer below comes with code, and every output shown was produced by compiling and running it.

The questions start with the wrappers and boxing, then move to generics: erasure, wildcards and bounds. Then take the free Java diagnostic — ten questions across every Java topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What are wrapper classes in Java, and why do they exist?

    In short: Each primitive has an object counterpart — Integer for int, Character for char, and so on — because collections and generics hold only objects, and a wrapper can also be null.

    Generics work only with reference types, so List<int> does not compile and a list of numbers has to be a List<Integer>. The wrappers, Byte, Short, Integer, Long, Float, Double, Character and Boolean, are immutable and final, and they carry useful static methods such as Integer.parseInt, Integer.MAX_VALUE, Character.isDigit and Integer.toString(n, radix). Their nullability is sometimes the point, representing 'no value' in a database mapping, and sometimes the bug. The cost is memory and speed: an Integer is an object with a header, typically 16 bytes against an int's 4, and a List<Integer> is an array of references to such objects, which is why performance-sensitive code uses int[] or primitive streams such as IntStream.

    int n = Integer.parseInt("42");
    System.out.println(n + 1);
    // 43
    var b = Integer.toString(n, 2);
    System.out.println(b);
    // 101010
  2. 2.What are autoboxing and unboxing in Java, and where can they go wrong?

    In short: Autoboxing converts an int to an Integer automatically and unboxing converts it back; the traps are a null Integer throwing NullPointerException when unboxed and hidden object creation in loops.

    The compiler inserts Integer.valueOf(n) wherever an int is used where an Integer is needed, and x.intValue() in the other direction: in arithmetic, in comparisons with a primitive, and in assignments to a primitive. Because the call is invisible, so is the failure: unboxing a null reference throws NullPointerException on a line that shows no method call, as below, so any Integer that can be null deserves a check before it meets an int. Boxing in a hot loop is the other cost: with Long sum = 0L, each sum += i unboxes, adds and boxes a new Long, which can make a loop several times slower than the same code with a long. Mixing boxed and primitive operands in one expression unboxes too.

    Integer boxed = null;
    int total = 10;
    total += boxed;
    // throws NullPointerException
  3. 3.Why does == on two Integer objects give different answers for small and large values?

    In short: Integer.valueOf, which autoboxing uses, returns cached objects for -128 to 127, so == on those compares one shared object, while larger values are separate objects; compare Integers with equals().

    The Integer class keeps a cache of the objects for -128 through 127, and Integer.valueOf returns one of those when the value is in range, so two boxed 100s are the same object and == is true. Outside the range each boxing creates a new object, so == compares two different references and is false even though the values match, which is this page's sample. The upper bound can be raised with a JVM option, which is why the sample says 'with default JVM settings'. Byte, Short and Long cache the same range and Character caches 0 to 127; Float and Double cache nothing. The fix is never to use == on wrappers: use equals(), or compare with a primitive, since Integer == int unboxes and compares values.

    Integer a = 100, b = 100;
    Integer c = 1000, d = 1000;
    System.out.println(a == b);
    // true
    System.out.println(c == d);
    // false
    boolean e = c.equals(d);
    System.out.println(e);
    // true
    System.out.println(c == 1000);
    // true
  4. 4.What is type erasure in Java generics?

    In short: The compiler checks generic types and then erases them, so at run time a List<String> is just a List, which is why new T() and overloads differing only in a type argument are impossible.

    Generics were added in Java 5 without changing the JVM, so the compiler does all the work: it checks that only Strings go into a List<String>, inserts casts where values come out, and then replaces each type parameter with its bound, Object by default. The consequences follow directly. new T() and new T[n] are impossible, because T is unknown at run time, so code passes a Class<T> or a Supplier<T> instead. For a variable of type Object, instanceof List<String> does not compile, because the check cannot be made at run time, though instanceof List<?> does. Two methods whose parameters differ only in a type argument erase to the same signature, so they cannot be overloaded. Raw types such as a plain List switch the checks off and turn type errors into ClassCastExceptions far from their cause, as below.

    List raw = new ArrayList();
    raw.add(42);
    List<String> xs = raw;
    String s = xs.get(0);
    // throws ClassCastException
  5. 5.What is the difference between <? extends T> and <? super T> in Java generics?

    In short: ? extends T accepts T or any subtype and is safe for reading values out as T; ? super T accepts T or any supertype and is safe for putting T values in — producer extends, consumer super.

    Generics are invariant: a List<Integer> is not a List<Number>, even though an Integer is a Number, because otherwise you could add a Double to a list of Integers through the wider reference. Wildcards restore flexibility safely. A List<? extends Number> can refer to a List<Integer> or a List<Double>, and each element can be read as a Number, but nothing except null can be added, since the real element type is unknown. A List<? super Integer> can refer to a List<Integer>, a List<Number> or a List<Object>, and Integers can be added, but reading gives only Object. Joshua Bloch's mnemonic PECS, producer extends and consumer super, is the answer interviewers want, and Collections.copy(dest, src) is its textbook example.

    List<Integer> in = List.of(1);
    List<? extends Number> r = in;
    Number n = r.get(0);
    System.out.println(n);
    // 1
    List<Number> out =
        new ArrayList<>();
    List<? super Integer> w = out;
    w.add(5);
    System.out.println(out);
    // [5]
  6. 6.What are bounded type parameters in Java generics?

    In short: A bound such as <T extends Number> restricts T to types with that capability, so the method body can call Number's methods on a T, and the compiler rejects arguments that do not qualify.

    An unbounded T can only be treated as an Object. A bound says what T can do: in the method below, every T is known to be a Number, so doubleValue() can be called on it, and half("7") is a compile error rather than a run-time failure. A type parameter can have several bounds, at most one class and any number of interfaces, joined with &, as in <T extends Number & Comparable<T>>. The bound is also what T erases to, so the compiled method takes a Number. Library signatures often loosen a bound further, as Collections.sort does with <T extends Comparable<? super T>>, so that a subclass which inherits compareTo from its parent still qualifies.

    static <T extends Number>
            double half(T v) {
        return v.doubleValue() / 2;
    }
    
    System.out.println(half(7));
    // 3.5
  7. 7.What is the difference between Integer.parseInt and Integer.valueOf?

    In short: parseInt returns a primitive int, while valueOf returns an Integer, reusing the cached objects for -128 to 127; both throw NumberFormatException for text that is not a number.

    Integer.parseInt("42") gives an int and is what arithmetic needs. Integer.valueOf("42") gives an Integer object; for values from -128 to 127 it returns the shared cached instance, otherwise a new one, the same rule autoboxing follows, because autoboxing calls valueOf(int). new Integer(...) always created a new object, which is why it is deprecated for removal and valueOf is preferred. Both parsing methods reject surrounding spaces, a trailing decimal point and the empty string with NumberFormatException, so user input needs trimming and a try-catch. The same pairs exist for the other wrappers, such as Long.parseLong and Double.valueOf, and Boolean.parseBoolean returns false for anything but "true", ignoring case, instead of throwing.

    int a = Integer.parseInt("-7");
    var b = Integer.valueOf("7");
    System.out.println(a + b);
    // 0
    Integer.parseInt(" 7");
    // throws NumberFormatException

How the diagnostic asks it

One question from the Java bank, exactly as a sitting would show it. The bank has 3 on wrappers & generics and 30 across Java.

Wrappers & Generics · mediumJAVA-023

What does this Java code print, with default JVM settings?

Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println((a == b) + " " + (c == d));
  1. 1true true
  2. 2false false
  3. 3true falsecorrect
  4. 4false true

Autoboxing calls Integer.valueOf, which returns cached objects for values from -128 to 127, so a and b are the same object and a == b is true, while 128 is outside the cache and c and d are two different objects, so c == d is false. true true assumes == compares values; it does only when one side is a primitive int. false false assumes no caching. false true reverses the cache range. The lesson is to compare Integer values with equals(), or unbox them, never with ==. The upper bound of the cache can be raised with a JVM option, which is why the question says default settings.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 Java questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated