📜 ⬆️ ⬇️

The absolute mystery of inheritance in Java

Why is this riddle absolute? For two reasons:
• It relates to the core of the Java language, not some little-known nuance of the API.
• She melted my brain when I stumbled upon it.
If you want to test yourself before further reading, take this test .

To begin, prepare the environment. We will have 3 classes in 2 packages. Classes C1 and C2 will be in the p1 package:
package p1;
public class C1 {
public int m() { return 1;}
}
public class C2 extends C1 {
public int m() { return 2;}
}


* This source code was highlighted with Source Code Highlighter .
Class C3 will be in a separate p2 package:
package p2;
public class C3 extends p1.C2 {
public int m() { return 3;}
}


* This source code was highlighted with Source Code Highlighter .
We also need the test class p1.Main with this main method:
public static void main(String[] args) {
C1 c = new p2.C3();
System. out .println(cm());
}


* This source code was highlighted with Source Code Highlighter .
Note that we call the class 1 method on an instance of the class C3 . As you might guess, this example will display "3".

Now let's change the visibility of m() in all three classes to default visibility:
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() { return 3;}
}


* This source code was highlighted with Source Code Highlighter .
Now the output will be "2"!
Why is this happening? The Main class, which makes the call, does not see the m() method of the C3 class, since it is in a separate package. As for Main , the chain of inheritance ends in C2 . But since C2 is in the same package, its m() method overrides the corresponding C1 method. Not very intuitive, but so it works.

Now let's try something else: change the C3.m() modifier back to public . What happens now?
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
public int m() { return 3;}
}


* This source code was highlighted with Source Code Highlighter .
Now Main sees the C3.m() method. But oddly enough, the result is still "2"!
It seems that C3.m() does not redefine C2.m() . You can think of it this way: the overriding method must have access to the method that it overrides (via super.m() ). However, in this case, C3.m() does not have access to its base method, since it is in a different package. Therefore, C3 is considered part of a completely different chain of inheritance, not one that contains C1 and C2 . If we called C3.m() directly from Main , we would get the expected result "3".
')
Now let's take a look at one more example. protected - an interesting visibility modifier. It behaves as a default modifier for members of the same package and as public for subclasses. What happens if we change all visibility to protected ?
public class C1 {
protected int m() { return 1;}
}
public class C2 extends C1 {
protected int m() { return 2;}
}
public class C3 extends p1.C2 {
protected int m() { return 3;}
}


* This source code was highlighted with Source Code Highlighter .
I reasoned like this: since Main not a subclass of any of our classes, then protected , in this case, should behave the same as the default modifier and the result should be "2." However, it is not. The important point is that C3.m() has access to super.m() and, therefore, the actual output will be "3".
Personally, when I first encountered this situation, I was very confused and could not figure it out until I had studied all the examples. So far, we can conclude that a subclass is part of the chain of inheritance, if and only if you can refer to super.m() from it.

This assumption is also wrong. Consider the following example:
public class C1 {
/*default*/ int m() { return 1;}
}
public class C2 extends C1 {
/*default*/ int m() { return 2;}
}
public class C3 extends p1.C2 {
/*default*/ int m() { return 3;}
}
public class C4 extends p2.C3 {
/*default*/ int m() { return 4;}
}


* This source code was highlighted with Source Code Highlighter .
Note that C4 is in the p1 package. Now change the code of the Main class as follows:
public static void main(String[] args) {
C1 c = new C4();
System. out .println(cm());
}


* This source code was highlighted with Source Code Highlighter .
Then he will print "4". However, super.m() not accessible from C4 : if you add the annotation @Override to C4.m() , the code will not be compiled. At the same time, if we change the main method to
public static void main(String[] args) {
p2.C3 c = new C4();
System. out .println(cm());
}


* This source code was highlighted with Source Code Highlighter .
then the result will again be "3". This means that C4.m() overrides C2.m() and C1.m() , but not C3.m() . This also makes the situation even more confusing, and the correct assumption is as follows: the subclass method overrides the base class method, if and only if the method in the base class is accessible from the subclass . Here, the “base class” refers to any ancestor, not necessarily the direct parent.

The moral of the story is this: although this behavior is described in the specification, it is not intuitive. In addition, there can be not one chain of inheritance, but many, and by changing the visibility modifier from “default” to protected , you can break the code in a completely different place without even knowing it, because several chains inheritance will merge into one.

UPD: use the Override annotation, then the code will not compile in this situation.

Source: https://habr.com/ru/post/62602/


All Articles