Thursday, February 3, 2011

Chapter 20: Overloading In Detail

Overloading is a concept that we looked at in detail in the chapter on Polymorphism. At that time, we did not know much about method arguments, wrapper classes and boxing. But now we do, so it is time to look into Overloading in greater detail. Lets get started!!!

Overloading & Method Matching

In this chapter, we’re going to take a look at three factors that can make overloading a little tricky:
• Widening
• Autoboxing
• Var-args

When a class has overloaded methods, one of the compiler’s jobs is to determine which method to use whenever it finds an invocation for the overloaded method. Let’s look at an example that doesn’t use any new Java 5 features:

class TestOverload {
static void test(int x) { System.out.print("int "); }
static void test(long x) { System.out.print("long "); }
static void test(double x) { System.out.print("double "); }

public static void main(String [] args) {
byte b = 5;
short s = 5;
long l = 5;
float f = 5.0f;

test(b);
test(s);
test(l);
test(f);
}
}

Which produces the output:
int int long double

This probably isn’t much of a surprise; the calls that use byte and the short arguments are implicitly widened to match the version of the test() method that takes an int. Of course, the call with the long uses the long version of test(), and finally, the call that uses a float is matched to the method that takes a double.

In every case, when an exact match isn’t found, the JVM uses the method with the smallest argument that is wider than the parameter.

Overloading with Boxing and Var-args

Now let’s take our last example, and add boxing into the mix:

class TestBoxing {
static void test(Integer x) { System.out.println("Integer"); }
static void test(long x) { System.out.println("long"); }

public static void main(String [] args) {
int i = 5;
test(i);
}
}

Can you guess which test() method would get invoked in the above piece of code?

As we’ve seen earlier, if the only version of the test() method was one that took an Integer, then Java 5’s boxing capability would allow the invocation of test() to succeed. Likewise, if only the long version existed, the compiler would use it to handle the test() invocation. The question is, given that both methods exist, which one will be used? In other words, does the compiler think that widening a primitive parameter is more desirable than performing an autoboxing operation? The answer is that the compiler will choose widening over boxing, so the output will be

long

Java 5’s designers decided that the most important rule should be that preexisting code should function the way it used to, so since widening capability already existed, a method that is invoked via widening shouldn’t lose out to a newly created method that relies on boxing. Based on that rule, try to predict the output of the following:

class TestTestVarArgs {
static void test(int x, int y) { System.out.println("int,int");}
static void test(byte... x) { System.out.println("byte... "); }
public static void main(String[] args) {
byte b = 5;
test(b,b); // which test() will be invoked?
}
}

As you probably guessed, the output is
int,int

Because, once again, even though each invocation will require some sort of conversion, the compiler will choose the older style before it chooses the newer style, keeping existing code more robust. So far we’ve seen that

• Widening beats boxing
• Widening beats var-args

At this point, you may want to know, does boxing beat var-args?

class TestBoxOrTestVarArgs {
static void test(Byte x, Byte y)
{ System.out.println("Byte, Byte"); }
static void test(byte... x) { System.out.println("byte... "); }

public static void main(String [] args) {
byte b = 5;
test(b,b);
}
}

As it turns out, the output is
Byte, Byte

A good way to remember this rule is to notice that the var-args method is “looser” than the other method, in that it could handle invocations with any number of byte parameters. A var-args method is more like a catch-all method, in terms of what invocations it can handle, and it makes most sense for catch-all capabilities to be used as a last resort.

Widening Reference Variables

We’ve seen that it’s legal to widen a primitive. Can you widen a reference variable, and if so, what would it mean? Let’s think back to our favorite polymorphic assignment:

Car a = new Ferrari();

Along the same lines, an invocation might be:
class Car {static void run() { } }

class Ferrari3 extends Car {
public static void main(String[] args) {
Ferrari3 d = new Ferrari3();
d.test(d); // is this legal ?
}
void test(Car a) { }
}

No problem! The test() method needs a Car, and Ferrari3 IS-A Car. (Remember, the test() method thinks it’s getting an Car object, so it will only ask it to do Car things, which of course anything that inherits from Car can do.) So, in this case, the compiler widens the Ferrari3 reference to an Car, and the invocation succeeds. The key point here is that reference widening depends on inheritance, in other words the IS-A test. Because of this, it’s not legal to widen from one wrapper class to another, because the wrapper classes are peers to one another. For instance, it’s NOT valid to say that Short IS-A Integer.

Exam Tip: It’s tempting to think that you might be able to widen an Integer wrapper to a Long wrapper, but the following will NOT compile:

class Ferrari4 {
public static void main(String [] args) {
Ferrari4 d = new Ferrari4();
d.test(new Integer(5)); // can't widen an Integer
// to a Long
}
void test(Long x) { }
}

Remember, none of the wrapper classes will widen from one to another! Bytes won’t widen to Shorts, Shorts won’t widen to Longs, etc.

Overloading When Combining Widening and Boxing

We’ve looked at the rules that apply when the compiler can match an invocation to a method by performing a single conversion. Now let’s take a look at what happens when more than one conversion is required. In this case the compiler will have to widen and then autobox the parameter for a match to be made:

class TestWidenAndBoxing {
static void test(Long x) { System.out.println("Long"); }

public static void main(String [] args) {
byte b = 5;
test(b); // must widen then box - illegal
}
}

This is just too much for the compiler:

TestWidenAndBoxing.java:6: test(java.lang.Long) in TestWidenAndBoxing cannot be
applied to (byte)

Strangely enough, it IS possible for the compiler to perform a boxing operation followed by a widening operation in order to match an invocation to a method. This one might blow your mind:

class TestBoxingAndWiden {
static void test(Object o) {
Byte b2 = (Byte) o; // This is ok - it's a Byte object
System.out.println(b2);
}

public static void main(String [] args) {
byte b = 5;
test(b); // can this run?
}
}

This compiles and produces the output:
5

Surprising, isnt it? This is what happened under the covers when the compiler, then the JVM, got to the line that invokes the test() method:

1. The byte b was boxed to a Byte.
2. The Byte reference was widened to an Object (since Byte extends Object).
3. The test() method got an Object reference that actually refers to a Byte object.
4. The test() method cast the Object reference back to a Byte reference (re member, there was never an object of type Object in this scenario, only an object of type Byte!).
5. The test() method printed the Byte’s value.

Why didn’t the compiler try to use the box-then-widen logic when it tried to deal with the TestWidenAndBoxing class? Think about it...if it tried to box first, the byte would have been converted to a Byte. Now we’re back to trying to widen a Byte to a Long, and of course, the IS-A test fails.

Overloading in Combination with Var-args

What happens when we attempt to combine var-args with either widening or boxing in a method-matching scenario? Let’s take a look:

class TestVarArg {
static void wide_TestVarArg(long... x)
{ System.out.println("long..."); }
static void box_TestVarArg(Integer... x)
{ System.out.println("Integer..."); }
public static void main(String [] args) {
int i = 5;
wide_TestVarArg(i,i); // needs to widen and use var-args
box_TestVarArg(i,i); // needs to box and use var-args
}
}

This compiles and produces:
long...
Integer...

As we can see, you can successfully combine var-args with either widening or boxing.

Exam Tip:
Below are the most important rules for overloading methods using widening, boxing, and var-args:
• Primitive widening uses the “smallest” method argument possible.
• Used individually, boxing and var-args are compatible with overloading.
• You CANNOT widen from one wrapper type to another. (IS-A fails.)
• You CANNOT widen and then box. (An int can’t become a Long.)
• You can box and then widen. (An int can become an Object, via Integer.)
• You can combine var-args with either widening or boxing.

There are more tricky aspects to overloading, but we will be looking at them in future. I know its already too much for a single chapter, so this chapter is over…

Previous Chapter: Chapter 19 - Using Wrappers and Boxing

Next Chapter: Chapter 21: Garbage Collection

No comments:

Post a Comment

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers