9.4. Protected Visibility¶
Instead of saying that something has protected visibility, we usually just say that it’s protected. In Java, only member-level declarations are allowed to be protected. Protected members are slightly more visible than things that are package private; they are only visible from lines of code in the same package or a child class (regardless of its package).
Visibility |
Visible From |
|||
---|---|---|---|---|
Name |
Same Class |
Same Package |
Child Class |
Elsewhere |
protected |
Y |
Y |
Y |
N |
In Java, the
protected
modifier must be included in a member’s declaration for the compiler to consider it a protected member.In UML, the
#
symbol is used just before a member’s identifier to illustrate that it’s protected.The javadoc program includes protected declarations in a documentation website by default. If you don’t want to include protected members, then you can tell javadoc to only include public declarations (as explained in the section on public visibility).
9.4.1. Example 1¶
In this example, we’ll consider two situations where a protected
member is visible and one that’s not. To get started, let’s consider
the UML diagram below and the three code snippets that follow it.
There are two snippets for the Book
class constructor, each
representing an alternative approach (i.e., in reality, we would
see one or the other, but not both).
public Book(String title, double price) {
super(price); // <---------------------------- LINE1
this.title = title;
} // Book
public Book(String title, double price) {
setPrice(price); // <------------------------- LINE2
this.title = title;
} // Book
public static void main(String[] args) {
Book lotr = new Book("The Lord of the Rings", 11.99);
lotr.setPrice(lotr.getPrice() * 0.8); // <---- LINE3
} // main
The visibility situation for each labeled line is summarized in the table below.
Member |
Accessed |
||||||
---|---|---|---|---|---|---|---|
LINE |
Name |
Declared |
In |
From |
Same Package? |
From Child? |
Visible? |
1 |
|
protected |
|
|
N |
Y |
Y |
2 |
|
protected |
|
|
N |
Y |
Y |
3 |
|
protected |
|
|
N |
N |
N |
In LINE1
and LINE2
, the price
variable was not visible
(it’s package private and the labeled lines are attempting access
from another package). The author’s two constructor approaches utilize
indirection to initialize a non-visible inherited member, something
that is discussed further here.
9.4.2. Example 2¶
In this example, we remind ourselves that protected members are visible from the same package. To illustrate this, let’s consider the UML diagram below (same diagram as example 1) and the code snippet that follows it.
// inside StoreDriver.java (cs1302.store package)
public static void main(String[] args) {
Book lotr = new Book("The Lord of the Rings", 11.99);
lotr.setPrice(lotr.getPrice() * 0.8); // <---- LINE1
} // main
The visibility situation for each labeled line is summarized in the table below.
Member |
Accessed |
||||||
---|---|---|---|---|---|---|---|
LINE |
Name |
Declared |
In |
From |
Same Package? |
From Child? |
Visible? |
1 |
|
protected |
|
|
Y |
N |
Y |
9.4.3. Example 3¶
Since a direct parent of a direct parent is still considered a parent in Java, it’s possible for a child class to inherit protected members not originally declared in its direct parent. To illustrate this, let’s consider the UML diagram below and the code snippet that follows it.
// inside A.java (cs1302.baz package)
public void doStuff() {
doThis(); // <------- LINE1
doThat(); // <------- LINE2
} // doStuff
// inside Driver.java (cs1302.buz package)
// assume cs1302.baz.A is imported
public static void main(String[] args) {
A a = new A();
a.doStuff(); // <---- LINE3
} // main
The visibility situation for each labeled line is summarized in the table below.
Member |
Accessed |
Reference |
|||||||
---|---|---|---|---|---|---|---|---|---|
LINE |
Name |
Declared |
In |
From |
Same Package? |
From Child? |
Variable |
Type |
Visible? |
1 |
|
protected |
|
|
N |
Y |
|
|
Y |
2 |
|
protected |
|
|
N |
Y |
|
|
Y |
3 |
|
public |
|
|
N |
Y |
|
|
Y |
The calls on LINE1
and LINE2
to inherited protected members are visible based on
the rules that we have covered so far in this reading. The part that is most noteworthy
is the observation that A
is considered a child of C
by the compiler, even
though it’s not a direct child.
The call to doStuff()
on LINE4
does not involve protected visibility; however,
it is interesting. Although calls to doThis()
and doThat()
would not be visible
on LINE3
, a call to a visible method that has access still works. This is similar
to what we often see with visible “getter” methods that access private instance variables.
9.4.3.1. More on Inheritance and Visibility¶
You may recall from the inheritance-related readings that child classes inherit instance members from their parent. In such a scenario, it’s usually pretty clear that inherited members are declared elsewhere (in the parent class); however, some situations involving overloading, shadowing and initialization can be tricky to determine.
9.4.4. Overload Resolution¶
Since Java allows authors to override an inherited method, it’s possible for there to be multiple declarations that sometimes have different visibilities. While most overrides preserve the visibility of the original declaration, it’s also possible for them to be declared more visible in the child. This can make some situations a little tricky to parse, but the general rule of thumb is this:
If you try to access
var.someMethod
on some line of code, then the visibility that’s used by the compiler is determined by the type of the variablevar
itself, and not the type of the object thatvar
refers to. Java’s dynamic binding [8] will still bind the call to the override that’s closest to the object’s type (e.g., to allow for polymorphism).
Perhaps that’s a little dense. You may find it easier to remember this:
The variable type is used for visibility and the object type is used for binding.
9.4.5. Non-Visible Inherited Members¶
It’s often possible to access non-visible inherited members indirectly via a member that is visible.
For inherited variables, the child class might utilize a visible getter or setter. That usually works so long as the instance variable is not shadowed (i.e., declared again in the child, a practice that is highly discouraged).
For inherited methods, the child class may have access to a visible overload that internally calls the private method.
If we apply the second idea to constructors, then a child class constructor may be able to
access non-visible inherited variables (e.g., to initialize them) using a call to a
visible super()
(or some overload of super
); this works really well when
the parent constructor initializes its own declared instance variables.
This is considered a common pattern that exemplifies separation of concerns and
encapsulation as each class is responsible for its own variables.