In Java, there are a set of access modifiers. You can find a complete list of these modifiers and their meanings and use in the Java Reference material on the Access Modifiers page. An access modifier defines the accessibility of a class, method, or data member. In other words, the access modifier will indicate where a programmer can reference or refer to a particular class, method, or data member.
For example, the main() method in a Java program is defined as public. This means that the main method may be accessed (called, invoked) anywhere in the program's code, and also anywhere in another program's code.
A class that is defined as "public" may be instantiated by any other class. Applets must be defined as public classes because they have to be instantiated by a user's browser. A class can be defined with no modifier, such as this one:
class MyMessage { // ... class code goes here ... }
This class is not available to all other classes; it is only available to classes in the same package. We refer to this as default, friendly, or package access. For our use, this means that this class is available only to classes in the same folder. This is technically slightly incorrect, but we'll talk about this when we learn to create our own packages later on.
A method that is defined as public can be invoked or called by any other code in the class or program where the method is defined, or even from other classes and programs. For example, a class MyMessage has a public method called "displayMessage()":
public class MyMessage { public String message; public void displayMessage() { System.out.println(message); } }
This displayMessage() method can be accessed by another Java class or program:
public class TestMyMessage { public static void main(String[] args) { MyMessage hello = new MyMessage(); hello.message = "Hello, World!"; displayMessage(); } }
An instance variable defined as public can be read or written to by any other class or program. This is usually an undesirable situation, as we'll learn in the next couple of sessions, however the previous example demonstrates the use of a public variable. The MyMessage class has a public variable called "message". We can assign a value to this variable (as we did in the statement hello message = "Hello, World!";) and we can also read the value in the variable and use it (as we could do in a statement such as String newMessage = "kaluha! " + hello.message;).
Notice that we did not include classes in this sub-heading! That's because classes are generally not defined as private! In fact, a class can only use the "public" access modifier unless it's an inner class. You'll learn about inner classes in the second course.
A method that is defined as private in a class or program is only available inside that class or program. For example, create the following class and program:
public class Book { public String bookTitle; public double bookPrice; private void displayBook() { System.out.println(bookTitle + ": $" + bookPrice); } }
public class TestBook { public static void main(String[] args) { Book javaText = new Book(); javaText.bookTitle = "OOP Develpment Using Java"; javaText.bookPrice = 122.95; javaText.displayBook(); } }
When you compile both programs, you'll get the following error:
TestBook.java:17: displayBook() has private access in Book javaText.displayBook(); ^
This error is telling you that you can't invoke the javaText.displayBook() method because it's defined as private in the Book class. You can't access a private method from outside the class where it is defined.
Why would you want to define a private method in a class? Later we'll talk about encapsulation, which is the main reason behind private members of classes and programs. Simply, a private method is usually a "helper" method that performs a task to help out the tasks performed in other methods in the same class or program. The following scenario should help understand this:
Say you are asked to write a program that makes use of an Employee class that another programmer has already written. Your program prints the pay checks for the employees in the company. You read the documentation for the Employee class and see that there are some data members and some methods that are going to be useful to you. One of them is a method that prints a pay check.
This printPayCheck() method accesses the employee database and gathers the necessary information, then calculates all the fields necessary for a pay check, such as federal tax deductions, union dues, pension plan deductions, gross pay, and net pay. This printPayCheck() method is probably made up of many code statements that call other methods, such as the method to calculate the federal tax deduction. As a programmer using the Employee class, you don't need to know about or even care about those methods; you are only interested in the public method that prints a pay check. The little methods that "help" the printPayCheck() method are of no use to other programmers, only to the printPayCheck() method inside the Employee class. Therefore, those "helper" methods will be defined as private, so they can't be accessed outside the Employee class.
As we'll see later with instance variables, members of a class are often defined as private in order to protect instances of the class from being maliciously or accidentally altered in some way.
Private instance variables work the same way as private methods: they are only accessible inside the class and are not available to code in other classes or programs. You might wonder, why on earth would we add a data member to a class if it's not to be accessed by another program or class? In fact, this is a standard practice! In order to protect a piece of data from being given an incorrect value, we often make the data member private, and then we provide an "accessor method" for that data member. The accessor method will accept the value you want to put into the data member, make sure it's valid, and place the valid value in the data member itself. We'll learn about accessor methods in a later session.
When we define the data members (also called properties or attributes) for a class, we define instance variables. These are not the same as the variables we have been defining in a program's main() method; those variables were local variables. A few of the significant differences between local variables and instance variables are:
Instance Variables | Local Variables |
---|---|
|
|
|
|
|
|
|
|
Local variables can be thought of as "working" variables; we use them to hold data values temporarily until we are ready to use them later in our code. Instance variables are the pieces of data that contribute to the definition of a particular object; they are part of our class "recipe" or template.
Instance variables should always be defined as private. This allows you to protect the instance variables from being given invalid data, and allows you to add code for exception and error handling in the event that a variable is given bad data by another programmer.
One problem you might realize can be demonstrated with the following modification of the Message class:
public class Message { private String message; public void display() { System.out.println(message); } }
We've made the message instance variable a private variable. Now try to compile the test class:
public class TestMessage { public static void main(String[] args) { Message greeting = new Message(); greeting.message = "Hello, World!"; greeting.display(); } }
When you compile the test class, you get the following error:
TestMessage.java:13: message has private access in Message greeting.message = "Hello, World!"; ^ 1 error
You can't access a private data member (or method, even) from outside its class. Here, we are trying to access the message instance variable, but it's defined as private in the Message class. This means it's not available outside the Message class.
Ideally, you would add code to your class to allow limited access to private instance variables. For example, if you had an Account class, you wouldn't want just anyone being allowed to change the balance instance variable, and you certainly wouldn't want someone to give negative balances to account objects! In addition, you wouldn't want just any object having access to certain data. For example, the Customer object shouldn't be able to read other Customer object's balances, only their own. So how can we protect our instance variables from receiving invalid values, and how can we protect them from being read by other objects? In object-oriented programming, we use accessors and mutators.
As previously discussed, in a well-designed class, the programmer should keep the instance variables private, so that access to them is limited. Recall that private instance variables can't be accessed outside the class in which they are defined; in other words, no other class or program can read or change the value of a private instance variable. We need to make instance variables private in order to protect them from receiving invalid data. One problem with this of course is that other classes and programs can't access the variables even for legitimate purposes. For this reason, we provide accessor and mutator methods as gateways or filters to the private instance variables.
Code the following test program for your Room class, then compile them both, and run the program:
public class TestRoom { public static void main(String[] args) { Room office = new Room(); office.length = 16.5; office.width = 20; System.out.println("Area: " + office.calcArea()); } }
When we run the program, we get the following error messages:
TestRoom.java:15: length has private access in Room classroom.length = 16.5; ^ TestRoom.java:16: width has private access in Room classroom.width = 20; ^ 2 errors Press any key to continue...
We've discussed this kind of error before: You can't access the private instance variables length and width ouside of the Room class. So how do we give them values? How do we retrieve those values later if we want to display them? This is where accessor and mutator methods come in.
Mutator methods are special methods that put values into private instance variables. Many mutator methods will also validate the data going into a variable, for example when we made the Circle class, we decided that if a radius value was 0 or less, we would use a default value of 1 instead. If you were writing an employee class, you might want to make sure that an employee object's rate of pay was always greater than 0 and never greater than 100.00. In our Room class, we might decide that 0 or negative values are invalid for the length and width instance variables. We can program our mutator methods to check the values first, then either assign them if they're valid, or use a default if they're not.
There are rules you must follow when defining a mutator method for a class. Some of these are stylistic rules but a few are syntax rules:
Let's create to very basic mutator methods for the Room class that set the values of length and width, but only if the programmer-specified values are positive. If the parameter is 0 or negative, use a default value of 1:
public void setLength(double length) { if (length > 0) this.length = length; else this.length = 1; } public void setWidth(double width) { if (width > 0) this.width = width; else this.width = 1; }
To use these methods, you simply call them as you would any other method:
Room classroom = new Room(); classroom.setLength(16.5); classroom.setWidth(20); classroom.displayDimensions();
Try it with invalid values for length and/or width and see what output you get.
Accessor methods are similar to mutator methods, except that instead of setting the values of instance variables, accessor methods allow another class or program to read the values of instance variables. An accessor method returns the value of a particular instance variable, so as with mutators, there are a set of rules that need to be followed:
The accessor methods for our Room class would be defined as:
public double getLength() { return length; } public double getWidth() { return width; }
1. Add code to your TestRoom program that uses the class's accessor methods: calculate and display what the volume of the room would be if the height of the room was 15.
2. Modify the Circle.java class from the previous session: Ensure that the radius data member is private. Define acessor and mutator methods for the radius variable. A radius of a circle should never be 0 or less. Your new class should have the following structure:
Class: Circle |
- radius : double |
+ Circle() + Circle(radius: double) + getRadius() : double + setRadius(radius : double) : void + calcArea() : double + calcCircumference() : double + toString() : String |
Write a test program that tests out your Circle class:
3. Modify the Time.java class from the previous session. Ensure that all three instance variables are private. Define accessor and mutator methods for each instance variable. The hours should be between 0 and 23 inclusive, and the minutes and seconds should be between 0 and 59, inclusive.
Your class should now have the following structure:
Class: Time |
Data Members: - hours : int - minutes : int - seconds : int |
Methods: + Time() + Time(hours : int, minutes : int, seconds : int) + getHours() : int + getMinutes() : int + getSeconds() : int + setHours(hours : int) : void + setMinutes(minutes : int) : void + setSeconds(seconds : int) : void + calcTotalSeconds() : int + toString() : String |
Write a program that uses the Time class:
4. Modify the Dice class from the previous session. Add accessor methods only to the class.
Your class should now have the following structure:
Class: Dice |
Data Members: - firstDie : int - secondDie : int |
Methods: + Dice() + getFirstDie() : int + getSecondDie() : int + tossDice() : void + sum() : int + toString() : String |
Why are there no mutator methods in the Dice class? Because the dice get their values from tossing, not by allowing a programmer to choose and set the dice values. The tossDice() method generates two random numbers from 1 to 6 inclusive and assigns them to the two instance variables, so we won't be needing methods to set the dice values.
Write a program that tests your Dice class: