Regarding visibility,
protected
is one of the most misunderstood modifiers in Java. It's always tricky to get it right, and when combined with the default (or package) visibility, it can become really confusing. Here is my attempt to cover every single possibility of these two access levels.
Let's start with some code:
package com.svpino.snippets.white;
public class Parent {
void method0() {
System.out.println("Parent::method0");
}
protected void method1() {
System.out.println("Parent::method1");
}
}
Here we are defining two methods,
method0
has default visibility (since no modifier is specified), and
method1
is protected. So far, so good. Let's see now the other class:
package com.svpino.snippets.white;
public class Child1 extends Parent {
public void method2() {
System.out.println("Child1::method2");
method0();
method1();
}
}
The class
Child1
is inheriting from
Parent
and its located on the same package. This means, that lines 7 and 8 are perfectly valid. Line 7 can be executed because
method0
has package visibility and
Child1
is located on the same
com.svpino.snippets.white
package as
Parent
. Line 8 is valid because
method1
is protected so
Child1
can access it through inheritance.
But, what if we try these methods from a different package? Let's see:
package com.svpino.snippets.black;
import com.svpino.snippets.white.Parent;
public class Child2 extends Parent {
public void method2() {
System.out.println("Child2::method2");
method0(); // Error! This doesn't compile!
method1();
}
}
Now in this case, Line 9 is not valid anymore. We can't call
method0
because we are on a different package than
Parent
. However, Line 10 is still valid since we are accessing a protected member through inheritance.
Now let's spice things up a little bit:
package com.svpino.snippets.white;
import com.svpino.snippets.black.Child2;
public class Neighbor1 {
public void method3() {
Parent parent = new Parent();
parent.method0();
parent.method1();
Child1 child1 = new Child1();
child1.method0();
child1.method1();
child1.method2();
Child2 child2 = new Child2();
child2.method0(); // Error! This doesn't compile!
((Parent) child2).method0();
child2.method1();
child2.method2();
}
}
Line 9 has no problem to compile, since we are on the same package than
Parent
. Line 10 also compiles (and this is interesting) since the protected visibility in Java works the same as the package visibility. Keep that in mind all the time:
A protected member has essentially package-level or default access to all classes except for subclasses.
Moving to the next block, Line 13 and Line 14 will also compile for the same reasons Line 9 and 10 compile respectively. Line 14 is calling a public method from
Child1
so it will also compile. Nothing fancy here.
Let's see what happens in the last block. Line 18 doesn't compile because we are trying to access
method0
through the
Child2
class. If you go back to that class you'll see it's defined in a different package than
Neighbor1
, and because
method0
has default visibility, it won't be visible at this point. However, casting
child2
to a
Parent
will let us access
method0
. The compiler will validate that we do have access to everything defined with package visibility in the
Parent
class, so Line 19 won't be a problem.
Now, think about Line 20. It will compile, but why? It was kind of shocking for me discovering this. Supposedly,
method1
should behave exactly as
method0
since package visibility acts like default visibility, however there's another subtle difference between them. I had to check the
Java specification to find the key:
6.6.1 (...) if the member or constructor is declared protected, then access is permitted only when one of the following is true:
* Access to the member or constructor occurs from within the package containing the class in which the protected member or constructor is declared.
(...)
Now compare that to the following about default access:
6.6.1 (...) Otherwise, we say there is default access, which is permitted only when the access occurs from within the package in which the type is declared.(...)
So the difference is that a protected member will be always accessible when the access occurs from within the package where the class containing the member was declared, meanwhile default access is only possible when the access occurs from the same package where the type (the type of the object we are using to access the member) was declared. In our case, the member
method1
was declared in the package
com.svpino.snippets.white
, and we are accessing it from
Neighbor1
which happens to be on the same package, so the access will be allowed even though we are using
Child2
(the type) to do it.
It sounds complicated, and I had to read it more than once myself to make sense out of it. But if you pay careful attention to the wording, you'll see it. Finally, it's not surprising than Line 21 will compile since
method2
has public access.
The code for our last test is as follows:
package com.svpino.snippets.black;
import com.svpino.snippets.white.Child1;
import com.svpino.snippets.white.Parent;
public class Neighbor2 {
public void method3() {
Parent parent = new Parent();
parent.method0(); // Error! This doesn't compile!
parent.method1(); // Error! This doesn't compile!
Child1 child1 = new Child1();
child1.method0(); // Error! This doesn't compile!
child1.method1(); // Error! This doesn't compile!
child1.method2();
Child2 child2 = new Child2();
child2.method0(); // Error! This doesn't compile!
((Parent) child2).method0(); // Error! This doesn't compile!
child2.method1(); // Error! This doesn't compile!
child2.method2();
}
}
Lines 10 and 11 will not compile since we are accessing them from outside the package where they were defined. Same thing happens with lines 14 and 15, meanwhile line 16 is a simple public access.
Now, line 19 is trying to access a default member that was defined in a different package, so it won't compile. The cast in line 20 won't compile because
Parent
still belongs to a different package, and look how interesting is line 21. We can't access
method1
even though it is inherited by
Child2
from
Parent
. The reason is because
once a subclass outside the package inherits a protected member, that member becomes private to any code outside the subclass with the exception of subclasses of the subclass. Yes, another tongue-twister, so you might want to read it again. Basically, as soon as
method1
is inherited, its visibility becomes private to any outsider (like our
Neighbor2
class).
So that's about it. Impressive how tricky these modifiers can be, specially the
protected
access. It took some time to wrap my head around all these concepts, but now I have it pretty clear.
Hope it helps.
No comments:
Post a Comment