Wrapper Classes
Why this matters
Imagine you're at an arcade in Dallas. You have a pocketful of plain cash ($5, $10). That cash is simple, fast, and great for buying a soda from the vending machine. But to play any of the arcade games, you need to go to the counter and exchange your cash for game tokens.
Those tokens represent the same value as your cash, but they're in a form the machines understand. They're "wrapped" in a new format.
In Java, our primitive types like int and double are like that cash. They're efficient and straightforward. But some powerful tools, like the ArrayList we've been using, are like the arcade games—they only work with objects, not primitives.
So, what do we do? We need a way to "wrap" our primitive values into object form. That's exactly what wrapper classes do. We'll explore how Java helps us do this, sometimes so automatically it feels like magic.
Concept overview
flowchart TD
subgraph Autoboxing
A[primitive int value e.g. 42] --> B{Context needs an Object?<br/>e.g. ArrayList.add()};
B -- Yes --> C[Java automatically creates<br/>new Integer object];
C --> D[Integer object holding 42];
end
subgraph Unboxing
E[Integer object holding 42] --> F{Context needs a primitive?<br/>e.g. int x = myInteger};
F -- Yes --> G[Java automatically extracts<br/>the primitive value];
G --> H[primitive int value 42];
end
Core explanation
Hello everyone, it's Saavi. Today we're tackling a topic that bridges the gap between the simple data we've used so far and the powerful data structures we need for AP-level problems.
Primitives vs. Objects: The "Why"
Let's quickly review. In Java, we have two main kinds of data:
- PrimitivesThese are the simple, fundamental values. Think
int,double,boolean. They hold a single value and nothing else. They're like a single, raw ingredient, like a cup of flour. - ObjectsThese are more complex. They can hold data (instance variables) and have behaviors (methods). Think
String,Scanner, or theArrayListswe've been working with. They're like a finished cake—made of ingredients, but with its own structure and things you can do with it (like slice it).
Here's the problem that leads us to wrapper classes: ArrayList can only hold objects.
If you try to write this code, your program won't even compile:
// This code will NOT work!
ArrayList<int> myNumbers = new ArrayList<int>();
Java will give you an error because int is a primitive, not an object. It's like trying to put raw flour into a cake box that's only designed for finished cakes.
The Solution: Wrapper Classes
To solve this, Java provides wrapper classes. For every primitive type, there's a corresponding class that "wraps" it in an object. The two you absolutely must know for the AP exam are:
int->Integerdouble->Double
Notice the capital letters—that's our clue that Integer and Double are classes, not primitives.
So, the correct way to declare an ArrayList for integers is:
// This is the correct way!
ArrayList<Integer> myNumbers = new ArrayList<Integer>();
Now, myNumbers is an ArrayList that can hold Integer objects.
Autoboxing and Unboxing: Java's Helping Hand
You might be thinking, "Great. So now every time I want to add a number, I have to manually create an Integer object? That sounds tedious."
Five years ago, you would have been right. You would have had to write:
myNumbers.add(new Integer(10));
Thankfully, Java now does this for us automatically. This process is called autoboxing.
Autoboxing is the automatic conversion from a primitive type to its corresponding wrapper class object.
ArrayList<Integer> scores = new ArrayList<Integer>();
// Autoboxing in action!
// Java automatically converts the primitive int 100 into an Integer object.
scores.add(100);
// It also works on assignment.
Integer myGrade = 95; // Java converts the int 95 to an Integer object.
The reverse process is called unboxing.
Unboxing is the automatic conversion from a wrapper class object back to its primitive type.
// Let's get the score back from our ArrayList.
// The .get() method returns an Integer object.
Integer scoreObject = scores.get(0);
// Unboxing in action!
// Java automatically "unboxes" the Integer object into a primitive int.
int primitiveScore = scoreObject;
// You can even do it directly in one line.
int myScore = scores.get(0); // .get(0) returns an Integer, which is unboxed to an int.
Think of it like this: Autoboxing is like putting a $10 bill into a gift card envelope. Unboxing is like taking the bill back out of the envelope to spend it. Java handles the envelope for you so you can just think about the money.
A Critical Detail: Immutability
Here's a concept that often trips students up. Integer and Double objects are immutable.
"Immutable" means that once the object is created, its internal value can never be changed.
Let's look at what seems like a contradiction:
Integer myAge = 25;
myAge = myAge + 1; // myAge is now 26
You might say, "Saavi, you just said it's immutable, but I just changed its value from 25 to 26!"
This is a fantastic observation, and it gets to the heart of how objects work. You didn't change the Integer object that held the value 25. Instead, Java did this behind the scenes:
- It unboxed
myAgeinto a primitiveint(25). - It added 1 to that primitive
int, resulting in 26. - It autoboxed the result (26) into a brand new
Integerobject. - It made the
myAgevariable point to this new object.
The original Integer object with the value 25 is now gone (garbage collected), and myAge refers to a completely different object. It's like signing a contract; you can't just cross out a term and change it. You have to void the old contract and write a whole new one.
Converting Strings to Numbers: parseInt and parseDouble
What if your data comes in as a String? For example, a user types their age into a text box on a website. You'll get the data as "25", not the number 25. You can't do math with a String!
The wrapper classes give us a tool for this. These are static methods, meaning you call them on the class itself, not an object.
Integer.parseInt(String s): Takes a string and returns a primitiveint.Double.parseDouble(String s): Takes a string and returns a primitivedouble.
String ageInput = "42";
String priceInput = "19.99";
// Convert the strings to primitive numbers
int age = Integer.parseInt(ageInput);
double price = Double.parseDouble(priceInput);
System.out.println("In ten years, you will be: " + (age + 10));
// Output: In ten years, you will be: 52
double priceWithTax = price * 1.0825; // Using a Dallas sales tax rate
System.out.println("Total price: " + priceWithTax);
// Output: Total price: 21.639175
See it in action
Worked examples
Let's walk through a couple of problems to make these concepts concrete.
Calculating the Average of Test Scores
Problem: You are given a list of test scores for a class. Calculate the average score. The scores are stored in an ArrayList.
Solution Walkthrough:
- 1Set up the DataFirst, we need an
ArrayListto hold the scores. SinceArrayListcan't hold primitiveints, we must use theIntegerwrapper class.ArrayList<Integer> scores = new ArrayList<Integer>(); scores.add(88); // Autoboxing: int -> Integer scores.add(92); // Autoboxing scores.add(77); // Autoboxing scores.add(100); // AutoboxingWhy: We choose
ArrayList<Integer>because it's the only way to store a list of integer numbers in anArrayList. As we add the primitiveintvalues, Java automatically converts them toIntegerobjects for us. - 2Sum the ScoresWe need to iterate through the list and add up all the scores.
int sum = 0; for (Integer score : scores) { sum = sum + score; // Unboxing here! }Why: Inside the loop, the variable
scoreis anIntegerobject. When we writesum + score, Java sees that we're trying to do math with an object. It automatically unboxes theIntegerobjectscoreinto a primitiveintso the addition can happen. This is the magic of unboxing in action. - 3Calculate the AverageNow that we have the sum, we can calculate the average. Remember, an average can have a decimal, so we should use a
double.double average = (double) sum / scores.size(); System.out.println("The average score is: " + average); // Output: The average score is: 89.25Why: We cast
sumto adoublebefore the division. If we didn't, we'd be doing integer division, which would chop off the decimal part and give us89instead of89.25. This is a common mistake, but it's not related to wrapper classes—just good practice!
Where students go wrong: They might try to declare the ArrayList as ArrayList<int>. Or, they might get confused about the sum = sum + score line, not realizing that unboxing is happening automatically. They know it works, but not why it works. Understanding the unboxing step is key.
Processing Online Order Data
Problem: You're processing data from an online form. You receive the quantity of an item as a String "3" and its price per item as a String "29.50". Calculate the total cost.
Solution Walkthrough:
- 1Identify the InputsWe start with two
Stringvariables.String quantityStr = "3"; String priceStr = "29.50";Why: This simulates getting data from a user or a file, which often comes in as text. We can't multiply
"3"by"29.50". - 2Convert Strings to NumbersWe need to convert these strings into usable numeric types. We'll use the
parsemethods for this.int quantity = Integer.parseInt(quantityStr); double price = Double.parseDouble(priceStr);Why:
Integer.parseInt()is the perfect tool for converting a string representation of a whole number into a primitiveint.Double.parseDouble()does the same for a decimal number, converting it to a primitivedouble. We choose primitives here because our goal is simply to perform a calculation. - 3Perform the CalculationNow that we have
intanddoubleprimitives, we can do the math.double totalCost = quantity * price; System.out.println("Total cost: $" + totalCost); // Output: Total cost: $88.5Why: The calculation is straightforward once the data is in the correct numeric format.
Where students go wrong: A common mistake is to confuse Integer.parseInt() with creating an Integer object. A student might write Integer quantity = Integer.parseInt(quantityStr);, which works due to autoboxing, but it's an unnecessary step. The goal of parseInt is to get a primitive int for calculations. Understanding that parseInt returns a primitive int simplifies your thinking.
Try it yourself
Ready to try it yourself? Take a shot at these.
Problem 1: Sum of Even Numbers
Write a method sumEvens that takes an ArrayList<Integer> as a parameter. The method should iterate through the list and return the sum of only the even numbers.
public int sumEvens(ArrayList<Integer> numbers) {
// Your code here
}
Problem 2: Gradebook from Text
Write a method calculateAverage that takes an ArrayList<String> of grades (e.g., "95.5", "82.0", "76.8"). The method should calculate and return the average of these grades as a double.
public double calculateAverage(ArrayList<String> gradeStrings) {
// Your code here
}Practice — 8 questions
In simple terms, wrapper classes let you treat simple numbers like `int` and `double` as full-fledged objects, which is necessary for data collections like `ArrayList`.
// This code will NOT work!
ArrayList<int> myNumbers = new ArrayList<int>();
- 4.7.A: Develop code to use Integer and Double objects from their primitive counterparts and determine the result of using these objects.
- 4.7.A.1
- The Integer class and Double class are part of the java.lang package. An Integer object is immutable, meaning once an Integer object is created, its attributes cannot be changed. A Double object is immutable, meaning once a Double object is created, its attributes cannot be changed.
- 4.7.A.2
- Autoboxing is the automatic conversion that the Java compiler makes between primitive types and their corresponding object wrapper classes. This includes converting an int to an Integer and a double to a Double. The Java compiler applies autoboxing when a primitive value is: • passed as a parameter to a method that expects an object of the corresponding wrapper class • assigned to a variable of the corresponding wrapper class
- 4.7.A.3
- Unboxing is the automatic conversion that the Java compiler makes from the wrapper class to the primitive type. This includes converting an Integer to an int and a Double to a double. The Java compiler applies unboxing when a wrapper class object is: • passed as a parameter to a method that expects a value of the corresponding primitive type • assigned to a variable of the corresponding primitive type
- 4.7.A.4
- The following class Integer method— including what it does and when it is used—is part of the Java Quick Reference: • static int parseInt(String s) returns the String argument as an int.
- 4.7.A.5
- The following class Double method— including what it does and when it is used—is part of the Java Quick Reference: • static double parseDouble(String s) returns the String argument as a double.
flowchart TD
subgraph Autoboxing
A[primitive int value e.g. 42] --> B{Context needs an Object?<br/>e.g. ArrayList.add()};
B -- Yes --> C[Java automatically creates<br/>new Integer object];
C --> D[Integer object holding 42];
end
subgraph Unboxing
E[Integer object holding 42] --> F{Context needs a primitive?<br/>e.g. int x = myInteger};
F -- Yes --> G[Java automatically extracts<br/>the primitive value];
G --> H[primitive int value 42];
end
Read what Saavi narrates
(gentle, warm music starts and fades to background)
Hello everyone, it's Saavi. Let's talk about something that feels a little abstract at first, but is super practical.
Imagine you're at an arcade in Dallas. You have cash, but the games only take tokens. Your cash is like a primitive `int` in Java... simple and fast. But some things, like the `ArrayList`, are like those arcade games—they only work with objects, not primitives.
So what do we do? We need to exchange our cash for tokens. We need to "wrap" our primitive values into object form. That’s what wrapper classes, like `Integer` and `Double`, are for. They let us put our numbers into collections like `ArrayList`.
The best part is, Java often does the work for you. This is called autoboxing and unboxing.
Let's look at a quick example. Say we have an `ArrayList` of test scores. We have to declare it as an `ArrayList` of `Integer` objects.
But when we add scores, we can just write `scores.add(88)`. We use the primitive `int`, and Java automatically "boxes" it into an `Integer` object for us. That's autoboxing.
Then, if we want to calculate the sum, we can write a loop. Inside, we might have a line like `sum equals sum plus score`. Here, `score` is an `Integer` object from our list. Java sees we're trying to do math, so it automatically "unboxes" it back into a primitive `int`. It's like taking the token out of your pocket and seeing it's worth one dollar.
One common mistake I see is when students try to compare two `Integer` objects using the double equals sign. Because they are objects, you're comparing their memory addresses, not their values. It's a tricky trap! Always use the dot-equals method to be safe.
Wrapper classes might seem like a small detail, but they are the essential glue that lets our simple data work with Java's more powerful object-oriented features. You've got this. Keep practicing, and it will become second nature.
(music swells and fades out)
The type parameter for a generic class like `ArrayList` must be an object type (a class). `int` is a primitive type.
Use the corresponding wrapper class: `ArrayList<Integer>`. Java's autoboxing will make it feel almost the same.
`==` compares memory addresses for objects. Two different `Integer` objects with the value 150 will be at different memory addresses, so `==` will return `false`. (Note: Java caches small integer values, so this might *seem* to work for numbers between -128 and 127, which is a dangerous trap!)
Use the `.equals()` method for comparing the values of any two objects: `integer1.equals(integer2)`. Alternatively, unbox them to primitives and compare with `==`: `(int)integer1 == (int)integer2`.
`Integer` objects are immutable. That line of code creates a *new* `Integer` object and assigns its reference to `myInteger`. The old object is unchanged.
Understand that operations on wrapper objects often create new objects. This is important for understanding performance and memory in more complex applications.
`Integer.parseInt("5")` is a static method that takes a `String` and returns a primitive `int`. `new Integer(5)` (or the autoboxed `Integer x = 5;`) creates an `Integer` object. They result in different types.
Use `parseInt` when your goal is to get a primitive `int` from a `String` for a calculation. Use the `Integer` object type when you need an object, like for an `ArrayList`.
These methods can only parse strings that contain valid numeric characters. Anything else will cause your program to crash with a `NumberFormatException`.
Ensure the string you are parsing contains only digits (and optionally a single `.` for `parseDouble` or a leading `-`). In the real world, you'd use error handling, but for the AP exam, assume the input strings are valid unless told otherwise.