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).

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package cs1302.books {

   BookDriver -up-> Book: uses
   class Book {
        -title: String
        +<<new>>Book(title: String, price: double)
        +<<override>>toString: String
   }
   class BookDriver {
        +main(args: String[]): void)
   }

   hide BookDriver fields
}

package cs1302.store {
   StoreDriver -up-> Product: uses

   abstract class Product << abstract >> {
      ~price: double
      #<<new>>Product(price: double)
      +getPrice(): double
      #setPrice(price: double): void)
   }
   class StoreDriver {
      +main(args: String[]): void)
   }

   hide StoreDriver fields
}

Product <|-- Book: extends

Inside Book.java (cs1302.books package) – FIRST APPROACH
public Book(String title, double price) {
    super(price); // <---------------------------- LINE1
    this.title = title;
} // Book
Inside Book.java (cs1302.books package) – SECOND APPROACH
public Book(String title, double price) {
    setPrice(price); // <------------------------- LINE2
    this.title = title;
} // Book
Inside BookDriver.java (cs1302.books package)
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

Product(price)

protected

Product

Book

N

Y

Y

2

setPrice(price)

protected

Product

Book

N

Y

Y

3

setPrice(price)

protected

Product

BookDriver

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.

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package cs1302.books {

   BookDriver -up-> Book: uses
   class Book {
        -title: String
        +<<new>>Book(title: String, price: double)
        +<<override>>toString: String
   }
   class BookDriver {
        +main(args: String[]): void)
   }

   hide BookDriver fields
}

package cs1302.store {
   StoreDriver -up-> Product: uses

   abstract class Product << abstract >> {
      ~price: double
      #<<new>>Product(price: double)
      +getPrice(): double
      #setPrice(price: double): void)
   }
   class StoreDriver {
      +main(args: String[]): void)
   }

   hide StoreDriver fields
}

Product <|-- Book: extends

// 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

setPrice(price)

protected

Product

StoreDriver

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.

hide circle
set namespaceSeparator none
skinparam classAttributeIconSize 0

package cs1302.foo {

   class C {
      #doThis(): void
   }

   hide C fields
}

package cs1302.bar {

   class B {
      #doThat(): void
   }

   hide B fields
}

package cs1302.baz {

   class A {
      +doStuff(): void
   }
}

package cs1302.buz {

   class Driver {
      +main(args: String[]): void)
   }
}
C <|-- B: extends
B <|-- A: extends
A <- Driver: uses

// 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

doThis()

protected

C

A

N

Y

this (implied)

A

Y

2

doThat()

protected

B

A

N

Y

this (implied)

A

Y

3

doStuff()

public

A

Driver

N

Y

a

A

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 variable var itself, and not the type of the object that var 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.