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
privatemodifier keyword. It indicates that the element is only available to other elements within the class. -
public is indicated by using the
publicmodifier 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
protectedmodifier 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
Gradeclass 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 aGradeclass within thebookpackage, which would make its fully qualified name bebook.Grade. -
In line 6 the field
letteris declaredpublic. This means that any part of the code that happens to have aGradeobjectgcan access that field by sayingg.letter. -
In line 9 the constructor for the
Gradeclass is marked aspublic. That means anyone can call on that constructor to create aGradeobject. -
Lines 14 and 20 declare two public methods. That means anyone with access to a
Gradeobject can call on those methods, by saying something likeg.getPoints(). -
Line 7 indicates that the
pointsfield isprivate. That means that no one outside of the grade class has access to it. Ifgis aGradeobject in some other part of the code, that code cannot sayg.pointswithout hitting a compiler error. However, methods within theGradeclass have no problems accessing points. For example the constructor set an initial value topointsin line 11, and thegetPointsmethod can access that value in line 20, when it saysreturn points.
In this context, we would say that the field
pointshas 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
equalsmethod 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
Gradeclass. Any grade-reporting application would likely need to haveGradeobjects, which would invariably have some string representation even if it is not just aletter, and which would likely have some numericpointsvalue 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 ourcountsForCreditexample. 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.
