Skip to main content

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 is public. So it can be accessed from any other part of the code base. Every part of the application can know that there is a Grade class within the book package, which would make its fully qualified name be book.Grade.
  • In line 6 the field letter is declared public. This means that any part of the code that happens to have a Grade object g can access that field by saying g.letter.
  • In line 9 the constructor for the Grade class is marked as public. That means anyone can call on that constructor to create a Grade 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 like g.getPoints().
  • Line 7 indicates that the points field is private. That means that no one outside of the grade class has access to it. If g is a Grade object in some other part of the code, that code cannot say g.points without hitting a compiler error. However, methods within the Grade class have no problems accessing points. For example the constructor set an initial value to points in line 11, and the getPoints method can access that value in line 20, when it says return 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 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 have Grade objects, which would invariably have some string representation even if it is not just a letter, and which would likely have some numeric points value associated with them, and it would certainly make sense for these objects to have a method like countsForCredit. 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 our countsForCredit 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.
Table 7.4.1. Kinds of classes and their use of fields and accesors
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.