Access Modifiers in Java
You've probably seen the following piece of code:
public static void main(String[] args) {
// some code goes here
}
Ever wondered why the word "public" is used? It means that the main(...)
method is available to everyone. The word public
is an access modifier, a special keyword that specifies who is allowed to use your code or a part of it. It can be placed in front of any field, method, or class.
So, you already know at least one of the access modifiers — public
keyword. The others are: package-private
, protected
, and private
. Why do we need them? Let's find out.
Access Modifiers in use
There are two main reasons to take access modifiers under control: clarity and safety of code.
Code clarity — Imagine your code is the motor of a washing machine with functions such as choosing a washing program or starting a washing process. How can we help the user figure out how to wash their clothes? We can cover the motor with the body and add buttons for choosing a washing mode and starting the process. The user doesn't need to know what is going on inside the machine’s body. The user can press the buttons to run the machine.
That’s how access control helps in code – you can hide the motor from the user by restricting access and simply providing them with the public buttons.
Code safety — Now imagine you have developed a rather useful library that is used by other developers. One day Jane Doe wants to use your code’s functionality in her project, but the problem is that she needs to change a variable in one of your classes. If it is public, nothing can stop her from doing that in her code before using a particular method from the library.
What can happen if the variable is used somewhere else in another method? The second method would probably start to act unpredictably. So, protecting important parts of your code is a guarantee that it will be used as an unmodifiable part and its behavior will be the exact one you have developed it for.
Public and package-private classes
When you manage the access in your code, you divide objects of your program into two groups: top-level and low-level items. Fields and methods that are explicitly used outside the class are called top-level fields and methods. If fields and methods are used inside the class, they are known as low-level fields. This low-level and top-level logic is also applicable to classes.
Usually, using low-level items helps to unload top-level classes, methods, or fields to structure and decompose the code. If these items are not explicitly used, it will be efficient to restrict access to them.
Now let’s see how we can set restrictions to different parts of the code: a top-level class can have one of two following modifiers:
- package-private (default, no explicit modifier) — visible only for classes from the same package;
- public — visible to all classes everywhere.
Here is a class inside the package org.hyperskill.java.packages.theory.p1
. with default package-private access:
package org.hyperskill.java.packages.theory.p1;
class PackagePrivateClass{
}
This class can be used only by other classes from the same package. It's not even visible for classes from any other package:
org.hyperskill
org.hyperskill.java.packages.theory
default package
Note the first two examples – if the class is package-private in package a.b
, it is still unavailable from package a.c
and package a
itself.
Here is a public class inside the package org.hyperskill.java.packages.theory.p2
package org.hyperskill.java.packages.theory.p2;
public class PublicClass {
}
This class has no access restrictions, it is visible to all classes and can be used everywhere:
org.hyperskill
org.hyperskill.java.packages.theory
org.hyperskill.java.packages.theory.p1
default package
The common way of using top-level class modifiers is:
- make the classes containing methods for users (the buttons) public;
- make all other classes with low-level logic methods used by public ones package-private (cover the motor with the body).
Remember: everything that’s not meant to be used/changed by classes from other packages should not be public.
Private members
A class member (a field or a method, e.g. class constructor) has more options to choose from: private, package-private, protected, and public. Let's consider member modifiers in more detail.
Fields are often declared private to control access to them from any other class. In some cases, these fields are only used internally in the class and there is no way to change or even access them from any other class. In other cases, it can be done via accessor methods (e.g. getters and setters). Getter and setter methods are used to protect and hide your data when creating classes. A getter method returns the value of a field, while a setter method sets or updates the value. We will discuss the main features of getter and setter methods in further topics.
Private methods are used to hide the internal low-level logic implementation from the rest of the code and make public methods more brief and readable.
Here is the Counter
class with the private field current
. This field can be read with the method getCurrent()
, a getter method, and changed with the inc()
method. The last one is not exactly a setter method because it doesn't manually set a value to a current
variable, but just increments it.
public class Counter {
private long current = 0;
public long getCurrent() {
return this.current;
}
public void inc() {
inc(1L);
}
private void inc(long val) {
this.current += val;
}
}
Sometimes, a private class constructor is required. This type of constructor can only be used inside the class, e.g. from another constructor, which can be public or private, or from the class methods.
Package-private members
A package-private access modifier does not require any keyword. If a field, a method, or a constructor has this modifier, then it can be read or changed from any class inside the same package.
Let's see an example: here are two classes in the same package: Salary
and Promotion
.
The Salary
class has a package-private field and a constructor. An instance of the Salary
class can be created inside a Promotion
class, and the field can also be accessed by Promotion
and its members because they belong to the same package.
public class Salary {
long income;
Salary(long income) {
this.income = income;
}
}
public class Promotion {
Salary salary;
Promotion(Salary salary) {
this.salary = salary;
}
public void promote() {
salary.income += 1500;
}
}
Protected and public members
A protected access modifier means that a class member can be accessed from classes inside the same package and all subclasses of this class. For now, it is important to remember that the protected option is less restricting than package-private.
A public access modifier means that there is no restriction on using a field, method, or class. It's often used for constructors, and methods representing the class API but not commonly used with fields.
Here are some common questions to understand which access modifier to choose. It is not the ultimate algorithm, but it can help you understand the main use cases of the modifiers.
Let's review the names of access modifiers (from most to least limiting):
- private modifier — available only inside a class;
- package-private (also known as default modifier, implicit) modifier — available for all classes in the same package;
- protected modifier — available for classes in the same package and for subclasses (will be covered later);
- public modifier — available for all classes everywhere.
The table illustrates the level of access provided by the access modifiers: the class always has access to its members, and so on. Note that by a subclass here, we mean only a subclass of this class used in another package.
Remember that only public or default (no keywords) modifiers may be used when declaring non-nested classes. All four of them can be applied to class members: fields, methods, etc.
Now, let's contrast protected
with its adjacent modifiers, private
and "package-private" (default
).
Protected vs default
Think of classes from the same package as neighbors and subclasses as children of a particular class. There are some things you can share or do with your neighbors, for example, discuss a maintenance plan or share the basement. These things and actions would be package-private (default
).
There are also things you can do for children or close friends, like lending money or enjoying a park trip on a Sunday. These actions would fall under the protected
category.
Protected vs private
This distinction is even simpler: if a variable, a method, or an inner class is used solely by the class itself, then it is private
; otherwise, it is protected
. Remember this guideline:
Use the most restrictive access level that fits for a particular member.
If you're unsure about the need for other classes to use the method, start by making it private. You can always grant more access later if needed.
Example
Now let's see how all of this works in practice. In the example below, the package org.hyperskill.bluetooth
has three classes: Laptop
, SmartPhone
, and SmartWatch
. All the gadgets in the package can be connected via Bluetooth. Laptop
has a method named receiveInfo()
, responsible for getting any information from connected gadgets.
package org.hyperskill.bluetooth;
public class Laptop {
private String info;
void receiveInfo(String info) {
this.info = info;
}
}
The Laptop
class has only a single info
field, which is not directly accessible since it is declared as private. But all classes from the same package can access it invoking the receiveInfo
method which is declared as package-private (no modifier).
We consider that SmartPhone
and SmartWatch
classes extend the same MobileGadget
class with the printNotification
method:
package org.hyperskill.bluetooth;
public class MobileGadget {
protected void printNotification(String data) {
System.out.println(data);
}
}
The printNotification
method is accessible for all subclasses of this class as well as for all classes in the same package (including the Laptop
class).
The SmartPhone
class can access the receiveInfo
method of the Laptop
class and the printNotification
method of the MobileGadget
class.
package org.hyperskill.bluetooth;
public class SmartPhone extends MobileGadget {
private Laptop connectedLaptop;
public SmartPhone() {
this.connectedLaptop = new Laptop();
}
private void sendInfoToLaptop(String info) {
printNotification("Sending info to laptop : " + info);
connectedLaptop.receiveInfo(info);
}
}
The SmartWatch
class has a private method countHeartRate
, which is not available to other classes (even from a “brother” class SmartPhone
). It also uses the receiveInfo()
method of the Laptop
class and the parent's printNotification()
method to print the notification:
package org.hyperskill.bluetooth;
public class SmartWatch extends MobileGadget {
private int avgHeartRate;
private Laptop connectedLaptop;
public SmartWatch() {
this.avgHeartRate = 75;
this.connectedLaptop = new Laptop();
}
private int countHeartRate() {
System.out.println("Counting heart rate");
return avgHeartRate;
}
private void sendInfoToLaptop(String info) {
printNotification("Sending info to laptop : " + info);
connectedLaptop.receiveInfo(info);
}
}
We hope all modifiers are clear now!
Conclusion
In this topic, you learned about access modifiers that allow you to determine who will be able to use the code. Using them makes your code safer and clearer. In conclusion, here is a piece of advice: use the most restrictive access level that makes sense for a particular member. Don't make all members public.