Reputation: 741
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
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
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
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