2

When I call a.displayName("Test") ,it calls the method of class Icecream. displayName(String...s) method takes in variable arguments. Output-

test Icecream
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

But when I change the method to displayName(String s)(I have commented out that section in the code), then it calls the method of class Faloodeh. New output-

test Faloodeh 
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

I wanted to know why does that happen.

class Icecream{
    public void displayName(String...s){
        System.out.println(s[0]+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

**Edit- ** Thanks for the answers. Please help me with another doubt. I changed the code to -

class Icecream{
    public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String...s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

Now this gives the following output-

test Icecream
test Icecream
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

As you all explained, here b is an object of class Faloodeh. And displayName(String...s) of class Faloodeh doesn't gets override. Still in the output, it displays test Icecream Why so?

Mukund Banka
  • 66
  • 1
  • 7
  • Since its does not match the method signature, the method does not get overridden and hence goes to icecream. – Arun Gowda Apr 05 '20 at 15:28

2 Answers2

5

The key point here is that changing displayName(String... s) to displayName(String s) causes the displayName(String s) method in Faloodeh to override the method in its superclass.

Icecream.displayName(String... s) and Faloodeh.displayName(String s) have different signatures, so they do not override each other. But changing the former to accept one String only causes them to have the same signature, which causes overriding to occur.

In Java, method calls are resolved in roughly three steps (for more info: JLS §15.12, I also explained in more detail here):

  1. Find the class to search for applicable methods. This is based on the compile time type of the object on which you are calling the method. In this case, a. a's compile time type is Icecream, so only Icecream's methods will be considered. Notice that it doesn't find the displayName method in Faloodeh because the compile time type of a is Icecream.
  2. Determine which overload of the method to call based on the arguments you passed. There is only one choice here. As both before and after the change, displayName is the only overload that is compatible with the arguments you passed.
  3. Determine which implementation of the method to call based on the runtime type of the object on which you called the method. a's runtime type is Faloodeh. Before the change, displayName is not overridden in Faloodeh, so it calls the superclass implementation. After the change, displayName becomes overridden, so the implementation in Faloodeh is called.

Regarding your edit:

In this case, since the compile time type of b is Faloodeh, the class to search is Faloodeh (Step 1). However, there are 2 methods that matches the arguments you gave (Step 2):

  • displayName(String...) which is declared in Faloodeh, and;
  • displayName(String) which is inherited.

In such a situation, the compiler always favours the overload without variable arity - displayName(String). This is specified clearly in JLS §15.12.2. In particular, Step 2 is further split into three more sub-steps. The first sub-step tries to find a method without allowing variable arity methods, and if any sub-step finds any method, the remaining sub-steps are skipped.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

Your tests seem to indicate you're having fun with Polymorphism. So, as you might know, in Java, we can say your object possesses two 2 types :

  • A static type: based on the declaration of your variable: Icecream a = .... The Object a has a compile (i.e static) type of Icecream.
  • A dynamic class: based on the affectation of this variable: ... a = new Faloodeh().

When writing code, let's say that you only use the static type. Which means that the compiler is aware of classes/fields/methods you can use, and let you use them. This is the reason why you can write code like :

Icecream a = new Icecream();
a.displayName("test");

And can't write:

Icecream a = new Icecream();
a.unknownMethod("test");

The compiler knows that your class Icecream has a method named displayName that takes a var-arg. It also knows that there is a Faloodeh class. The compiler knows that this class can have its own methods and fields, and have also access to the methods and fields of its parent.

Faloodeh b = new Faloodeh();
b.displayName("test");

So basically, when you declare and implement a method in a class, its children can override the behaviour by re-implementing the method. Which is what you did with the method void describe(String s).

But the method displayName is tricky because you call it the same way, but in reality, they are not the same, because of the args they both take. Let's try to be a compiler. I compiled both of your classes, and here what I have created:

Icecream => displayName | varargs
Icecream => describe | String
Fadooleh => displayName | String
Faloodeh => describe | String

Now, when running the code, let's see what methods you will be really called with the lines in your main:

Icecream a = new Faloodeh();
Faloodeh b = (Faloodeh)a;
a.displayName("test"); => Icecram => displayName | varargs
b.displayName("test"); => Fadooleh => displayName | String
a.describe("test"); => Faloodeh => describe | String
b.describe("test"); => Faloodeh => describe | String

Why is the first line calling the method displayName(String...) and not the displayName(String) ? Because, statically, the compiler saw that you were using the Icecream type to call the displayName method, and dynamically, there is only one method displayName with a varargs, this method has never been override by Faloodeh. Hence, you call directly the Icecream displayName method.

If you uncomment the method displayName(String s) in Icecream, then you have Faloodeh taking over, because, dynamically, the Faloodeh have its own implementation on the displayName(String), which is why it gets called.

Hope it helps. More information on polymorphism: https://www.tutorialspoint.com/java/java_polymorphism.htm

** Edit ** That's pretty much the same reason. Both of your classes have statically a method displayName(String), and another one has the displayName(String...).

When using b.displayName("test"), it will first match the displayName(String), only implemented in your Icecream object. Hence, the behavior.

This will not be possible for instance:

Icecream a = new Faloodeh()
a.displayName("test", "test");

As Icecream doesn't know anything about a method named displayName(String...).

RUARO Thibault
  • 2,672
  • 1
  • 9
  • 14