is there any application for these? its really hard to learn something if i dont know where am i going to apply what i learn. so im asking for its application
Yes, it can be pretty common and is one of the advantages of using a hierarchical class system. For example, consider a class hierarchy based on simple scientific classification using Genus and Species:
abstract class Animal {
public String kingdom() {
return "Animalia";
}
abstract String genus();
abstract String species();
}
abstract class Vulpes extends Animal {
@Override
public String genus() {
return "Vulpes";
}
}
/** Red fox. */
class VulpesVulpes extends Vulpes {
@Override
public String species() {
return "Vulpes vulpes";
}
public void strokeFur() { ... }
}
/** Swift fox. */
class VulpesVelox extends Vulpes { ... }
/** Fennec fox. */
class VulpesZerda extends Vulpes { ... }
If some method needs to handle animals generically, we would have it take an Animal
as input, but would pass in concrete subclasses of Animal
:
void processAnimals(Animal... animals) {
System.out.println("Processing animals...");
for (Animal animal : animals) {
System.out.printf("order=%s, species=%s%n", animal.order(), animal.species());
}
}
// called like this:
processAnimals(new VulpesVulpes(), new VulpesVelox(), new VulpesZerda());
By default, we have “upcast” to Animal
so we only have access to the methods and public members of the Animal
class, but that is probably what we want anyway. The relatively rare situation where we need to “downcast” is if we need to do something special while doing generic processing. For example, maybe we need to do something special for fennec foxes:
void handleAnimal(Animal animal) {
if (animal instanceof VulpesZerda) {
takePicturesOfAdorableFox((VulpesZerda)animal);
}
feedAnimal(animal);
cleanAnimal(animal);
}
void takePicturesOfAdorableFox(VulpesZerda fennecFox) { ... }
void feedAnimal(Animal animal) { ... }
void cleanAnimal(Animal animal) { ... }
There are ways to restructure your code to require less of this, but upcasting is extremely useful. Downcasting should generally be avoided if possible since it requires a type check to make sure you have the correct type.
Another place we see something similar to upcasting is while referring to types by their interface (where the particular class is an implementation detail). For example, we often see this with collection types:
void someMethod() {
List<Thing> things = new ArrayList<>();
things.add(...);
...
things.get(0);
...
methodThatUsesThings(things);
...
anotherMethodThatUsesThings(things);
}
void methodThatUsesThings(List<Thing> things) { ... }
void anotherMethodThatUsesThings(List<Thing> things) { ... }
Since the specific class is just an implementation detail, we could change it out later if there was a need for different performance characteristics. For example, we can change one line and now it is a linked list:
List<Thing> things = new LinkedList<>();