• Tidak ada hasil yang ditemukan

As we described in Chapter 4, there is a schism in the Java world between class types (i.e., objects) and primitive types (i.e., numbers, characters, and boolean values). Java accepts this trade-off simply for efficiency reasons. When you’re crunching numbers,

as objects, Java supplies a standard wrapper class for each of the primitive types, as shown in Table 5-1.

Table 5-1. Primitive type wrappers Primitive Wrapper

void java.lang.Void boolean java.lang.Boolean char java.lang.Character byte java.lang.Byte short java.lang.Short int java.lang.Integer long java.lang.Long float java.lang.Float double java.lang.Double

An instance of a wrapper class encapsulates a single value of its corresponding type.

It’s an immutable object that serves as a container to hold the value and let us retrieve it later. You can construct a wrapper object from a primitive value or from a String representation of the value. The following statements are equivalent:

Float pi = new Float( 3.14 );

Float pi = new Float( "3.14" );

The wrapper constructors throw a NumberFormatException when there is an error in parsing a string.

Each of the numeric type wrappers implements the java.lang.Number interface, which provides “value” methods access to its value in all the primitive forms. You can retrieve scalar values with the methods doubleValue(), floatValue(), longValue(), intValue(), shortValue(), and byteValue():

Double size = new Double ( 32.76 );

double d = size.doubleValue(); // 32.76 float f = size.floatValue(); // 32.76 long l = size.longValue(); // 32 int i = size.intValue(); // 32

This code is equivalent to casting the primitive double value to the various types.

The most common need for a wrapper is when you want to pass a primitive value to a method that requires an object. For example, in Chapter 7, we’ll look at the Java Col‐

lections API, a sophisticated set of classes for dealing with object groups, such as lists, sets, and maps. The Collections API works on object types, so primitives must be wrapped when stored in them. We’ll see in the next section that Java makes this

wrapping process automatic. For now, however, let’s do it ourselves. As we’ll see, a List is an extensible collection of Objects. We can use wrappers to hold numbers in a List (along with other objects):

// Simple Java code

List myNumbers = new ArrayList();

Integer thirtyThree = new Integer( 33 );

myNumbers.add( thirtyThree );

Here, we have created an Integer wrapper object so that we can insert the number into the List, using the add() method, which accepts an object. Later, when we are extracting elements from the List, we can recover the int value as follows:

// Simple Java code

Integer theNumber = (Integer)myNumbers.get(0);

int n = theNumber.intValue(); // 33

As we alluded to earlier, allowing Java to do this for us (“autoboxing”) makes the code more concise and safer. The usage of the wrapper class is mostly hidden from us by the compiler, but it is still being used internally:

// Java code using autoboxing and generics

List<Integer> myNumbers = new ArrayList<Integer>();

myNumbers.add( 33 );

int n = myNumbers.get( 0 );

We’ll see more of generics later.

Method Overloading

Method overloading is the ability to define multiple methods with the same name in a class; when the method is invoked, the compiler picks the correct one based on the arguments passed to the method. This implies that overloaded methods must have different numbers or types of arguments. (In “Overriding methods” on page 159, we’ll look at method overriding, which occurs when we declare methods with identical sig‐

natures in subclasses.)

Method overloading (also called ad hoc polymorphism) is a powerful and useful fea‐

ture. The idea is to create methods that act in the same way on different types of argu‐

ments. This creates the illusion that a single method can operate on many types of arguments. The print() method in the standard PrintStream class is a good exam‐

ple of method overloading in action. As you’ve probably deduced by now, you can print a string representation of just about anything using this expression:

System.out.print( argument )

The variable out is a reference to an object (a PrintStream) that defines nine differ‐

the following types: Object, String, char[], char, int, long, float, double, and boolean.

class PrintStream {

void print( Object arg ) { ... } void print( String arg ) { ... } void print( char [] arg ) { ... } ...

}

You can invoke the print() method with any of these types as an argument, and it’s printed in an appropriate way. In a language without method overloading, this requires something more cumbersome, such as a uniquely named method for print‐

ing each type of object. In that case, it’s your responsibility to figure out what method to use for each data type.

In the previous example, print() has been overloaded to support two reference types: Object and String. What if we try to call print() with some other reference type? Say, a Date object? When there’s not an exact type match, the compiler searches for an acceptable, assignable match. Since Date, like all classes, is a subclass of Object, a Date object can be assigned to a variable of type Object. It’s therefore an acceptable match, and the Object method is selected.

What if there’s more than one possible match? For example, what if we want to print the literal "Hi there"? That literal is assignable to either String (since it is a String) or to Object. Here, the compiler makes a determination as to which match is “better”

and selects that method. In this case, it’s the String method.

The intuitive explanation for this is that the String class is “closer” to the literal "Hi there" in the inheritance hierarchy. It is a more specific match. A slightly more rigor‐

ous way of specifying it would be to say that a given method is more specific than another method if the argument types of the first method are all assignable to the argument types of the second method. In this case, the String method is more spe‐

cific because type String is assignable to type Object. The reverse is not true.

If you’re paying close attention, you may have noticed we said that the compiler resolves overloaded methods. Method overloading is not something that happens at runtime; this is an important distinction. It means that the selected method is chosen once, when the code is compiled. Once the overloaded method is selected, the choice is fixed until the code is recompiled, even if the class containing the called method is later revised and an even more specific overloaded method is added. This is in con‐

trast to overridden methods, which are located at runtime and can be found even if they didn’t exist when the calling class was compiled. In practice, this distinction will not usually be relevant to you, as you will likely recompile all of the necessary classes at the same time. We’ll talk about method overriding later in the chapter.

Dokumen terkait