Classes and objects in Java

Learn how to make classes, fields, methods, constructors, and objects work together in your Java applications

1 2 3 Page 3
Page 3 of 3

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:

  1. private: Only code in the same class as the member can access the member.
  2. public: Any code in any class in any package can access the member.
  3. 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.

Copyright © 2019 IDG Communications, Inc.

1 2 3 Page 3
Page 3 of 3