Friday, October 30, 2009

Wrapper Classes








Wrapper Classes


The student system must store student charges in a collection to be totaled later. Each charge is an int that represents the number of cents a student spent on an item.



Remember that primitive types are not objectsthey do not inherit from the class java.lang.Object. The java.util.List interface only supplies an add method that takes a reference type as a argument. It does not supply overloaded add methods for each of the primitive types.


In order to store the int charge in a collection, then, you must convert it to an object. You accomplish this by storing the int in a wrapper object.


For each primitive type, java.lang defines a corresponding wrapper class.[8]

[8] The table lists all available primitive types and corresponding wrappers. You will learn more about the unfamiliar types in Lesson 10.


Type

Wrapper Class

char

Character

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

boolean

Boolean



Each wrapper class provides a constructor that takes a primitive of the appropriate type as argument. The wrapper stores this primitive and allows for extracting it using a corresponding getter method. In the case of the Integer wrapper, you extract the original int by sending the message intValue to the wrapper.


A test (in StudentTest) for the example:



public void testCharges() {
Student student = new Student("a");
student.addCharge(500);
student.addCharge(200);
student.addCharge(399);
assertEquals(1099, student.totalCharges());
}


If you had to write the implementation using pre-J2SE 5.0 code, it would look something like this:



public class Student implements Comparable {
...
private List charges = new ArrayList();
...
public void addCharge(int charge) {
charges.add(new Integer(charge));
}

public int totalCharges() {
int total = 0;
Iterator it = charges.iterator();
while (it.hasNext()) {
Integer charge = (Integer)it.next();
total += charge.intValue();
}
return total;
}
...


The addCharge method shows how you would wrap the int in an instance of the Integer wrapper class in order to pass it to the add method of the charges collection. When iterating through the collection (in totalCharges), you would have to cast each retrieved Object to the Integer wrapper class. Only then would you be able to extract the original int value through use of the intValue method.


J2SE 5.0 simplifies the code through the use of parameterized types and the for-each loop.



public class Student implements Comparable<Student> {
...
private List<Integer> charges = new ArrayList<Integer>();
...
public void addCharge(int charge) {
charges.add(new Integer(charge));
}

public int totalCharges() {
int total = 0;
for (Integer charge: charges)
total += charge.intValue();
return total;
}
...


In addition to allowing you to wrap a primitive, the wrapper classes provide many class methods that operate on primitives. Without the wrapper classes, these methods would have nowhere to go. You have already used the Character class method isWhitespace.


A further simplification of the code comes about from one of the new J2SE 5.0 features, autoboxing.


Autoboxing and Autounboxing


Autoboxing is the compiler's ability to automatically wrap, or "box" a primitive type in its corresponding wrapper class. Autoboxing only occurs when Java can find no method with a matching signature. A signature match occurs when the name and arguments of a message send match a method defined on the class of the object to which the message is being sent. An argument is considered to match if the types match exactly or if the type of the argument is a subtype of the argument type as declared in the method.


As an example, if Box declares a method as:



void add(List list) {... }


then any of the following message sends will resolve to this add method:



Box b = new Box();
b.add(new List());
b.add(new ArrayList());


The second add message send works because ArrayList is a subclass of List.


If Java finds no direct signature match, it attempts to find a signature match based on wrapping. Any method with an Object argument in the appropriate place is a match.


In the addCharge method, the explicit wrapping of the int into an Integer is no longer necessary:



public void addCharge(int charge) {
charges.add(charge);
}


It is important for you to understand that the wrapping does occur behind the scenes. Java creates a new Integer instance for each primitive value you wrap.


Autoboxing only occurs on arguments. It would be nice if autoboxing occurred anywhere you attempted to send a message to a primitive. This is not yet a capability, but it would allow expressions like:



6.toString() // this does not work


Autounboxing occurs when you attempt to use a wrapped primitive anywhere a primitive is expected. This is demonstrated in the following two tests:



public void testUnboxing() {
int x = new Integer(5);
assertEquals(5, x);
}

public void testUnboxingMath() {
assertEquals(10, new Integer(2) * new Integer(5));
}


The more useful application of autounboxing is when you extract elements from a collection bound to a primitive wrapper type. With both autoboxing and autounboxing, the charge code in Student becomes:



public void addCharge(int charge) {
charges.add(charge);
}

public int totalCharges() {
int total = 0;
for (int charge: charges)
total += charge;
return total;
}


You will learn about addition capabilities of wrapper classes in Lesson 10 on mathematics.








    No comments: