Section 7.4 Encapsulation, access modifiers, and class kinds
Subsection 7.4.1 Encapsulation and access modifiers
It is time to discuss an important idea related to all programming in general, but which is especially true for object-oriented programming, and that is the idea of encapsulation.
Encapsulation of data is any mechanism that binds together certain data with the code that operates on that data. In a class-based setting, this is simply the idea that we can place our data in fields, and the code that can access these fields is right there in the same file, as part of the same class.Encapsulation is about restricting access to our data to a well-defined portion of the code.
In Java in particular, though similar concepts exist in other languages, we achieve this at a first level by providing an access modifier to each field, method or class. This modifier is there to indicate to the Java ecosystem which other parts of our program should have access to this element. There are four levels of access available in Java:
- private is indicated by using the
private
modifier keyword. It indicates that the element is only available to other elements within the class. - public is indicated by using the
public
modifier keyword. It indicates that the element is available from anywhere in the code base. - package-private is indicated by not using any modifier. It indicates that the element is available from other elements within the same package. We haven’t formally specified packages, but for now think of them simply as folders that you have used to group your files. Other files within the same folder will be able to access this element.
- protected is indicated by using the
protected
modifier keyword. It is used to indicate something that should be accessible from any subclass. It normally has limited use.
Let’s look at a quick example from earlier:
package book;
import java.util.function.Consumer;
public class Grade {
public String letter;
private double points;
public Grade(String letter, double points) {
this.letter = letter;
this.points = points;
}
public boolean countsForCredit() { return letter.equals("W"); }
void doIfCountsForCredit(Consumer<Double> f) {
if (countsForCredit()) f.accept(getPoints());
}
public double getPoints() { return points; }
}
Notice that this code is part of the package
book
. It resides in a file within a folder namedbook
, and the file itself is called Grade.java
.- The class declaration in line 5 indicates that the
Grade
class itself ispublic
. So it can be accessed from any other part of the code base. Every part of the application can know that there is aGrade
class within thebook
package, which would make its fully qualified name bebook.Grade
. - In line 6 the field
letter
is declaredpublic
. This means that any part of the code that happens to have aGrade
objectg
can access that field by sayingg.letter
. - In line 9 the constructor for the
Grade
class is marked aspublic
. That means anyone can call on that constructor to create aGrade
object. - Lines 14 and 20 declare two public methods. That means anyone with access to a
Grade
object can call on those methods, by saying something likeg.getPoints()
. - Line 7 indicates that the
points
field isprivate
. That means that no one outside of the grade class has access to it. Ifg
is aGrade
object in some other part of the code, that code cannot sayg.points
without hitting a compiler error. However, methods within theGrade
class have no problems accessing points. For example the constructor set an initial value topoints
in line 11, and thegetPoints
method can access that value in line 20, when it saysreturn points
.
In this context, we would say that the field
points
has been encapsulated: we have restricted the direct access to it. No one outside of the class has direct access to this field. We have therefore grouped together the data (the points
field) and the code that can access it (the various methods in the class). And these methods are the only way in which the points
field can be used.This has many advantages, the most important of which is perhaps that we are free to change the internal representation of this data without requiring the entire program to change, as long as we don’t change the interface of the methods used to access this data. For example perhaps instead of storing the points in a field, we have some code that dynamically computes the points from the letter. Then we might not even have a field at all, but the
getPoints
method will continue to do its work of returning the amount of points that a grade is worth, and users of the grade class who have relied on the getPoints
method will continue to work just fine.In many settings this idea of encapsulation is used somewhat incorrectly: In an effort to control the access to a field, we make the field private, but then we provide
getXXX
and setXXX
methods, which are then used to get and set the value of the field (these methods are typically called getters and setters). And somehow suddenly all is well with the world! Problem solved, right? The field is private! But nothing can be further from the truth.If you are effectively allowing someone to freely get and set the value you have in your field, then you have not truly isolated that field from the rest of the world. You do have more control over knowing when someone accesses the field, but you let them mess with it by calling the setters! You have to ask yourself whether the rest of the world needs to know about this field in the first place: Just because you have a field called
points
, that doesn’t mean you need to automatically let the rest of the world know about it via getPoints
and setPoints
methods.Only provide access to private fields in your class if the rest of your application truly needs to know about them. Choose what parts of the internal details of your class you truly want to share with the world.
After all, just because you have a date of birth, social security number and an email password, that doesn’t mean that you should freely share these pieces of information with the rest of the world.
Treat fields and methods in your class the same way: only share what is truly needed, and keep the rest hidden. This is usually known as information hiding. We will touch on it again later.
Subsection 7.4.2 Kinds of classes
Now, with all this in mind, we can roughly classify the classes in our application broadly in three categories:
- First we have the "classes" that in most other languages would not even be classes, but something typically called "structures". These classes simply put together some data, and they are used solely to keep that data together as it travels through the system. They are therefore used to pass around multiple pieces of information as if it was one thing. I like to call these data transfer objects. These don’t need to do any kind of encapsulation: they can keep their fields public, and typically even final (cannot be modified after initialization), and they don’t need any methods except perhaps for an
equals
method to identify two such objects as equal if all their data fields are equal. These are also called value objects, because they truly contain just values, no logic or state. Modern versions of java provide a Record construct that can be used for these kinds of classes, but we will not be using it. - Next we have objects that I will call model/domain objects. These objects represent core concepts in our application’s domain model, in other words any program in the same problem domain will likely have these objects with these methods. A good example would be our
Grade
class. Any grade-reporting application would likely need to haveGrade
objects, which would invariably have some string representation even if it is not just aletter
, and which would likely have some numericpoints
value associated with them, and it would certainly make sense for these objects to have a method likecountsForCredit
. In general these objects will contain fields that are encapsulated but still somewhat accessible to the world via getters and possibly setters. And they will have other helper methods that describe domain concepts, like ourcountsForCredit
example. These are objects often referred to as entities. - Lastly we have objects that I will call interaction objects. They don’t represent some core concept, but instead hold some responsibilities related to our application problem. A class that knows how to read grades from a file might fit into this description. These objects are specific to our application and what our application is trying to accomplish, and they do this by coordinating with other objects and manipulating domain objects. They will tend to have completely private fields that hold internal information helping them do their job, that no outside entities need to know about. They will therefore tend to not have publicly available getters and setters for these fields. A good example is our imaginary "read-grades-from-a-file" class. It will likely need to store information regarding that file access, for example. No one else needs to know about that.
Kind of class | Fields | Getters/Setters |
---|---|---|
data-transfer objects | public | not needed |
model/domain objects | private | most likely |
interaction objects | private | unlikely |
Each class in our program will typically fit into one of these categories. Use that as a guide to determine what access to give to the fields, and how to structure the class overall.