Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override a method with default (package) visibility scope?

My problem is that I can't understand how method resolution works in the following case: Suppose, we have two packages, A and B. There are two classes, A is placed within A, B within B.

A:

package com.eka.IO.a;
import com.eka.IO.b.B;

public class A {

    void foo() {
        System.out.println("parent");
    }

    public static void main(String... args) {
        B obj = new B();
        obj.foo();
    }

}

B:

package com.eka.IO.b;
import com.eka.IO.a.A;

public class B extends A {

    public void foo() {
        System.out.println("child");
    }

}

The code above prints "child", which is perfectly OK. But if I change the method main the following way:

public static void main(String... args) {
    A obj = new B();
    obj.foo();
}

the code prints "parent", and I don't understand why. (obj has runtime type B, B has a public method foo)

Next, I change foo's visibility to public,

public class A {

    public void foo() {

and the code prints "child" again.

As far as I know, instance methods are resolved at runtime, using the following principle:

  1. JVM checks the runtime class of the object.
  2. JVM looks for the method of runtime class
  3. If method is found, JVM calls it, otherwise moves to the parent runtime class.

In my example, in any of three cases, runtime class for obj is always B. B's method foo is always public. Why in the second case JVM calls A's method?

Up: Good answers, but still some things are unclear for me. a) It's the compiler that checks whether a method overrides another method. (Hope, I'm right). b) in case of A obj = new B(); the compiler generates the following code:

INVOKEVIRTUAL com/eka/IO/a/A.foo ()V

b1)if A's foo is declared without modifier (package visibility), then JVM calls A's method. b2)if A's foo is declared public, then JVM calls B's method.

The unclear thing is why in the second case INVOKEVIRTUAL actually calls B.foo. How does it know, that B overrides the method?

like image 530
ekaerovets Avatar asked Nov 03 '14 20:11

ekaerovets


People also ask

Can visibility of a method change in overriding?

The class implementing the interface cannot change the visibility of the method (we cannot change it from public to protected).

Can you override a package private method in Java?

No, we cannot override private or static methods in Java. Private methods in Java are not visible to any other class which limits their scope to the class in which they are declared.

Can we override an interface method with visibility that is not public?

No. We cannot override an interface method if it's visibility is not public. And if it has its visibility as public then you can override it with the same method signature (i.e., with the same access specifier public) whenever you implement the interface to any class.

How do you override a private method?

You cannot override a private or static method in Java. If you create a similar method with same return type and same method arguments in child class then it will hide the super class method; this is known as method hiding. Similarly, you cannot override a private method in sub class because it's not accessible there.


2 Answers

The process is slightly different than you described it. First, Java will only make the methods that exist in the declared class and are visible at the current scope available. This is already done at compile time.

At runtime,

  • JVM checks the runtime class of the object.
  • JVM checks whether the object's runtime class has overridden the method of the declared class.
  • If so, that's the method called. Otherwise, declared class's method is called.

Now, the tricky part is "has it been overridden"?

A class can't override a method that is not visible to it. It can declare a method by the same name and with the same arguments, but this method is not considered to be overriding the original method. It's simply a new method, just like any other method that's defined in B but not in A.

If this was not so, then you could break the parent's class contract at a place where the author thought it should not be broken and therefore did not allow access to it.

So since the class did not override the method, you can only reference that method the same way you'd be able to reference any method declared in B that was not in A - only through a B reference.

Why doesn't the compiler prevent you from using names of methods that are already in the parent class, then?

Well, if you get a package, and the only information you have about it is what's in the classes' contracts, as written in its Javadoc, you won't even know about the existence of that method. All of a sudden, you write a method that, as far as you know, is unique, and you get a compilation error.

There is no reason to do that. What's not visible to you should not prevent you from freely naming your own methods. Thus it is allowed.

But if you want the compiler to prevent you from making mistakes like that, use the @Override annotation whenever you are writing a method that is supposed to override a parent class's method. This way, the compiler will warn you if you are trying to override a method that is not part of the class's contract.

like image 170
RealSkeptic Avatar answered Oct 21 '22 23:10

RealSkeptic


You're experiencing Method Shadowing. From Java Language Specification. Chapter 6. Names. 6.4. Shadowing and Obscuring. 6.4.1. Shadowing (emphasys mine):

Some declarations may be shadowed in part of their scope by another declaration of the same name, in which case a simple name cannot be used to refer to the declared entity

(...)

A declaration d is said to be visible at point p in a program if the scope of d includes p, and d is not shadowed by any other declaration at p.

(...)

A declaration d of a method named n shadows the declarations of any other methods named n that are in an enclosing scope at the point where d occurs throughout the scope of d.

Let's check if B#foo overrides A#foo. From 8.4.8.1. Overriding (by Instance Methods):

An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:

  • A is a superclass of C.
  • C does not inherit mA.
  • The signature of mC is a subsignature (§8.4.2) of the signature of mA.
  • One of the following is true:
    • mA is public. (not your case)
    • mA is protected. (not your case)
    • mA is declared with package access in the same package as C (not your case since the classes are in different packages), and either C declares mC or mA is a member of the direct superclass of C.
    • mA is declared with package access and mC overrides mA from some superclass of C (not your case because there should be another class between C and A that let's you override mA).
    • mA is declared with package access and mC overrides a method m' from C (m' distinct from mC and mA), such that m' overrides mA from some superclass of C (not your case because there should be another class between C and A that let's you override mA).

So, B#foo does not override A#foo by any mean. With this in mind, when you call obj.foo() then foo will be obtained based on the class obj is designated at compile time. The explanation of this part is explained at 15.12. Method Invocation Expressions

If you want to avoid this, mark your method with @Override annotation in the subclass to make sure you're specifically overriding the desired method and not hiding it. If you get a compiler error when annotating your method, then you will know that you're not overriding such method but shadowing it.

As a result from this, you cannot override a method with default scope from a subclass that is in a different package than the parent class. Mark the method as protected in the parent class or redesign your classes accordingly to avoid this scenario.

like image 24
Luiggi Mendoza Avatar answered Oct 21 '22 21:10

Luiggi Mendoza