Concepts / Primitive Types and Their Limits

Primitive Types and Their Limits

Generic data structures in Java can only hold objects, and primitive values such as int are not objects, so they cannot be used as type arguments directly.

  • Programming

A Simple Question About Scores

Suppose you are writing a program that keeps track of a list of integer scores. Java offers generic collections such as ArrayList, which let you specify the kind of element the list will hold. The natural first instinct is to write the element type as int, the primitive integer type, and move on. This section follows what happens to a single value, the number 17, when you try that approach, and shows how Java's wrapper classes and automatic conversions get around the problem. The goal is to understand the mechanism well enough that you can predict what the compiler accepts, what the program does at runtime, and what that behavior costs.

Why Generic Containers Reject Primitives

Generic programming in Java is built around the idea that a container holds objects. A primitive value such as the int 17 is a raw number, not an object, so it does not fit into that design. The generic machinery was designed for objects, and primitives sit outside that category. This is the root of the restriction, and it is not an arbitrary rule you must simply memorize. It follows directly from the fact that primitive values and objects are different kinds of things. A generic container needs something it can refer to as an object, and a bare int does not offer that.

What do you think happens?

Can you declare a list whose element type is the primitive int, as in ArrayList<int>?

  • Yes, it works just like ArrayList<Integer>

  • No, type arguments must be objects, and int is not an object

Reveal answer
Answer:

No, type arguments must be objects, and int is not an object. The declaration ArrayList<int> is rejected.

Generic type arguments in Java must be object types. A primitive such as int is not an object, so it cannot be used as the element type. The fix is to use the wrapper class Integer instead.

Once you see the restriction this way, the solution becomes clear: you need an object that carries the primitive value inside it. That object is the wrapper. The primitive 17 cannot enter the list directly. It first becomes an Integer object, and only that object is placed into the container.

What a Wrapper Object Holds

Every primitive type in Java has an associated wrapper class. A wrapper is an ordinary class whose job is to package a primitive value inside an object. An Integer object therefore contains a value of type int. You can think of the object as a labeled box: the box itself is an object that generic structures can handle, and the primitive number sits inside it. The same idea holds for Boolean and Character, and for the remaining wrapper classes.

xholds 17 directlywrefers to an objectInteger objectcontains int value 17refers to
How does a variable refer to a wrapper object, compared with a primitive variable that holds the value directly?

The diagram contrasts two kinds of variables. The primitive variable x holds the number itself. The reference variable w does not hold the number directly; it refers to a separate object, and that object is the one holding the int value. Generic containers work with the object, which is why the wrapper is the piece that can be stored in a collection.

Because a wrapper is a real object, it also comes with methods. The Integer class provides equals(), compareTo(), and toString(). These behave the way you would expect, comparing and printing the underlying int values. Similar methods are available across the wrapper classes. This means that once a number is wrapped, you can still ask it to compare itself with another number or to display itself as text.

Wrappers also make it possible to work with generic interfaces. A list of Integer values can be held in a variable whose type is ArrayList<Integer>, and it can likewise be viewed through interfaces such as Collection<Integer> or Set<Integer>. None of these would be available for a bare primitive, since none of them accept primitives as their element type.

Autoboxing and Unboxing in Motion

If you had to create a wrapper object by hand every time you added a number to a list, generic collections would be tedious to use. Java removes that burden by performing automatic conversions between a primitive type and its corresponding wrapper type. The conversion from a primitive to a wrapper is called autoboxing. The conversion from a wrapper back to a primitive is called unboxing. The practical effect is that a generic container holding wrapper objects can be used almost as though it held primitive values.

Code walkthrough
List<Integer> scores = new ArrayList<>();
scores.add(17);
int first = scores.get(0);
System.out.println(first);
  1. 1
    Line 1 declares a list of Integer objects. Writing List<int> here would be rejected, as the previous section showed.
  2. 2
    Line 2 adds the literal 17. The list cannot store a raw int, so the value is autoboxed into an Integer before it is placed in the container.
  3. 3
    Line 3 reads the element back. get(0) returns an Integer object, and the assignment to an int variable performs unboxing.
  4. 4
    Line 4 prints the unboxed value, which is 17.
  • The element type is Integer, a wrapper class, so the list is allowed to hold it.
  • Passing the primitive 17 to add() triggers autoboxing: an Integer object is created to hold 17.
  • Calling get(0) returns the stored Integer object.
  • Assigning that object to an int variable triggers unboxing: the int value inside is extracted.
Output
17

Notice the two directions in the diagram below. Autoboxing moves a value from the primitive world into the object world, and unboxing moves it back. Both are automatic, which is why you rarely see them written out in code. They are two separate conversions, even though a value may pass through both.

int 17primitive valueInteger objectholds 17ArrayList of Integerstores the objectInteger objectretrieved from listint 17primitive value againautoboxingaddgetunboxing
When an int goes into a List of Integer and comes back out, which conversion steps happen, and in what order?

Read the diagram from left to right. The primitive 17 is autoboxed into an Integer object, the object is placed in the list, and later the object is retrieved and unboxed back into a primitive int. The conversion happens at the point where you use the container, so the code you write looks as if raw numbers are going in and coming out.

Worked Example: Storing and Retrieving a Score

Worked example

Adding a Score to a List and Reading It Back

Trace what happens to the value 85 when it is added to a List of Integer and then read into an int variable.

  1. 1
    Declare the container

    The list's element type is Integer. This is a wrapper class, which is an object type, so the declaration is allowed.

  2. 2
    Add the primitive

    The value 85 is a primitive int. Because the list needs an object, autoboxing creates an Integer object that contains 85, and that object is stored in the list.

  3. 3
    Read the element

    get(0) returns the stored Integer object. The list does not hand back a raw int; it hands back the object.

  4. 4
    Assign to an int

    Assigning the Integer to an int variable triggers unboxing, which extracts the int value 85 from inside the object.

Result

The int variable ends up holding 85. Two conversions occurred: autoboxing on the way in and unboxing on the way out.

The Price of Wrapping Many Values

Autoboxing hides work from you, but the work still happens. Each conversion of a primitive into a wrapper creates an object, and creating an object takes time and uses memory. For a small collection this is usually unimportant. For a program that performs many such operations, the overhead can matter. The source material makes the comparison directly: creating wrapper objects adds time and memory overhead, so an array of int is more efficient than an ArrayList of Integer in performance-sensitive code.

Aspectint arrayArrayList of Integer
Element typePrimitive intWrapper Integer objects
Object creation per valueNone; values are stored directlyOne wrapper object per stored value, created by autoboxing
Conversion when reading or writingNoneAutoboxing on write, unboxing on read
Generic collection interfacesNot availableAvailable, such as Collection of Integer
Best fitPerformance-sensitive code with many numbersConvenience when generic collections are needed

Storing primitives directly versus storing them as wrapper objects. The trade-off is convenience and generic support on one side and time and memory overhead on the other.

Mistakes When Using Wrappers

  • Common mistake

    Declaring a generic collection with a primitive type argument

    What it looks like
    List<int> scores = new ArrayList<>();
    Why it fails

    Generic type arguments must be objects, and int is a primitive, not an object.

    Do this instead
    Use the wrapper class instead: List<Integer> scores = new ArrayList<>();
  • Common mistake

    Assuming autoboxing does no work

    What it looks like

    Adding millions of primitive values to an ArrayList of Integer in a tight loop.

    Why it fails

    Each conversion creates an object that takes time and memory, even though the code looks like it passes raw numbers.

    Do this instead

    For performance-sensitive code with many values, store the numbers in a primitive array.

  • Common mistake

    Forgetting that autoboxing and unboxing are separate conversions and both can occur on the same value

Practice: Trace the Conversions

EASY

A program stores the boolean value true in a List of Boolean, then reads it back into a boolean variable. Describe, in order, each conversion that happens, and state which wrapper class is used. Then explain why a List of boolean would be rejected.

Hints
  • Think about which direction each conversion moves the value: primitive to object, or object to primitive.
  • The wrapper for boolean is Boolean, as listed in the mapping table.
  • Recall why generic type arguments must be object types.

Key Takeaways

  • Generic collections in Java hold objects, and primitive values such as int are not objects, so they cannot be used as type arguments directly.
  • Each primitive type has a matching wrapper class, such as Integer for int, Boolean for boolean, and Character for char, and a wrapper object contains the primitive value.
  • Autoboxing converts a primitive into its wrapper object, and unboxing converts a wrapper back into a primitive, letting generic collections be used almost as if they held primitives.
  • Creating wrapper objects adds time and memory overhead, so an array of int is more efficient than an ArrayList of Integer in performance-sensitive code.