A class's body is composed of interface and implementation. The interface is that part of the class that's accessible to code located outside of the class. The implementation is that part of the class that exists to support the interface. Implementation should be hidden from external code so that it can be changed to meet evolving requirements.
Consider the Book
class. Constructor and method headers form this class's interface. The code within the constructors and methods, and the various fields are part of the implementation. There is no need to access these fields because they can be read or written via the getter and setter methods.
However, because no precautions have been taken, it's possible to directly access these fields. Using the previous book
reference variable, you could specify book.title
and book.pubYear
, and that would be okay with the Java compiler. To prevent access to these fields (or at least determine who can access them), you need to take advantage of access levels.
An access level is an indicator of who can access a field, method, or constructor. Java supports four access levels: private, public, protected, and package (the default). Java provides three keywords that correspond to the first three access levels:
private
: Only code in the same class as the member can access the member.public
: Any code in any class in any package can access the member.protected
: Any code in the same class or its subclasses can access the member.
If there is no keyword then package access is implied. Package access is similar to public access in that code outside of the class can access the member. However, unlike public access, the code must be located in a class that belongs to the same package (discussed later in the Java 101 series) as the class containing the member that is to be accessed.
You can prevent external code from accessing Book
's title
and pubYear
fields so that any attempt to access these fields from beyond Book
will result in a compiler error message. Accomplish this task by prepending private
to their declarations, as demonstrated below:
class Book { // fields private String title; private int pubYear; // publication year // ... }
To show you why it's a good idea to hide access to fields, I've created another example that expands the Book
class to include an author
field, getter/setter methods for accessing this field, and another constructor that lets you also specify an author's name. The following example shows the modified parts of the updated Book
class:
class Book { // ... private String author; // ... Book(String title, int pubYear) { this(title, pubYear, ""); } Book(String title, int pubYear, String author) { setTitle(title); setPubYear(pubYear); setAuthor(author); ++count; } // ... String getAuthor() { return author; } // ... void setAuthor(String author) { this.author = author; } // ... }
If you recall, the Book(String title)
constructor executes this(title, -1);
to avoid code duplication. Similarly, I've designed Book(String title, int pubYear)
to execute this(title, pubYear, "");
, which calls the newest constructor, to avoid code duplication.
You can easily create a new Book
object that includes the author name and obtain this name via the getAuthor()
method:
Book book = new Book("A Tale of Two Cities", 1859, "Charles Dickens"); System.out.println(book.getAuthor()); // Output: Charles Dickens
Consider that the upgraded Book
class doesn't allow for multiple authors. You cannot change the constructor or getAuthor()
/setAuthor()
headers to make this enhancement because doing so would change the interface and cause external code that relies on this interface to stop working. Instead, you would need to rework the implementation, as follows:
class Book { // ... private String[] authors; // ... Book(String title, int pubYear, String author) { this(title, pubYear, new String[] { author }); } Book(String title, int pubYear, String[] authors) { setTitle(title); setPubYear(pubYear); setAuthors(authors); ++count; } // ... String getAuthor() { return authors[0]; } String[] getAuthors() { return authors; } // ... void setAuthor(String author) { setAuthors(new String[] { author }); } void setAuthors(String[] authors) { this.authors = authors; } // ... }
I changed the previous author
field to an authors
field. Also, I changed its type from String
to the String[]
array type.
I modified the Book(String title, int pubYear, String author)
constructor to call the Book(String title, int pubYear, String[] authors)
constructor. Because that constructor's third parameter is of type String[]
, the this()
call converts its String author
argument to a single-element array consisting of itself via new String[] { author }
. The new
operator is used to create a single-element String
array and assign the author
string reference to this element.
The original getAuthor()
method accesses the first element in the authors
array. I modified the original setAuthor()
method to convert its single String
argument to a single-element String
array, then call setAuthors()
with this array as the argument.
Finally, I added new getAuthors()
and setAuthors()
methods to return the authors
array reference or assign a new reference to authors
.
You can create a new Book
object that includes multiple author names by calling the new constructor. You can then obtain only the first name via getAuthor()
or obtain all names via getAuthors()
:
Book book = new Book("Grimms' Fairy Tales", 1812, new String[] { "Jacob Grimm", "Wilhelm Grimm" }); System.out.println(book.getTitle()); // Output: Grimms' Fairy Tales System.out.println(book.getPubYear()); // Output: 1812 System.out.println(book.getAuthor()); // Output: Jacob Grimm String[] authors = book.getAuthors(); for (int i = 0; i < authors.length; i++) System.out.println(authors[i]); // Output: Jacob Grimm Wilhelm Grimm (on successive lines)
If the original author
field wasn't marked private
, you wouldn't be able to expand Book
this way. Furthermore, external code would be able to directly access this field (e.g., book.author
) and would break when you changed the field name.
Hiding constructors or methods
As well as hiding fields, you might need to hide constructors or methods. You typically hide a constructor to prevent a utility class from being instantiated. You typically hide a method when it only exists to service another method (or a constructor). Such a method is known as a helper method. Below is a short example:
// compute permutation of n as n! / (n - r)!. public long perm(long n) { return fact(n) / fact(n - r); } // fact() is hidden because it has no use outside // of the class that computes the permutation. private long fact(long n) { // code that computes and returns n! }
Conclusion
As you've learned in this Java tutorial, an object-based application encapsulates state and behaviors inside classes and objects. In contrast, an object-oriented application also supports inheritance. The next tutorial in this series gets you started with Java inheritance--namely using the extends
keyword to derive a child class from a parent class, invoke parent class constructors and methods, and override methods.
This story, "Classes and objects in Java" was originally published by JavaWorld.