Druckermann
Druckermann

Reputation: 741

Java inheritance and method resolution order

I've got the following code example:

class p {
    public void druckauftrag() {
        // ...
        drucke();
    }

    public void drucke() {
        System.out.println("B/W-Printer");
    }
}

class cp extends p {
    public void drucke() {
        System.out.println("Color-Printer");
    }
}

Calling the following lines:

  cp colorprinter = new cp();
  cp.druckauftrag();

There is no problem understanding why "cp.druckauftrag();" results in console output "Color-Printer".

But when I call:

    p drucker = (p)colorprinter;
    drucker.druckauftrag();

I get the same output - why? Does the typecast overwrite the object "drucker" 's method "drucke" with "drucke" from colorprinter?

Thanks in advance for every explanation.

Upvotes: 3

Views: 1850

Answers (3)

NotGaeL
NotGaeL

Reputation: 8484

colorprinter does not stop being an instance of cp when you use the cast operator on it, so its implementation of public void drucke() does not change

What you are expressing with your (p)colorprinter casting is the kind of contract (interface) you expect the object colorprinter to satisfy, which includes a public method with the signature public void drucke(), but not any specific implementation.

And, by the way, this casting is already performed implicitly when you declare drucker of the type p, so (p) is redundant in p drucker = (p)colorprinter;. p drucker = colorprinter; will suffice.

Here you can learn more about typecasting.

Keep in mind that it's best practice to extend from abstract classes or interfaces and only @Override (implement) abstract methods. A better design of your code would be:

abstract class BasePrinter {

    public void druckauftrag() {
        // ...
        drucke();
    }

    public void drucke();

}

class p extends BasePrinter {    
    public void drucke() {
        System.out.println("B/W-Printer");
    }
}

class cp extends BasePrinter {
    public void drucke() {
        System.out.println("Color-Printer");
    }
}

But of course constraints don't always allow for that kind of redesign. Passing the base requirements as parameters to the constructor (dependency injection) instead of extending a base class can also be a good alternative:

interface Druckable {
    void drucke();
}

class Druckauftrager {

    Druckable dk;
    Druckauftrager(Drukable dk){
        this.dk = dk;
    }
    public void druckauftrag() {
        // ...
        dk.drucke();
    }

}

class p implements Druckable {    
    public void drucke() {
        System.out.println("B/W-Printer");
    }
}

class cp implements Druckable {
    public void drucke() {
        System.out.println("Color-Printer");
    }
}

Now, if you want to express that a printer requires or can have multiple printing capabilities (like both color and b/w), you just write the class with as much extra Drukable properties and constructor parameters as you want, for example:

class BlackAndWhiteOrColorPrinter {

    p blackAndWhitePrintService;
    cp colorPrintService;

    Druckable selectedPrintService;

    BlackAndWhiteOrColorPrinter (p blackAndWhitePrintService, cp colorPrintService){
        this.blackAndWhitePrintService = blackAndWhitePrintService;
        this.colorPrintService = colorPrintService;
        this.selectedPrintService = blackAndWhitePrintService;
    }

    public void druckauftrag() {
        // ...
        selectedPrintService.drucke();
    }

}

This way, you can even write a class MultiPrinter with a MultiPrinter(List<Druckable> printServices) constructor and add any number of printing modes to its list of printing services: p, cp, and whatever other implementation of Druckable with its public void drucke() comes in the future. It is also extra practical if you want to introduce unit testing, so you can provide mockup objects that force the particular conditions you want to test, like druke() throwing a PaperJamException, for example.

For more information on how interfaces, overriding and inheritance work, see https://docs.oracle.com/javase/tutorial/java/IandI/usinginterface.html

BTW, acording to the latest revision of the official java code conventions guide and also by de facto standard, classes in Java should use CamelCase naming convention. You can also benefit greatly from using semanting naming on all your definitions, like BlackAndWhitePrinter blackAndWhitePrinter and ColorPrinter colorPrinter.

Upvotes: 4

nits.kk
nits.kk

Reputation: 5316

When you create an object using new operator, memory is allocated in heap. Methods and fields are actually there depending upon the concrete actual class of the object. Alter a sub class overrides and modifies a behavior from its super class, invoking the overridden method will always result in the modified behavior. Casting will only mean that the object of sub class is now represented by the super type as the object has a modified behavior for a method will always result in the modified behavior.

Suppose you have below classes

public class Fruit{
   public void taste(){
     System.out.println("depends upon the actual fruit"); 
   }
}

public class Mango extends Fruit{
   @Override
   public void taste(){
     System.out.println("sweet"); 
   }
   public void wayToExposeSuperMethod(){
     super.taste();
   }
}

In other words its like calling mango as a fruit but still mango remains mango. For above code

Fruit fruit = new Mango();

fruit.taste(); // <-- this will output : sweet

((Mango)fruit).taste();// <-- this will output : sweet

fruit.wayToExposeSuperMethod(); // <-- this will not compile

((Mango)fruit).wayToExposeSuperMethod(); // <-- this will output : depends upon the actual fruit

Upvotes: 0

Konstantin Yovkov
Konstantin Yovkov

Reputation: 62864

colorprinter is an instance of cp. Even when you upcast it to p, it's drucke() method will be still the one from cp.

The difference is that after you upcast colorprinter, you will not be able to invoke the methods that cp defines on its own.

Upvotes: 3

Related Questions