1

I am working with an API in my company where I would like to create a subclass for an existing object. Here are the caveats:

  • I cannot modify the superclass
  • I cannot modify how the superclass object is instantiated

The example I see most commonly is a Dog as a subclass of Animal, so I will use that. Let's say you have this class in the API:

//API class, which I cannot modify
public class Animal(){
    public void eat(){
        //All animals can do this
    }
}

Now I would like to create a class like this, which adds a few methods to Animal.

//My subclass, which I can modify
public class Dog extends Animal(){
    public void fetch(){
        //Only dogs can do this
    }
}

So now let's say I have an instance of Animal (one that is not a Dog). I essentially need to downcast it into a Dog. I get that downcasting is not directly supported in Java, but is there any workaround for this?

public class AnimalExample{
    public static void main(String[] args){
        Animal animal = MyApi.getAnAnimal();
        //Dog dog = (Dog) animal; ---throws a runtime error
        Dog dog = Dog.getDog(animal); //Maybe something like this?

        //Then I should be able to call both eat() and fetch()
        dog.eat();
        dog.fetch();
    }
}

Again, I understand that downcasting is not directly supported. But there has to be some workaround for this, and I can't figure it out. I know that I could use a wrapper class (eg DogWrapper), but that would be a little more difficult than I'd like because I still frequently call the dozens of superclass methods.

UPDATE: I understand that it's not yet a Dog, but I was wondering if there was a way to convert it into a Dog. It basically sounds like, from what people are saying, that I either have to convert it manually (copy each attribute/method over one-by-one) or just use a Wrapper class. A Wrapper class seems a lot less messy, so unfortunately I'll just have to go that route. So DogWrapper will have a fetch() method and a getAnimal() method. So if I want the Dog to eat, then I have to call dog.getAnimal().eat(). I was avoiding having to do that, but I guess there's no way around it. Does anyone see anything simpler than that?

Jake Fairbairn
  • 183
  • 3
  • 14
  • 2
    "I essentially need to downcast it into a Dog" But it's *not* a `Dog`. You can't ask a non-Dog to behave like a `Dog`. Downcasting *is* supported, but it's done safely... the cast only succeeds if the execution-time type is compatible with the one requested. What would you expect `dog.fetch()` to do? What if it uses some fields declared in `Dog`, which aren't in the actual object? – Jon Skeet Nov 16 '16 at 14:44
  • So, you have some `Animal` which is not a `Dog`, but you want to treat it as if it is a `Dog` anyway. You'll have to convert it to a `Dog` yourself; Java cannot know by itself how to do this. (And why do you think this is useful? If the animal is not a `Dog` then what should it do if you would call a method on it that is specific to `Dog`?). – Jesper Nov 16 '16 at 14:46
  • I would suggest doing something like if(animal instanceof Dog) { Dog dog = (Dog)animal } else { //handle problem case } – mdewit Nov 16 '16 at 14:48
  • I think what you mean is "I want to use the delegate pattern to wrap a non-dog in dog stuff so I can use it as a dog, but I don't want to work hard and write all the delegate methods", right? – RealSkeptic Nov 16 '16 at 14:50
  • I understand that it's not yet a `Dog`, but I was wondering if there was a way to convert it into a `Dog`. It basically sounds like, from what people are saying, that I either have to convert it manually (copy each attribute over one-by-one) or just use a Wrapper class. A Wrapper class seems a lot less messy, so unfortunately I'll just have to go there. So `DogWrapper` will have a `fetch()` method and a `getAnimal()` method. So if I want the `Dog` to eat, then I have to call `dog.getAnimal().eat()`. I was avoiding having to do that, but I guess there's no way around it. – Jake Fairbairn Nov 16 '16 at 15:31

2 Answers2

0

You can have a constructor which takes Animal and instantiates the Dog part of the object with defaults or as required.

public Dog (Animal animal) {
    super(); // any instantiation that has to be done for animal
    // Dog instantiation
   // Use animal properties as required
}
Dog dog = new Dog(animal);

Also having a static method as you mentioned Dog.getDog(animal) is an option, depends on your coding preference.

tinker
  • 1,396
  • 11
  • 20
  • The problem with this is that I'm not using the _original_ `Animal` object anymore. The instance of `Dog` is a brand new `Animal`. So if I were to call `dog.eat()`, it would call the `eat()` method on the `Animal` that was created in `super()`, not the original `Animal` that was created first. – Jake Fairbairn Nov 16 '16 at 15:15
  • @JakeFairbairn what you're saying is not true, the only thing you do with the original animal object is copy any properties that you need, you do a deep copy so that no references from the original object are used. This will result in instantiating a brand new Dog object and when you call dog.eat it will be on the new object – tinker Nov 21 '16 at 23:01
0

Let's suppose I create a method that requires a Dog, but is meant to extend the Animal API. Sure, I could just make the signature like so:

public void doFetch(Dog dog)

But as I said, I want to extend the Animal API. Now, if the given Animal is not a Dog, I can't fetch. With that in mind, I can do the following:

public void doFetch(Animal fetcher) {
    if(fetcher instanceof Dog) {
        Dog dog = (Dog) fetcher;
        ... //Do fetchy things
        return;
    }
    //If we reach this point, then the animal is not a dog
    throw new IllegalArgumentException("fetcher is not a Dog!");
}

Now let's suppose, as in your case, I have an Animal that is not a dog, but I want it to be a Dog for some reason. In this case, I could convert any Animal into a dog using some kind of translator. I prefer to define things like this as static methods in the Dog class itself:

//Breaks the laws of nature by making a Dog from any Animal.
public static Dog fromAnimal(Animal animal) {
     Dog dog = new Dog();
     //Here you would set all the properties of the animal, e.g.:
     dog.setName(animal.getName());
     ...
     return dog;
}
Zircon
  • 4,677
  • 15
  • 32
  • Yeah I thought about making a copy of the `Animal` in a new `Dog` object, but that is really messy. If any changes are made in `Animal`, they are not reflected in `Dog`. Plus there are nearly a hundred setters I'd have to run through, which is ugly already. – Jake Fairbairn Nov 16 '16 at 15:12