Pages

Access modifiers. Private, protected, default, public

Hi! In today's lesson, we'll get acquainted with the concept of access modifiers and consider examples of how to work with them. Access modifiers. Private, protected, default, public - 1Of course, saying 'get acquainted' isn't quite right: you are already familiar with most of them from previous lessons. Just in case, let's refresh our memory of the most important point. Modifiers access are most often keywords that regulate access to different parts of your code. Why 'most often'? Because one of them is set by default without the use of a keyword :) Java has four access modifiers. We list them in order from most restrictive to most 'lenient':
  • private;
  • default (package visible);
  • protected;
  • public.
Let's take a look at each of them and identify when they might be useful. And we'll give examples :)

The private modifier

Access modifiers. Private, protected, default, public - 2private is the most restrictive access modifier. It limits the visibility of data and methods to within a single class. You know this modifier from the lesson about getters and setters. Remember this example?
public class Cat {

   public String name;
   public int age;
   public int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }
}

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();
       cat.name = "";
       cat.age = -1000;
       cat.weight = 0;
   }
}
We considered it in a previous lesson. We made a serious mistake here: We make our data public, which allowed fellow programmers to access the fields directly and change their values. What's more... these values were assigned without any checks. This means that our program could create a cat named "" with an age of -1000 years and weight of 0. To solve this problem, we used getters and setters, and also used the private modifier to limit access to the data.
public class Cat {

   private String name;
   private int age;
   private int weight;

   public Cat(String name, int age, int weight) {
       this.name = name;
       this.age = age;
       this.weight = weight;
   }

   public Cat() {
   }

   public void sayMeow() {
       System.out.println("Meow!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public int getWeight() {
       return weight;
   }

   public void setWeight(int weight) {
       this.weight = weight;
   }
}
Basically, limiting access to fields and implementing getters and setters are the most common examples of how private would be used in real work. In other words, the main purpose of this modifier is to achieve encapsulation in a program. This doesn't apply only to fields, by the way. Imagine that in your program has a method that implements some VERY complex functionality. What can we suggest as an example? Let's say your readDataFromCollider() method accepts as input a data address, reads data from the Large Hadron Collider in byte format, converts this data into text, writes it to a file, and prints it. Even a description of the method looks scary, to say nothing of the code :) To make the code more readable, it would be best to not write all the method's complex logic in one place. Instead, we should break apart the functionality into separate methods. For example, the readByteData() method is responsible for reading data, the convertBytesToSymbols() method converts the data read from the collider into text, the saveToFile() method saves the received text to a file, and the printColliderData() method prints our data file. In the end, our readDataFromCollider() method will be much simpler:
public class ColliderUtil {

   public void readDataFromCollider(Path pathToData) {
       byte[] colliderData = readByteData(pathToData);
       String[] textData = convertBytesToSymbols(colliderData);
       File fileWithData = saveToFile(textData);
       printColliderData(fileWithData);
   }

   public byte[] readByteData(Path pathToData) {

       // Reads data in bytes
   }

   public String[] convertBytesToSymbols(byte[] colliderDataInBytes) {

       // Converts bytes to characters
   }

   public File saveToFile(String[] colliderData) {

       // Saves read data to a file
   }

   public void printColliderData(File fileWithColliderData) {

       // Prints data from the file
   }
}
However, as you'll remember from the lesson about interfaces, the user only gets access to the external interface. And our 4 methods are not part of it. They are helper methods: we created them to improve the readability of the code and to not cram four different tasks into one method. You don't need to give the user access to these methods. If users have access to the convertBytesToSymbols() method when working with the collider, they will most likely simply be confused by the method and wonder what it's for. What bytes are converted? Where did they come from? Why convert them to text? The logic executed in this method is not part of the interface exposed to the user. Only the readDataFromCollider() method is part of the interface. So what do we do with these four 'internal' methods? Right! Use the private modifier to limit access to them. Doing this allows them to peacefully perform their work inside the class without confusing the user, who doesn't need to know the logic of each individual method.
public class ColliderUtil {

   public void readDataFromCollider(Path pathToData) {
       byte[] colliderData = readByteData(pathToData);
       String[] textData = convertBytesToSymbols(colliderData);
       File fileWithData = saveToFile(textData);
       printColliderData(fileWithData);
   }

   private byte[] readByteData(Path pathToData) {
       // Reads data in bytes
   }

   private String[] convertBytesToSymbols(byte[] colliderDataInBytes) {
       // Converts bytes to characters
   }

   private File saveToFile(String[] colliderData) {
       // Saves read data to a file
   }

   private void printColliderData(File fileWithColliderData) {
       // Prints data from the file
   }
}

The package visible modifier

Next on the list is the default modifier, also known as the package visible modifier. It's not indicated by a keyword, since Java applies it by default to all fields and methods. If you write the following in your code:
int x = 10
the x variable will have this package visible access. It's easy to remember what it does. Basically, default = protected inheritance :) Like the protected modifier, its application is limited. Most often, default access is used in a package that has some utility classes that don't implement the functionality of all the other classes in the package. Let's give an example. Imagine that we have a 'services' package. It contains various classes that work with a database. For example, there's a UserService class that reads user data from the database, a CarService class that reads car data from the same database, and other classes, each of which works with specific types of objects and reads corresponding data from the database.
package services;

public class UserService {
}

package services;

public class CarService {
}
But it would be easy for the data in the database to be in one format and we need it in another. Imagine that users' birthdates in the database is stored as <TIMESTAMP WITH TIME ZONE>...
2014-04-04 20:32:59.390583+02
...and instead we need the simplest object — a java.util.Date. To solve this problem, inside the services package, we can create a special Mapper class. It will be responsible for converting data from the database into our familiar Java objects. A simple helper class. We usually declare all classes as public class ClassName, but this isn't a requirement. We can declare our helper class simply as class Mapper. In this case, it still does its job, but it isn't visible to anyone outside the services package!
package services;

class Mapper {
}


package services;

public class CarService {

   Mapper mapper;
}
And here's the basic reasoning: why would anyone outside a package need to see a helper class that only works with the classes in that package?

The protected modifier

The next most restrictive modifier is protected. Access modifiers. Private, protected, default, public - 3Fields and methods marked by the protected access modifier will be visible:
  • within all classes included in the same package as ours;
  • within all classes that inherit our class.
At first, it's hard to imagine when this might be needed. Don't be surprised: there are far fewer use cases for protected than for private, and they are very specific. Imagine that we have an AbstractSecretAgent abstract class that represents a secret agent in some intelligence service, as well as a top_secret package that contains this class and its descendants. Concrete classes such as FBISecretAgent, MI6SecretAgent, MossadSecretAgent, etc. inherit it. Inside the abstract class, we want to implement an agent counter. It will increase when a new agent is created somewhere in the program. package top_secret;
public abstract class AbstractSecretAgent {

   public static int agentCount = 0;
}
But our agents are secret! This means that they and no one else should know how many of them exist. We can easily add the protected modifier to the agent_counter field. Then instances of other secret agent classes and other classes located in our top_secret package can get its value.
public abstract class AbstractSecretAgent {

   protected static int agent_counter = 0;
}
And that's the sort of specialized task that requires the protected modifier :)

The public modifier

And last but not least, the public modifier! You met this modifier on your first day of study on CodeGym the first time you ran public static void main(String[] args). Access modifiers. Private, protected, default, public - 4Now that you've studied the lesson about interfaces, its purpose is obvious to you :) After all, the public modifier was created to give something to users. For example, your program's interface. Suppose you've written a translator program that can translate Russian text into English. You created a translate(String textInRussian) method that implements all the necessary logic. You marked this method with the word public, and now it's part of the interface:
public class Translator {

   public String translate(String textInRussian) {

       // Translates text from Russian to English
   }
}
You can bind this method to the 'Translate' button on the screen and you're done! Anyone can use it. The parts of code marked with the public modifier are intended for the end user. Providing a real-life example, private is for all processes that occur inside a TV, but public is for the buttons on the remote control used to manage the TV. What's more, the user doesn't need to know how the television is built or how it works. The remote control is the set of public-methods: on(), off(), nextChannel(), previousChannel(), increaseVolume(), decreaseVolume() etc.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.