6

I need an intelligent way to differentiate between multiple objects, all child classes from the same parent class. Consider this parent class and two child classes:

public class Fruit {
    String name;
    public Fruit(String a){
        this.name = a;
    }
}
public class Orange extends Fruit {
    public Orange(String a){
        super(a);
    }
}
public class Apple extends Fruit {
    int price;
    public Apple(String a, int b){
        super(a);
        this.price = b;
    }
    public void checkPrice(){
        if(this.price>2)
            System.out.print("  --  This apple is too expensive!");
        else
            System.out.print("  --  This apple is cheap.");
    }
}

(Also suppose I have a lot more Fruit child classes, not shown here.) Now I want to store all the Fruit in a Fruit Cart:

public class FruitCart {
    Fruit[] cart;
    public FruitCart(){
        cart = new Fruit[4];
        cart[0] = new Apple("Apple #1", 1);
        cart[1] = new Orange("Orange #1");
        cart[2] = new Apple("Apple #2", 3);
        cart[3] = new Apple("Apple #3", 2);
    }
    public void cartInventory(){
        for(int i=0; i<cart.length; i++){
            System.out.print("Item "+i+")  "+cart[i].getClass());
            if(cart[i].getClass().getName().equals("area.Apple")){
                Apple tmp = (Apple)cart[i];
                tmp.checkPrice();
            }
            System.out.println();
        }
    }
}

The cartInventory() method looks for Apple objects and then runs a method specific to Apples. I'll get back to that "how do I recognize an Apple" part in a second, but first, I run the whole program out of main():

public class MainEvent {
    public static void main(String[] args){
        FruitCart cart = new FruitCart();
        cart.cartInventory();
        System.out.println("END OF PROGRAM.");
    }
}

BTW, Output is:

Item 0)  class area.Apple  --  This apple is cheap.
Item 1)  class area.Orange
Item 2)  class area.Apple  --  This apple is too expensive!
Item 3)  class area.Apple  --  This apple is cheap.
END OF PROGRAM.

So the whole thing works fine... but I'm bothered by the mechanism which inspects the Fruit objects and determines if the Fruit is an Apple. The way I do it is pretty clumsy:

if(cart[i].getClass().getName().equals("area.Apple"))

Basically, I call getName() and do a String comparison to see if cart[i] is an Apple or not. Isn't there a smarter way to do this? I keep thinking there must be a way to use cart[i].getClass() somehow... but I don't see a smarter way. Isn't there some way to take advantage of the parent/child relationship between Fruit & Apple?

Any suggestions? Thanks!

Pete
  • 1,511
  • 2
  • 26
  • 49

12 Answers12

5

You could put the method checkPrice in your Fruit class and eventually every child could override it.

public class Fruit {
    String name;
    public Fruit(String a){
        this.name = a;
    }

    // just a default implementation that does nothing
    // (or does whatever you want)
    public void checkPrice(){}

}

Now cartInventory would look like this:

public void cartInventory(){
    for(int i=0; i<cart.length; i++){
        System.out.print("Item "+i+")  "+cart[i].getClass());
        cart[i].checkPrice();
        System.out.println();
    }
}
Loris Securo
  • 7,538
  • 2
  • 17
  • 28
  • This code won't work as parent class doesn't have `checkPrice` method. Use `instanceof` instead. – pms Jan 06 '17 at 21:39
  • But that is even pointless, as he wants to just check the apples and not oranges. The code you suggested needs `checkPrice` of orange to do nothing which is a bad practice.You cannot force people to have a method and do nothing in it – pms Jan 06 '17 at 21:43
  • 1
    @pms if `Fruit` has a method `checkPrice` that does nothing, the childs don't have to implement it, they will inherit the default implementation of `Fruit` – Loris Securo Jan 06 '17 at 21:45
  • @pms Loris is right in general. Maybe not all instances need checkPrice, but the proper way to solve this problem is with inheritance and not some brittle use of instanceOf. – Thom Jan 06 '17 at 21:45
  • @LorisSecuro Again, you cannot gurantee that the child classes do not override the methods and also you cannot guarantee to override empty methods. THAT IS A BAD PRACTICE. – pms Jan 06 '17 at 21:48
  • @TheThom `but the proper way to solve this problem is with inheritance and not some brittle use of instanceOf` please make it clear. This sentence doesn't make sense to me. – pms Jan 06 '17 at 21:49
  • 1
    Having an empty checkPrice method is far less offensive than using instanceOf. An empty method body is not bad practice. Using instanceOf and downcasting is going against object oriented programming principles. – Amir Afghani Jan 06 '17 at 21:52
  • @pms AFAIK it is not bad practice at all, it's the CORRECT practice – Loris Securo Jan 06 '17 at 21:52
  • @LorisSecuro In this case it is a bad practice because you let developers who try to `extend` this class, override `checkPrice` method and when they do that, it will screw up with `cartInventory` method. So you have to fix it. The assumption here is that there is need for check price just on apple instance. I think that is very clear now that why your solution is bad practice here. – pms Jan 06 '17 at 21:56
  • @pms It is bad practice. Obviously, because the need exists, there is a method that needs to be available in the base class so that client classes can identify what's really going on. He's using a simplified example, so I can't look into the problem domain to design the correct solution, but usually instanceOf is used to solve a design flaw in your hierarchy. – Thom Jan 06 '17 at 21:57
  • @TheThom I am just looking at this simplified example and even with that, it is bad practice. We are not talking about designing a suffiisticated system. I don't like to use `instanceOf` either, unless when overriding the `equals` method. – pms Jan 06 '17 at 22:00
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/132585/discussion-on-answer-by-loris-securo-java-a-smarter-way-to-determine-the-child). – Bhargav Rao Jan 07 '17 at 19:41
3

Different things are possible:

For example make class Fruit abstract and add an abstract method checkPrice() which all sub-classes need to implement.

public abstract class Fruit {
    String name;
    public Fruit(String a){
        this.name = a;
    }

    public abstract void checkPrice();
}

The last line makes the compiler force you to implement the checkPrice(); on all sub-classes (Apple and Orange in your case).

Then your method could look like this:

public void cartInventory(){
        for(int i=0; i<cart.length; i++){
                Fruit tmp = (Fruit)cart[i];
                tmp.checkPrice();
        }
    }

You could improve this much more and and avoid the cast (Fruit) by e.g. using Generics, but I think it demonstrates the point. This approach uses the concept of Abstract classes. You can go ahead and also bring an interface into the mix which accomplishes something similar as other answers show.

If you don't want to be forced to implement it in every sub-class, then you could remove the abstract keyword in the parent Fruit class and add a default-implementation there.

As a recommendation I suggest to get familiar with concept of java interfaces vs. abstract class. Both can be mixed to do useful things.

Christoph
  • 3,980
  • 2
  • 40
  • 41
3

I suppose you can check with instanceOf. Eg :

if(cart[i] instanceOf Apple){
// do something
} else if(cart[i] instanceOf Orange){
//do something
}

However in practice it should not be used as its against the OOPS concept.

vk3105
  • 94
  • 3
3

I would avoid using reflection and stick to using polymorphism. You don't even need instanceof. Just declare an empty method on the Fruit class that is only overridden for Apple. Also, you might want to consider moving price up to Fruit.

public class Fruit {
    String name;
    public Fruit(String a){
        this.name = a;
    }

    public void checkPrice() {
       //default implementation is do nothing
    }
}

public class Orange extends Fruit {
    public Orange(String a){
        super(a);
    }      
}

public class Apple extends Fruit {
    int price;
    public Apple(String a, int b){
        super(a);
        this.price = b;
    }

    @Override
    public void checkPrice(){
        if(this.price>2)
            System.out.print("  --  This apple is too expensive!");
        else
            System.out.print("  --  This apple is cheap.");
    }
}

Now You can do:

public void cartInventory(){
    for(int i=0; i<cart.length; i++){
        cart[i].checkPrice();            
    }
}
Amir Afghani
  • 37,814
  • 16
  • 84
  • 124
  • Stole the words (and code) right out of my mouth... err.. fingertips...? I mean... Yes; if you don't want to use `instanceof` + casting, this is definitely the way to go! – nasukkin Jan 06 '17 at 21:47
3

You have to make up your mind whether the so called apple-specific method (in this case checkPrice()) is really specific to Apple. Or it is actually generally applicable to all fruits.

A method that is generally applicable should be declared in the base class

Assuming the answer is yes (in this case it does seems to be yes), then you should declare the method in the base class. In this case you can iterate through all the different types of fruits, and all of them would accept the method checkPrice(), so you don't even need to make a special case for apples.

A method that isn't generally applicable can be declared in an interface

What if the answer is no? Let's assume we need another method called getJuicePrice(), and we further assume that only some fruits can be made into juice (apple juice, orange juice) but other cannot (pineapple? durian?). In this case, a simple solution is to declare an interface, and only the fruits for which the method is appropriate would implement the interface. So let's say this interface is JuiceBehavior

package fruitcart;

import java.math.BigDecimal;

public interface JuiceBehavior {
    BigDecimal getJuicePrice();
}

And all fruits for which juice behavior is applicable (yes for Apple, no for Durian) would implement the interface:

package fruitcart;

import java.math.BigDecimal;

public class Apple implements JuiceBehavior {

    @Override
    public BigDecimal getJuicePrice() {
        // FIXME implement this
        return null;
    }

}

And then in your loop, what you check is whether a fruit is instanceof the interface:

if (fruit instanceof JuiceBehavior) {
    System.out.format("can be made into juice "
        + "with price $ %.2f%n", fruit.getJuicePrice());
} else {
    System.out.format("cannot be made into juice %n");

}

This solution would work for simple cases, but in more complicated cases, you may notice that you start to duplicate a lot of implementation code for getJuicePrice() for different types of fruits. This leads to the next topic

Design Pattern: Strategy

You may want to start thinking about the Design Pattern called Strategy, which further encapsulates JuiceBehavior and make it into a family of classes representing different juice behaviors. It also let you set different types of fruits to take different implementations of JuiceBehavior. I won't go into the details here. But you can read up on that on some books about Design Patterns. Such as

  1. Design Patterns: Elements of Reusable Object-Oriented Software
  2. Head First Design Patterns: A Brain-Friendly Guide
leeyuiwah
  • 6,562
  • 8
  • 41
  • 71
2

As you can see, there are multiple ways you could handle this. To choose which is the right one, you need to know exactly what you want.

If you only ever care about Apples, and do not foresee expanding this functionality beyond Apples, then try this:

if (cart[i] instanceof Apple)

The instanceof keyword is designed for exactly this purpose, to check if an object is an instance of a specific type.

If you care about Apples now, but might want to implement this for other fruit in the future, then you should move the price field into the Fruit class, and add a checkPrice() method to the fruit class that does nothing. This way you aren't forced to implement it in every subclass, but only override it in the ones that you want.

If you want this functionality for all fruit, you could make the checkPrice() method abstract within the Fruit class, but more likely with a method this simple, you would be better off providing a default implementation, and you can override it when necessary.

Sam Hazleton
  • 470
  • 1
  • 5
  • 21
  • But OP said they have a lot more subclasses... you want to manually check every one? I think he's implying that's exactly what OP wanted to avoid – Mat Jones Jan 06 '17 at 21:38
2

Doing getClass().getName() or getClass().equals(Apple.class)is indeed not a very maintainable solution.

You can add public void checkPrice() in the base class : Fruit and provide a empty implementation :

 public class Fruit {
    ...
    public void checkPrice(){}
   }

A check allows checking. If sometimes you check something in other cases as which one you may having nothing to check.
Doing it allows to use the same hierarchy with a common method for all classes of this hierarchy : checkPrice().

In your context, this solution is the better I think.


If you want really to have checkPrice() only available in Apple, you have not the choice, you should be able to distinguish instances which have to perform the check from which one which have no check to perform.
And if you have many children classes which need to have a checkPrice() method, you should not use instanceof or getClass() comparison to identify each class because it is not maintainable. A better solution would be to introduce an interface to address this particularity:

public interface IsPriceChecking{
   void checkPrice();
}

All classes which needs to have this should implement it. For example :

public class Apple extends Fruit implements IsPriceChecking{
   public void checkPrice(){
    if(this.price>2)
        System.out.print("  --  This apple is too expensive!");
    else
        System.out.print("  --  This apple is cheap.");
  }
}

and you could do it from the client side :

public void cartInventory(){
    for(int i=0; i<cart.length; i++){
        if(cart[i] instanceof isPriceChecking){
            IsPriceChecking tmp = (IsPriceChecking)cart[i];
            tmp.checkPrice();
        }
        System.out.println();
    }
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
2

Because I am not so much fan of inheritance, so I implement the code in other way

public interface Fruit {
}

public interface Price {

    public void checkPrice();
}

public class Orange implements Fruit{

}

public class Apple implements Fruit, Price{

    @Override
    public void checkPrice() {
        System.out.println("Apple");
    }

}

public class Cart {

    Fruit[] cart;
    public Cart(){
        cart = new Fruit[4];
        cart[0] = new Apple();
        cart[1] = new Orange();
        cart[2] = new Apple();
        cart[3] = new Apple();
    }

    public void cartInventory() {
        for (Fruit fruit : cart) {
            if (fruit.getClass().isAssignableFrom(Apple.class)) {
                ((Apple)fruit).checkPrice();
            }
        }

    }
}
pms
  • 944
  • 12
  • 28
1

For one example, you can use the instanceof operator.

if(cart[i] instanceof Apple){
    Apple tmp = (Apple)cart[i];
    tmp.checkPrice();
}

You could also implement an isApple function on fruit, which you'd override in Apple class. By definition it would return false but in Apple, it'd return true. I personally prefer the previous one.

But there is a third one, which might be better. Let checkprice() be a method of the parent class. And in each child class you implement it. In Apple it would do what it does now and in others maybe something else, maybe nothing. Up to you.

Among all three, I prefer this one.

sandor
  • 643
  • 6
  • 16
1

You could have the superclass have an abstract method public string GetType() and have each subclass implement it, returning the name of the type. Then, use a switch statement.

Mat Jones
  • 936
  • 1
  • 10
  • 27
1

The instanceof keyword will return true for superclasses as well.

If you want to see if an object is a direct instance of a class, you could compare the class. You can get the class object of an instance via getClass(). And you can statically access a specific class via ClassName.class.

if (a.getClass() == X.class) {
  // do something
}

Here the condition is true if a is an instance of X, but not if a is an instance of a subclass of X.

 if (a instanceof X) {
  // do something
 }

Here the condition is true if a is an instance of X, or if a is an instance of a subclass of X.

jack jay
  • 2,493
  • 1
  • 14
  • 27
1

I would suggest you make an interface.

public interface PriceCheckable {
    public void checkPrice()
}

Then have your apple implement it

public class Apple extends Fruit implements PriceCheckable {
    int price;
    public Apple(String a, int b){
        super(a);
        this.price = b;
    }
    public void checkPrice(){
        if(this.price>2)
            System.out.print("  --  This apple is too expensive!");
        else
            System.out.print("  --  This apple is cheap.");
    }
}

Then in your loop you check the instance, cast and access.

public void cartInventory(){
    for(int i=0; i<cart.length; i++){
        System.out.print("Item "+i+")  "+cart[i].getClass());
        if(cart[i] instanceof PriceCheckable){
            PriceCheckable tmp = (PriceCheckable)cart[i];
            tmp.checkPrice();
        }
        System.out.println();
    }
}

You can then add the pricecheckable to any fruit, product or thing you want.

Read up on java interfaces.

The gist is: An interface is a contract between you and the code.

If an object implements an interface, that object is required to have at least those objects. The code won't compile if they aren't present.

so if there is an interface

public interface Alphabet {
    public void A();
    public int B(int b);
    public int C();
}

Any object that has this method is required to implement the three methods above as they see fit.

sidenote

Usually the methods are followed by comments for intended use, with optional extra limitations and expectations, but one cam more think of those as suggestions.

enter image description here

Although, if you don't keep those suggestions, code that expects certain limitations would break.

Any class that has an interface, can be cast to an object of the interface type.

Alphabet alphabetInstance = (Alphabet)myobject;

You cannot access the normal methods of the myobject object alphabetInstance. The only methods available to the alphabetInstance instance are those outlined in the interface.

It allows you access to the method, but restricts access to to the other methods.

public void foo(MyObject myobject) {
    Alphabet alphabet = null;
    int worth = 10;
    if(MyObject instanceof Alphabet) {
         alphabet = (Alphabet)myobject;
         worth += alphabet.B(alphabet.C());
    }
    myobject.handleWorth(worth);
}

Either you do it like above, have an explicit test if an object is of a certain interface, and handle the interface code in an if statement, or what i prefer, make a special method for just that part.

public void foo(MyObject myobject) {
    Alphabet alphabet = null;
    int worth = 10;
    if(myobject instanceof Alphabet) {
        worth += this.getAlphabetWorth(myobject);  
    }
    myobject.handleWorth(worth);
}

public int getAlphabetWorth(Alphabet alphabet) {
    return alphabet.B(alphabet.C());
}

It might seem like uneccesary work to make an extra method for it, but ther e might be cases where you need more use, and it saves you copy and pasting the code.

Tschallacka
  • 27,901
  • 14
  • 88
  • 133