Certification Objective Overloading (Exam Objectives 1.5 and 5.4)


Certification Objective —Overloading (Exam Objectives 1.5 and 5.4)

1.5 Given a code example, determine if a method is correctly overriding or overloading another method, and identify legal return values (including covariant returns), for the method.

5.4 Given a scenario, develop code that declares and/or invokes overridden or overloaded methods

Overloading Made Hard—Method Matching

Although we covered some rules for overloading methods in Chapter 2, in this chapter we've added some new tools to our Java toolkit. In this section 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 EasyOver {   static void go(int x) { System.out.print("int "); }   static void go(long x) { System.out.print("long "); }   static void go(double x) { System.out.print("double "); }   public static void main(String [] args) {     byte b = 5;     short s = 5;     long 1 = 5;     float f = 5. Of;     go(b);     go(s);     go(l);     go(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 go() method that takes an int. Of course, the call with the long uses the long version of go(), 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.

You can verify for yourself that if there is only one version of the go() method, and it takes a double, it will be used to match all four invocations of go().

Overloading with Boxing and Var-Args

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

 class AddBoxing {   static void go(Integer x) { System.out.println("Integer"); }   static void go(long x) { System.out.println("long"); }   public static void main(String [] args) {     int i = 5;     go(i);           // which go() will be invoked?   } } 

As we've seen earlier, if the only version of the go() method was one that took an Integer, then Java 5's boxing capability would allow the invocation of go() to succeed. Likewise, if only the long version existed, the compiler would use it to handle the go() 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 pre-existing 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 AddVarargs {   static void go(int x, int y) { System.out.println("int,int");}   static void go(byte... x) { System.out.println("byte ... "); }   public static void main(String[] args) {     byte b = 5;     go(b,b);         // which go() 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, inquiring minds want to know, does boxing beat var-args?

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

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 int parameters. A var-args method is more like a catch-all method, in terms of what invocations it can handle, and as we'll see in Chapter 5, 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:

 Animal a = new Dog(); 

Along the same lines, an invocpation might be:

 class Animal {static void eat() { } } class Dog3 extends Animal {   public static void main(String[] args) {     Dog3 d = new Dog3();     d.go(d);              // is this legal ?   }   void go(Animal a) { } } 

No problem! The go() method needs an Animal, and Dog3 IS-A Animal. (Remember, the go() method thinks it's getting an Animal object, so it will only ask it to do Animal things, which of course anything that inherits from Animal can do.) So, in this case, the compiler widens the Dog3 reference to an Animal, 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.

image from book
Exam Watch

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 Dog4 {   public static void main(String [] args) {     Dog4 d = new Dog4();     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.

image from book

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 WidenAndBox {   static void go(Long x) { System.out.println("Long"); }   public static void main(String [] args) {     byte b = 5;     go(b);           // must widen then box - illegal   } } 

This is just too much for the compiler:

 WidenAndBox.java:6: go(java.lang.Long) in WidenAndBox 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 BoxAndWiden {   static void go(Object o) {     Byte b2 = (Byte) o;        // ok - it's a Byte object     System.out.println(b2);   }   public static void main(String [] args) {     byte b = 5;     go(b);       // can this byte turn into an Object ?   } } 

This compiles (!), and produces the output:

 5 

Wow! Here's what happened under the covers when the compiler, then the JVM, got to the line that invokes the go() 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 go() method got an Object reference that actually refers to a Byte object.

  4. The go() 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 go() 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 WidenAndBox class? Think about itif 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 Vararg {   static void wide_vararg(long... x)               { System.out.println("long..."); }   static void box_vararg(Integer ... x)               { System.out.println("Integer..."); }   public static void main(String [] args) {     int i = 5;     wide_vararg(5,5);    // needs to widen and use var-args     box_vararg(5,5);     // 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. Here's a review of the 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 other than a few rules concerning generics (which we'll cover in Chapter 7), this is all you'll need to know for the exam. Phew!




SCJP Sun Certified Programmer for Java 5 Study Guide Exam 310-055
SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) (Certification Press)
ISBN: 0072253606
EAN: 2147483647
Year: 2006
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net