Java Arena
Step 3 of 6

Access across packages

Module 34 listed four levels of access: private, nothing, protected and public. While every class was in the default package, only private made a difference. Writing nothing, or protected, let in the whole package, and the whole program was one package. With packages, the other levels start to matter.

public and package-private

A class, or a class's constructor, method or variable, with no word in front of it is package-private: only the classes of its own package can use it. From another package, only what's public can be used. A constructor without public is an easy mistake to make, since until now it changed nothing:

Main.java
import quiz.domain.Question;

public class Main {
    public static void main(String[] args) {
        Question planet = new Question("Which planet is the largest?", "Jupiter", 2);
        System.out.println(planet);
    }
}
quiz/domain/Question.java
package quiz.domain;

public class Question {
    private String prompt;
    private String answer;
    private int points;

    Question(String prompt, String answer, int points) {
        this.prompt = prompt;
        this.answer = answer;
        this.points = points;
    }

    @Override
    public String toString() {
        return this.prompt + " (" + this.points + " points)";
    }
}
What javac prints
Main.java:5: error: Question(String,String,int) is not public in Question; cannot be accessed from outside package
        Question planet = new Question("Which planet is the largest?", "Jupiter", 2);
                          ^
1 error

The message names the constructor by its parameter types, and the fix is public Question(...). The same rule holds for the rest of a class:

  • A method without public gives getPoints() is not public in Question; cannot be accessed from outside package.
  • A class written class Question, without public, can't be used outside its package at all, not even named in an import: Question is not public in quiz.domain; cannot be accessed from outside package.

So a class that other packages use is public, and so are the constructors and methods they call. Instance variables stay private, as always.

Package-private on purpose

Sometimes a class is meant only for its own package. The quiz's Scorer now also finds the best streak, the most right answers in a row. It leaves the counting to a small helper class, Streak, which has no public anywhere:

Main.java
import quiz.domain.Question;
import quiz.logic.Scorer;

public class Main {
    public static void main(String[] args) {
        Question planet = new Question("Which planet is the largest?", "Jupiter", 2);
        Question gold = new Question("What is the chemical symbol for gold?", "Au", 3);
        Question spider = new Question("How many legs does a spider have?", "8", 2);
        Scorer scorer = new Scorer();
        scorer.answer(planet, "Jupiter");
        scorer.answer(gold, "Au");
        scorer.answer(spider, "6");
        scorer.answer(planet, "jupiter");
        System.out.println("Score: " + scorer.getScore());
        System.out.println("Best streak: " + scorer.getBestStreak());
    }
}
quiz/domain/Question.java
package quiz.domain;

public class Question {
    private String prompt;
    private String answer;
    private int points;

    public Question(String prompt, String answer, int points) {
        this.prompt = prompt;
        this.answer = answer;
        this.points = points;
    }

    public boolean isRight(String reply) {
        return reply.trim().equalsIgnoreCase(this.answer);
    }

    public int getPoints() {
        return this.points;
    }
}
quiz/logic/Scorer.java
package quiz.logic;

import quiz.domain.Question;

public class Scorer {
    private int score;
    private Streak streak;

    public Scorer() {
        this.score = 0;
        this.streak = new Streak();
    }

    public boolean answer(Question question, String reply) {
        if (question.isRight(reply)) {
            this.score += question.getPoints();
            this.streak.right();
            return true;
        }
        this.streak.wrong();
        return false;
    }

    public int getScore() {
        return this.score;
    }

    public int getBestStreak() {
        return this.streak.getBest();
    }
}
quiz/logic/Streak.java
package quiz.logic;

class Streak {
    private int current;
    private int best;

    void right() {
        this.current++;
        if (this.current > this.best) {
            this.best = this.current;
        }
    }

    void wrong() {
        this.current = 0;
    }

    int getBest() {
        return this.best;
    }
}
Output
Score: 7
Best streak: 2
  • Scorer and Streak are in the same package, quiz.logic, so Scorer uses everything Streak has.
  • Main can't import Streak or write quiz.logic.Streak: javac would say Streak is not public in quiz.logic; cannot be accessed from outside package. Main works through Scorer's public methods.
  • A package's public classes are what the rest of the program uses. Its package-private classes are details: the package can change or remove them, and nothing outside can break, since nothing outside can use them. It's the idea of private, one level up: from a class to a package.

protected across packages

protected lets in the classes of its package, and also the subclasses, even those in other packages. Say a question's getAnswer() is protected: most classes shouldn't peek at the answer, but a subclass may need it. A question with a hint, in a package quiz.hints of its own, gives the answer's first letter:

Main.java
import quiz.hints.HintedQuestion;

public class Main {
    public static void main(String[] args) {
        HintedQuestion planet = new HintedQuestion("Which planet is the largest?", "Jupiter", 2);
        System.out.println(planet.hint());
        System.out.println(planet.isRight("jupiter"));
    }
}
quiz/domain/Question.java
package quiz.domain;

public class Question {
    private String prompt;
    private String answer;
    private int points;

    public Question(String prompt, String answer, int points) {
        this.prompt = prompt;
        this.answer = answer;
        this.points = points;
    }

    public boolean isRight(String reply) {
        return reply.trim().equalsIgnoreCase(this.answer);
    }

    protected String getAnswer() {
        return this.answer;
    }
}
quiz/hints/HintedQuestion.java
package quiz.hints;

import quiz.domain.Question;

public class HintedQuestion extends Question {
    public HintedQuestion(String prompt, String answer, int points) {
        super(prompt, answer, points);
    }

    public String hint() {
        return "It starts with " + this.getAnswer().charAt(0);
    }
}
Output
It starts with J
true

HintedQuestion is in another package, but it's a subclass of Question, so it may call getAnswer(). Main is neither in quiz.domain nor a subclass: if it called planet.getAnswer(), javac would say getAnswer() has protected access in Question. Subclasses in other packages are common: a class of your own that extends a class of a library is one.

There's one limit. From another package, a subclass may use an object's protected method or variable only through its own type: for example on this, or on a variable whose type is HintedQuestion (or a class that extends it). On a variable of type Question, such as a parameter Question other, javac gives the same getAnswer() has protected access in Question that Main gets, even when the object in it is a HintedQuestion.

The four levels, with packages

Written in front Its own class Its package Subclasses in other packages Everyone else
private yes no no no
nothing yes yes no no
protected yes yes yes, through their own type no
public yes yes yes yes

The table is for classes and their members. A method an interface lists is public even with nothing written in front (module 35).

One more thing: quiz and quiz.domain are two separate packages. The folder domain is inside the folder quiz, but for access that means nothing: a class of the package quiz can't use what quiz.domain keeps package-private.

Your task

Challenge 1 of 3

A vending machine's program keeps its snacks in the package vending.domain, but Main.java doesn't compile: Snack keeps too much to its own package. Make public exactly what Main uses, and nothing more. refill is only for the machine's service classes in vending.domain, so it stays package-private. Don't change Main.java. The program should print:

true
false
Pretzels: 0 left, 150 cents

Challenge 2

The vending company's machines get screens that scroll ads, and the ad screens get a package of their own, vending.ads. Screen, in vending.domain, is ready: it keeps the text it shows in text, which is protected, for its subclasses. A new Screen shows an empty text, getText() returns the text, and clear() empties it.

In vending/ads/AdScreen.java, write the class AdScreen, which extends Screen (import it). It has:

  • a constructor AdScreen(String text), which puts the text on the screen
  • public String scroll(), which moves the text's first character to its end and returns the new text. An empty text stays empty.

The check runs:

AdScreen screen = new AdScreen("Cold drinks! ");
System.out.println(screen.scroll());
System.out.println(screen.scroll());
System.out.println("Screen: " + screen.getText());

and expects:

old drinks! C
ld drinks! Co
Screen: ld drinks! Co

Challenge 3

A laundromat's program has three classes that use Machine, which is in the package laundromat.domain:

  • Technician, in the same package, calls stopNow(), to stop a machine in the middle of a cycle
  • Dryer, a subclass of Machine in the package laundromat.dryers, changes minutesLeft directly, to add time to a running cycle
  • Main, in the default package, creates machines and calls start and pass

In Machine.java, only the class and toString have a word in front of them yet, so the program doesn't compile. Give each variable, the constructor and each method the most restricted access that still works: private for what only Machine itself uses, nothing for what only its own package uses, protected for what a subclass in another package needs, and public for what Main uses. Change only Machine.java. The program should print:

Machine 4: 25 minutes left
Machine 9: 25 minutes left
Machine 4: free