Showing posts with label method overloading. Show all posts
Showing posts with label method overloading. Show all posts

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

Friday, January 7, 2011

Chapter 8: Object Oriented Concepts - Polymorphism:

Polymorphism can be considered as the ability of one thing being multiple other things (though partially or fully). Am I confusing you? I believe yes. To put it in simpler words, any java object that can pass more than one Is-A test can be considered polymorphic. (Remember the Is-A and Has-A relationships that we looked at in the previous chapter?)
Other than objects of type Object, all Java objects are polymorphic in that they pass the IS-A test for their own type and for class Object.

Remember that the only way to access an object is through a reference variable, and there are a few key things to remember about references:
• A reference variable can be of only one type, and once declared, that type can never be changed (although the object it references can change).
• A reference is a variable, so it can be reassigned to other objects, (unless the reference is declared final).
• A reference variable’s type determines the methods that can be invoked on the object the variable is referencing.
• A reference variable can refer to any object of the same type as the declared reference, or—this is the big one—it can refer to any subtype of the declared type!
• A reference variable can be declared as a class type or an interface type. If the variable is declared as an interface type, it can reference any object of any class that implements the interface.

In the previous chapter on Inheritance we crdriveed a class Car that was extended by two other classes Ferrari and Porsche. Now lets say you want to add a fdriveure “open hood” to the cars. Not all cars have a retractable hood but at the same time you want this fdriveure for certain sports models of Ferrari and Porsche. How would you implement this?

Can we create a class with the openHood() method and only some of the subclasses of Ferrari or Porsche inherit from them? If we can do that then we can implement the feature wherein only selected models of Ferrari or Porsche sports cars can do that. But, unfortunately that wont work in Java. Remember the part on Multiple Inheritance towards the end of the last chapter? You cannot have a class that extends from two classes at the same time. So a code like this would never work:

Public class Porsche911TurboConvertible extends Porsche, Convertible {

}

A class cannot extend more than one class. That means one parent per class. A class can have multiple ancestors, however, since class B could extend class A, and class C could extend class B, and so on. So any given class might have multiple classes up its inheritance tree, but that’s not the same as saying a class directly extends two classes.

So if that doesn't work, what else options do we have? We could simple put the openHood() code inside Car class and disable the method in cars that cant open their hood. But, that is really bad design choice owing to multiple reasons. It makes the code error-prone, makes the class Cars less cohesive and it means that as per the code inside the Car class, all cars can open their hoods but the fact is only certain models can do that.

So what else could you do? You already know the answer—crdrivee a Convertible interface, and have only the Car subclasses that can open their hoods implement that interface. Here’s the interface:

public interface Convertible {
public void openHood();
}

And here’s the modified Porsche class that implements the interface:

class Porsche extends Car implements Convertible {
public void takePorscheForADrive() {
System.out.println("driving a Porsche");
}
public void openHood() {
System.out.println("Opening Hood...");
}
// more code
}

So now we have a Porsche that passes the IS-A test for both the Car class and the Convertible interface. That means a Porsche can be trdriveed polymorphically as one of four things at any given time, depending on the declared type of the reference variable:
• An Object (since any object inherits from Object)
• A Car (since Porsche extends Car)
• A Porsche (since that’s what it really is)
• An Convertible (since Porsche implements Convertible)

The following are all legal declarations. Look closely:
Porsche porscheCar = new Porsche();
Object o = porscheCar;
Car shape = porscheCar;
Convertible mover = porscheCar;

There’s only one object here—an instance of type Porsche—but there are four different types of reference variables, all referring to that one object on the heap.
This is polymorphism – the scenario where one object can be perceived or considered as multiple things.

There are two other important topics that we shall cover in this chapter:
• Method Overloading and
• Method Overriding

Overridden Methods

Any time you have a class that inherits a method from a superclass, you have the opportunity to override the method (unless, as you learned in the earlier chapters, the method is marked final). The key benefit of overriding is the ability to define behavior that’s specific to a particular subclass type. The following example demonstrates a Porsche subclass of Car overriding the Car version of the drive() method:

public class Car {
public void drive() {
System.out.println("Generic Car Driving Generically");
}
}

class Porsche extends Car {
public void drive() {
System.out.println("Porsche driving Full Throttle");
}
}

For abstract methods you inherit from a superclass, you have no choice. You must implement the method in the subclass unless the subclass is also abstract. Abstract methods must be implemented by the concrete subclass, but this is a lot like saying that the concrete subclass overrides the abstract methods of the superclass. So you could think of abstract methods as methods you’re forced to override.

The Car class creator might have decided that for the purposes of polymorphism, all Car subtypes should have an drive() method defined in a unique, specific way. Polymorphically, when someone has an Car reference that refers not to an Car instance, but to an Car subclass instance, the caller should be able to invoke drive() on the Car reference, but the actual runtime object (say, a Porsche instance) will run its own specific drive() method. Marking the drive() method abstract is the Car programmer’s way of saying to all subclass developers, “It doesn’t make any sense for your new subtype to use a generic drive() method, so you have to come up with your own drive() method implementation!” A (non-abstract), example of using polymorphism looks like this:

public class TestCars {
public static void main (String [] args) {
Car a = new Car();
Car b = new Porsche(); //Car ref, but a Porsche object
a.drive(); // Runs the Car version of drive()
b.drive(); // Runs the Porsche version of drive()
}
}
class Car {
public void drive() {
System.out.println("Generic Car Driveing Generically");
}
}
class Porsche extends Car {
public void drive() {
System.out.println("Porsche driving Full Throttle");
}
public void brake() { }
}

In the preceding code, the test class uses a Car reference to invoke a method on a Porsche object. Remember, the compiler will allow only methods in class Car to be invoked when using a reference to a Car. The following would not be legal given the preceding code:

Car c = new Porsche();
c.brake(); // Can't invoke brake();
// Car class doesn't have that method

To reiterate, the compiler looks only at the reference type, not the instance type. Polymorphism lets you use a more abstract supertype (including an interface) reference to refer to one of its subtypes (including interface implementers).

The overriding method cannot have a more restrictive access modifier than the method being overridden (for example, you can’t override a method marked public and make it private). Think about it: if the Car class advertises a public drive() method and someone has an Car reference (in other words, a reference declared as type Car), that someone will assume it’s safe to call drive() on the Car reference regardless of the actual instance that the Car reference is referring to. If a subclass were allowed to sneak in and change the access modifier on the overriding method, then suddenly at runtime—when the JVM invokes the true object’s (Porsche) version of the method rather than the reference type’s (Car) version—the program would die a horrible death. Let’s modify the polymorphic example we saw earlier in this section:

public class TestCars {
public static void main (String [] args) {
Car a = new Car();
Car b = new Porsche(); //Car ref, but a Porsche object
a.drive(); // Runs the Car version of drive()
b.drive(); // Runs the Porsche version of drive()
}
}
class Car {
public void drive() {
System.out.println("Generic Car Driveing Generically");
}
}
class Porsche extends Car {
private void drive() { // whoa! - it's private!
System.out.println("Porsche driving Full Throttle");
}
}
If this code compiled (which it doesn’t), the following would fail at runtime:
Car b = new Porsche(); // Car ref, but a Porsche
// object , so far so good
b.drive(); // Chaos at runtime!


The variable b is of type Car, which has a public drive() method. But remember that at runtime, Java uses virtual method invocation to dynamically select the actual version of the method that will run, based on the actual instance. A Car reference can always refer to a Porsche instance, because Porsche IS-A Car. What makes that superclass reference to a subclass instance possible is that the subclass is guaranteed to be able to do everything the superclass can do. Whether the Porsche instance overrides the inherited methods of Car or simply inherits them, anyone with a Car reference to a Porsche instance is free to call all accessible Car methods. For that reason, an overriding method must fulfill the contract of the superclass.

The rules for overriding a method are as follows:
• The argument list must exactly match that of the overridden method. If they don’t match, you can end up with an overloaded method that you didn’t intend on creating.
• The return type must be the same as, or a subtype of, the return type declared in the original overridden method in the superclass.
• The access level can’t be more restrictive than the overridden method’s. (public to private not allowed)
• The access level CAN be less restrictive than that of the overridden method. (private to public allowed)
• Instance methods can be overridden only if they are inherited by the subclass. A subclass within the same package as the instance’s superclass can override any superclass method that is not marked private or final. A subclass in a different package can override only those non-final methods marked public or protected (since protected methods are inherited by the subclass).
• The overriding method CAN throw any unchecked (runtime) exception, regardless of whether the overridden method declares the exception.
• The overriding method must NOT throw checked exceptions that are new or broader than those declared by the overridden method. For example, a method that declares a FileNotFoundException cannot be overridden by a method that declares a SQLException, Exception, or any other non-runtime exception unless it’s a subclass of FileNotFoundException.
• The overriding method can throw narrower or fewer exceptions. Just because an overridden method “takes risks” doesn’t mean that the overriding subclass’ exception takes the same risks. Bottom line: an overriding method doesn’t have to declare any exceptions that it will never throw, regardless of what the overridden method declares.
• You cannot override a method marked final.
• You cannot override a method marked static.
• If a method can’t be inherited, you cannot override it. Remember that overriding implies that you’re re-implementing a method you inherited! For example, the following code is not legal, and even if you added an drive() method to Porsche, it wouldn’t be an override of Car’s drive() method.

public class TestCars {
public static void main (String [] args) {
Porsche h = new Porsche();
h.drive(); // Not legal because Porsche didn't inherit drive()
}
}
class Car {
private void drive() {
System.out.println("Generic Car Driveing Generically");
}
}
class Porsche extends Car { }

Invoking a Superclass Version of an Overridden Method

Often, you’ll want to take advantage of some of the code in the superclass version of a method, yet still override it to provide some additional specific behavior. It’s like saying, “Run the superclass version of the method, then come back down here and finish with my subclass additional method code.” It’s easy to do in code using the keyword super as follows:

public class Car {
public void drive() { }
public void changeGear() {
// Useful change gear code goes here
}
}
class Porsche extends Car {
public void changeGear() {
// Take advantage of Car code, then add some more
super.changeGear(); // Invoke the superclass (Car) code
// Then do Porsche-specific change gear work here
}
}

Note: Using super to invoke an overridden method only applies to instance methods. (Remember, static methods can’t be overridden.)

If a method is overridden but you use a polymorphic (supertype) reference to refer to the subtype object with the overriding method, the compiler assumes you’re calling the supertype version of the method. If the supertype version declares a checked exception, but the overriding subtype method does not, the compiler still thinks you are calling a method that declares an exception. Let’s take a look at an example:
class Car {
public void drive() throws Exception {
// throws an Exception
}
}
class Car2 extends Car {
public void drive() { /* no Exceptions */}
public static void main(String [] args) {
Car a = new Car2();
Car2 d = new Car2();
d.drive(); // ok
a.drive(); // compiler error - unreported exception
}
}

This code will not compile because of the Exception declared on the Car drive() method. This happens even though, at runtime, the drive() method used would be the Car2 version, which does not declare the exception.

Examples of Illegal Method Overrides

Let’s take a look at overriding the drive() method of Car:
public class Car {
public void drive() { }
}

Let us take a look at a few examples of overridden drive() methods taking the above version of the method in the Car Class

1. private void drive() {} – Access Modifier is more restrictive
2. public void drive() throws IOException {} – Declares a checked exception that is not defined by the super class
3. public void drive (String road) {} – A legal overload but not an override because the arguments to the method has changed (Don't bother about overload just yet, that is the next paragraph)
4. public String drive() {} – Not an override because of the return type and at the same time, not an overload either because there is no change in the argument list.

Overloaded Methods

Experienced java programmers can clearly identify the difference between overloaded methods and the overridden ones. We just had a detailed look at overridden methods and it is time to take a look at the overloaded ones.

Overloaded methods let you reuse the same method name in a class, but with different arguments (and optionally, a different return type). Overloading a method often means you’re being a little nicer to those who call your methods, because your code takes on the burden of coping with different argument types rather than forcing the caller to do conversions prior to invoking your method. The rules are simple:

• Overloaded methods MUST change the argument list.
• Overloaded methods CAN change the return type.
• Overloaded methods CAN change the access modifier.
• Overloaded methods CAN declare new or broader checked exceptions.
• A method can be overloaded in the same class or in a subclass. In other words, if class A defines a doStuff(int i) method, the subclass B could define a doStuff(String s) method without overriding the superclass version that takes an int. So two methods with the same name but in different classes can still be considered overloaded, if the subclass inherits one version of the method and then declares another overloaded version in its class definition.

Tip: Be careful to recognize when a method is overloaded rather than overridden. You might see a method that appears to be violating a rule for overriding, but that is actually a legal overload, as follows:
public class Car {
public void drive(int y, String s) { }
public void brake(int x) { }
}
class Ferrari extends Car {
public void drive(int y, long s) throws IOException { }
}
It’s tempting to see the IOException as the problem, because the overridden drive() method doesn’t declare an exception, and IOException is checked by the compiler. But the drive() method is not overridden! Subclass Ferrari overloads the drive() method, by varying the argument list, so the IOException is fine.

Legal Overloads

Let us say we have the Ferrari class with a method drive() as below:
Public class Ferrari extends Car {
Public void drive() {…}
}

Below are the legal overload method declarations:

1. public void drive(String destination, String roadToTake) {…}
2. public void drive(String destination, int timeToTake) {…}
3. public void drive(String destination) throws RouteNotFoundException {…}
Assuming that the Ferrari class has the above 3 methods, below are is an illegal overload:
1. public String drive (String destination, String roadToTake) {…} – You cannot have two methods which differ just in the return type. This is not allowed

Invoking Overloaded Methods

When you have multiple methods that have the same name, it is sometimes confusing for the programmer who is analysing the code to determine which method would get called and when. But, the good news is that the compiler does not have this dilemma.

When a method is invoked, more than one method of the same name might exist for the object type you’re invoking a method on. For example, the Ferrari class might have three methods with the same name but with different argument lists, which means the method is overloaded. (Check example methods above)

The compiler checks the list of arguments passed to the method before deciding which method to call. Let us say you call the drive method with only the String argument “Destination” the compiler knows exactly that the 3rd method that takes only one String as argument is the method to be invoked. The other 2 methods wouldn't be touched at all during program execution.
Example:

Public class Ferrari extends Car {
public void drive(String destination, String roadToTake) {…}
public void drive(String destination, int timeToTake) {…}
public void drive(String destination) throws RouteNotFoundException {…}
}
Let us say we have the above Ferrari class.

Public class FerrariTest {
Public static void main(String[] args) {
Ferrari car = new Ferrari();
Car.drive(“Central Railway Station”);
}
}

In the preceding code, the FerrariTest class invokes a drive method with just one String argument. So the compiler scans the Ferrari class to see if there is a method drive() that takes one string argument and since it finds one, it invokes it.

Invoking overloaded methods that take object references rather than primitives is a little more interesting. Say you have an overloaded method such that one version takes an Car and one takes a Porsche (subclass of Car). If you pass a Porsche object in the method invocation, you’ll invoke the overloaded version that takes a Porsche. Or so it looks at first glance:

class Car { }
class Porsche extends Car { }
class UseCars {
public void doStuff(Car a) {
System.out.println("In the Car version");
}
public void doStuff(Porsche h) {
System.out.println("In the Porsche version");
}
public static void main (String [] args) {
UseCars ua = new UseCars();
Car CarObj = new Car();
Porsche PorscheObj = new Porsche();
ua.doStuff(CarObj);
ua.doStuff(PorscheObj);
}
}

The output is what you expect:
in the Car version
in the Porsche version

But what if you use an Car reference to a Porsche object?
Car CarRefToPorsche = new Porsche();
ua.doStuff(CarRefToPorsche);

Which of the overloaded versions is invoked? You might want to say, “The one that takes a Porsche, since it’s a Porsche object at runtime that’s being passed to the method.” But that’s not how it works. The preceding code would actually print:
in the Car version

Even though the actual object at runtime is a Porsche and not an Car, the choice of which overloaded method to call (in other words, the signature of the method) is NOT dynamically decided at runtime. Just remember, the reference type (not the object type) determines which overloaded method is invoked! To summarize, which overridden version of the method to call (in other words, from which class in the inheritance tree) is decided at runtime based on object type, but which overloaded version of the method to call is based on the reference type of the argument passed at compile time. If you invoke a method passing it a Car reference to a Porsche object, the compiler knows only about the Car, so it chooses the overloaded version of the method that takes an Car. It does not matter that at runtime there’s actually a Porsche being passed.

Polymorphism in Overloaded and Overridden Methods

How does polymorphism work with overloaded methods? From what we just looked at, it doesn’t appear that polymorphism matters when a method is overloaded. If you pass an Car reference, the overloaded method that takes an Car will be invoked, even if the actual object passed is a Porsche. Once the Porsche masquerading as Car gets in to the method, however, the Porsche object is still a Porsche despite being passed into a method expecting an Car. So it’s true that polymorphism doesn’t determine which overloaded version is called; polymorphism does come into play when the decision is about which overridden version of a method is called. But sometimes, a method is both overloaded and overridden. Imagine the Car and Porsche classes look like this:

public class Car {
public void drive() {
System.out.println("Generic Car Driving Generically");
}
}
public class Porsche extends Car {
public void drive() {
System.out.println("Porsche driving Fast ");
}
public void drive(String s) {
System.out.println("Porsche driving " + s);
}
}

Notice that the Porsche class has both overloaded and overridden the drive() method.

Tip: Don’t be fooled by a method that’s overloaded but not overridden by a subclass. It’s perfectly legal to do the following:
public class Car {
void drive() { }
}
class Ferrari extends Car{
void drive(String s) { }
}

The Ferrari class has two drive() methods: the no-arg version it inherits from Car (and does not override), and the overloaded drive(String s) defined in the Ferrari class. Code with a reference to a Car can invoke only the no-arg version, but code with a reference to a Ferrari can invoke either of the overloaded versions.


Differences between Overloaded and Overridden Methods:

  Overloaded Methods Overridden Methods
Arguments Must Change Must Not Change
Return Type Can Change Cannot Change (Except for Covariant Returns)
Access Can Change Cannot Change to more restrictive (Can be less restrictive)
Invocation Reference type determines which overloaded version (based on declared argument types) is selected. Happens at compile time. The actual method that’s invoked is still a virtual method invocation that happens at runtime, but the compiler will already know the signature of the method to be invoked. So at runtime, the argument match will already have been nailed down, just not the class in which the method lives. Object type (in other words, the type of the actual instance on the heap) determines which method is selected. Happens at runtime.
Constructor Overloading is actually another topic that would be a part of this polymorphism/overloading discussion but we will be covering that as part of one of the subsequent chapters.

Previous Chapter: Chapter 7: Object Oriented Concepts - Inheritance

Next Chapter: Chapter 9: Reference Variable Casting
© 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