Certification Objective Overriding Overloading (Exam Objectives 1.5 and 5.4)


Certification Objective —Overriding / 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 and code that declares and/or invokes superclass, overridden, or overloaded constructors.

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 earlier, 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 Horse subclass of Animal overriding the Animal version of the eat() method:

 public class Animal {    public void eat() {       System.out.println("Generic Animal Eating Generically");    } } class Horse extends Animal {    public void eat() {        System.out.println("Horse eating hay, oats, "                            + "and horse treats");    } } 

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

 public class TestAnimals {   public static void main (String [] args) {     Animal a = new Animal();     Animal b = new Horse();  //Animal ref, but a Horse object     a.eat(); // Runs the Animal version of eat()     b.eat(); // Runs the Horse version of eat()   } } class Animal {   public void eat () {     System.out.println("Generic Animal Eating Generically"};   } } class Horse extends Animal {   public void eat() {     System.out.printin("Horse eating hay, oats, "                        + "and horse treats");    }    public void buck() { } } 

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

 Animal c = new Horse(); c.buck();  // Can't invoke buck();            // Animal 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 protected). Think about it: if the Animal class advertises a public cat() method and someone has an Animal reference (in other words, a reference declared as type Animal), that someone will assume it's safe to call eat() on the Animal reference regardless of the actual instance that the Animal 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 (Horse) version of the method rather than the reference type's (Animal) version—the program would die a horrible death. (Not to mention the emotional distress for the one who was betrayed by the rogue subclass.) Let's modify the polymorphic example we saw earlier in this section:

 public class TestAnimals {   public static void main (String [] args) {     Animal a = new Animal();     Animal b = new Horse();  //Animal ref, but a Horse object     a.eat(); // Runs the Animal version of eat()     b.eat(); // Runs the Horse version of eat()   } } class Animal {   public void eat() {     System.out.println("Generic Animal Eating Generically");   } } class Horse extends Animal {   private void eat() {  // whoa! - it's private!     System.out.println("Horse eating hay, oats, "                        + "and horse treats");   } } 

If this code compiled (which it doesn't), the following would fail at runtime:

 Animal b = new Horse();  // Animal ref, but a Horse                          // object , so far so good b.eat();                 // Meltdown at runtime! 

The variable b is of type Animal, which has a public eat() 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. An Animal reference can always refer to a Horse instance, because Horse IS-A(n) Animal. 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 Horse instance overrides the inherited methods of Animal or simply inherits them, anyone with an Animal reference to a Horse instance is free to call all accessible Animal 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 you didn't intend.

  • The return type must be the same as, or a subtype of, the return type declared in the original overridden method in the superclass. (More on this in a few pages when we discuss covariant returns.)

  • The access level can't be more restrictive than the overridden method's.

  • The access level CAN be less restrictive than that of the overridden method.

  • 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. (More in Chapter 5.)

  • 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. We'll look at an example in a few pages when we discuss static methods in more detail.

  • If a method can't be inherited, you cannot override it. Remember that overriding implies that you're reimplementing a method you inherited! For example, the following code is not legal, and even if you added an eat() method to Horse, it wouldn't be an override of Animal's eat() method.

 public class TestAnimals {    public static void main (String [] args) {       Horse h =  new Horse();       h.eat(); // Not legal because Horse didn't inherit eat()    } } class Animal {    private void eat() {       System.out.println("Generic Animal Eating Generically");    } } class Horse extends Animal { } 

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." (Note that there's no requirement that the superclass version run before the subclass code.) It's easy to do in code using the keyword super as follows:

 public class Animal {    public void eat() { }    public void printYourself() {       // Useful printing code goes here    } } class Horse extends Animal {    public void printYourself() {       // Take advantage of Animal code, then add some more       super.printYourself();  // Invoke the superclass                               // (Animal) code                               // Then do Horse-specific                               // print work here    } } 

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

image from book
Exam Watch

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 (more in Chapter 5). Let's take a look at an example:

 class Animal {   public void eat() throws Exception {     // throws an Exception   } } class Dog2 extends Animal {   public void eat() { // no Exceptions }   public static void main(String [] arga) {     Animal a = new Dog2();     Dog2 d = new Dog2();     d.eat();           // ok     a.eat();           // compiler error -                        // unreported exception   } } 

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

image from book

Examples of Legal and Illegal Method Overrides

Let's take a look at overriding the eat() method of Animal:

 public class Animal {    public void eat () { } } 

Table 2-1 lists examples of illegal overrides of the Animal eat() method, given the preceding version of the Animal class.

Table 2-1: Examples of Illegal Overrides

Illegal Override Code

Problem with the Code

private void eat () { }

Access modifier is more restrictive

public void eat() throws IOException { }

Declares a checked exception not defined by superclass version

public void eat (String food) { }

A legal overload, not an override, because the argument list changed

public string eat() { }

Not an override because of the return type, not an overload either because there's no change in the argument list

Overloaded Methods

You're wondering what overloaded methods are doing in an OO chapter, but we've included them here since one of the things newer Java developers are most confused about are all of the subtle differences between overloaded and overridden methods.

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.

image from book
Exam Watch

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 Foo {    public void doStuff(int y, String s) { }    public void moreThings(int x) { } } class Bar extends Foo {    public void doStuff(int y, long s) throws IOException { } } 

It's tempting to see the IOException as the problem, because the overridden dostuff() method doesn't declare an exception, and IOException is checked by the compiler. But the dostuff() method is not overridden! Subclass Bar overloads the dostuff() method, by varying the argument list, so the IOException is fine.

image from book

Legal Overloads

Let's look at a method we want to overload:

 public void changeSize(int size, String name, float pattern) { } 

The following methods are legal overloads of the changeSize() method:

 public void changeSize(int size, String name) { } public int changeSize(int size, float pattern) { } public void changeSize(float pattern, String name)                        throws IOException { } 

Invoking Overloaded Methods

Note that there's a lot more to this discussion on how the compiler knows which method to invoke, but the rest is covered in Chapter 3 when we look at boxing and var-args—both of which have a huge impact on overloading. (You still have to pay attention to the part covered here, though.)

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 Horse class might have three methods with the same name but with different argument lists, which means the method is overloaded.

Deciding which of the matching methods to invoke is based on the arguments. If you invoke the method with a String argument, the overloaded version that takes a String is called. If you invoke a method of the same name but pass it a float, the overloaded version that takes a float will run. If you invoke the method of the same name but pass it a Foo object, and there isn't an overloaded version that takes a Foo, then the compiler will complain that it can't find a match. The following are examples of invoking overloaded methods:

 class Adder {   public int addThem(int x, int y) {     return x + y;   }   // Overload the addThem method to add doubles instead of ints   public double addThem(double x, double y) {     return x + y;   } } // From another class, invoke the addThem() method public class TestAdder {   public static void main (String [] args) {     Adder a = new Adder();     int b = 27;     int c = 3;     int result = a.addThem(b,c); // Which addThem is invoked?     double doubleResult = a.addThem(22.5,9.3); // Which addThem?    } } 

In the preceding TestAdder code, the first call to a.addThem(b, c) passes two ints to the method, so the first version of addThem()—the overloaded version that takes two int arguments—is called. The second call to a.addThem(22.5, 9.3) passes two doubles to the method, so the second version of addThem()—the overloaded version that takes two double arguments—is called.

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 Animal and one takes a Horse (subclass of Animal). If you pass a Horse object in the method invocation, you'll invoke the overloaded version that takes a Horse. Or so it looks at first glance:

 class Animal { } class Horse extends Animal { } class UseAnimals {    public void doStuff(Animal a) {       System.out.println("In the Animal version");    }    public void doStuff(Horse h) {       System.out.println("In the Horse version");    }    public static void main (String [] args) {       UseAnimals ua = new UseAnimals();       Animal animalobj = new Animal();       Horse horseobj = new Horse();       ua.doStuff(animalobj);       ua.doStuff(horseobj);    } } 

The output is what you expect:

 in the Animal version in the Horse version 

But what if you use an Animal reference to a Horse object?

 Animal animalRefToHorse = new Horse();  ua.doStuff(animalRefToHorse); 

Which of the overloaded versions is invoked? You might want to say, "The one that takes a Horse, since it's a Horse 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 Animal version 

Even though the actual object at runtime is a Horse and not an Animal, 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 an Animal reference to a Horse object, the compiler knows only about the Animal, so it chooses the overloaded version of the method that takes an Animal. It does not matter that at runtime there's actually a Horse 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 Animal reference, the overloaded method that takes an Animal will be invoked, even if the actual object passed is a Horse. Once the Horse masquerading as Animal gets in to the method, however, the Horse object is still a Horse despite being passed into a method expecting an Animal. 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 Animal and Horse classes look like this:

 public class Animal {    public void eat () {       System.out.println("Generic Animal Eating Generically"};    } } public class Horse extends Animal {    public void eat() {        System.out.println("Horse eating hay ");    }    public void eat(String s) {       System, out .println ("Horse eating " + s) ;    } } 

Notice that the Horse class has both overloaded and overridden the eat() method. Table 2-2 shows which version of the three eat() methods will run depending on how they are invoked.

Table 2-2: Examples of Illegal Overrides

Method Invocation Code

Result

Animal a = new Animal(); a.eat();

Generic Animal Eating Generically

Horse h = new Horse(); h.eat();

Horse eating hay

Animal ah = new Horse (); ah.eat();

Horse eating hay

Polymorphism works—the actual object type (Horse), not the reference type (Animal), is used to determine which eat() is called.

Horse he = new Horse(); he.eat("Apples") ;

Horse eating Apples

The overloaded eat(String s) method is invoked.

Animal a2 = new Animal(); a2.eat ("treats");

Compier error! Compiler sees that Animal class doesn't have an eat() method that takes a String.

Animal ah2 = new Horse(); ah2.eat("Carrots");

Compiler error! Compiler still looks only at the reference, and sees that Animal doesn't have an eat() method that takes a String. Compiler doesn't care that the actual object might be a Horse at runtime.

image from book
Exam Watch

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 Foo {    void doStufff() { } } class Bar extends Foo {    void doStuff(String s) { } } 

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

image from book

Table 2-3 summarizes the difference between overloaded and overridden methods.

Table 2-3: Differences Between Overloaded and Overridden Methods
 

Overloaded Method

Overridden Method

Argument(s)

Must change.

Must not change.

Return type

Can change.

Can't change except for covariant returns.

Exceptions

Can change.

Can reduce or climinate. Must not throw new or broader checked exceptions.

Access

Can change.

Must not make 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.

The current objective (5.4) covers both method and constructor overloading, but we'll cover constructor overloading in the next section, where we'll also cover the other constructor-related topics that are on the exam. Figure 2-4 illustrates the way overloaded and overridden methods appear in class relationships.

image from book
Figure 2-4: Overloaded and overridden methods in class relationships




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