Packages, Interfaces, and Encapsulation - Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Wrox Press Java Programming 24-Hour Trainer 2nd (2015)

Lesson 6. Packages, Interfaces, and Encapsulation

A programming language is considered object-oriented if it supports inheritance, encapsulation, and polymorphism. You know by now that Java supports inheritance, which lets you design a class by deriving it from an existing one. This feature allows you to reuse existing code without copy-pasting code fragments from other classes. The class NJTax from Listing 3-5 was designed this way.

In Chapter 6 and Chapter 7 you learn what encapsulation means, and continue studying coding techniques and best practices. Although this lesson shows you several short code examples illustrating certain programming topics, the next one brings all the pieces you’ve learned so far together in one larger application.

Java Packages

A decent size project can have hundreds of Java classes, and you need to organize them in packages (think file directories). This will allow you to categorize files, control access to your classes (see the section “Access Levels” later in this chapter), and avoid potential naming conflicts: If both you and your colleague coincidentally decide to name a class Util, this won’t be a problem as long as these classes are located in different packages.

Sometimes you’ll be using third-party libraries of Java classes written in a different department or even outside your firm. To minimize the chances that package names will be the same, it’s common to use so-called reverse domain name notation. For example, if you work for a company called Acme, which has the website acme.com, you can prefix all package names with com.acme. To place a Java class in a certain package, add the package statement at the beginning of the class (it must be the first non-comment statement in the class). For example, if the class Tax has been developed for the accounting department, you can declare it as follows:

package com.acme.accounting;

class Tax {

// the code goes here

}

If you declare the class Tax as shown in the preceding code, the file Tax.java must be stored in the corresponding directory tree:

com

acme

accounting

Java classes are also organized into packages, and the fully qualified name of a class consists of the package name followed by the class name. For example, the full name of the Java class Double is java.lang.Double, where java.lang is the package name. As a matter of fact, java.lang is the only package name that you don’t have to explicitly mention in your code in order for its classes to be found, unless all classes are located in the same package.

The program documentation on all Java 8 packages is available at http://download.oracle.com/javase/8/docs/api/.

Let’s say your Tax class needs to connect to some URL with the help of the class URL located in the java.net package. You can write code containing the fully qualified name of this second class:

java.net.URL myURL = new java.net.URL ("http://www.acme.com");

But instead of using this rather long notation, include the import statement right above the class declaration, and then use just the name of the class:

import java.net.URL;

class Tax{

URL myURL = new URL("http://www.acme.com");

...

}

If you need to import several classes from the same package, use the wild card (*) in the import statement rather then listing each of the classes on a separate line:

import java.net.*;

But using the wild card makes your program less readable. The preceding code sample makes it not clear which specific classes from the java.net package are used in the program. Don’t be lazy and list import statements for each class separately. It doesn’t affect the size of the compiled code, but it will make your program easier to understand to whomever reads it in the future (even you, by the way).

Encapsulation

Encapsulation is the ability to hide and protect data stored in Java objects. You may ask, “Who are the bad guys who want to illegally access my data?” It’s not about bad guys. When a developer creates a Java class, he or she plans for a certain use pattern of this code by other classes. For example, the variable grossIncome should not be modified directly; instead, it should be modified via a method that performs some validation procedures to ensure that the value to be assigned meets application-specific rules.

A Java developer may decide to “hide” 15 out of 20 variables, say, so other classes can’t access them. Imagine how many parts exist in a car and how many functions those parts can perform. Does the driver need to know about all of them? Of course not. The driver needs to know how to start and stop the car, signal turns, open the windows, turn on the wipers, and do a few dozen other simple operations, which in programming jargon can be called the car’s public interface. Java has special keywords to control which elements of your programs should be made public, and which should not.

Access Levels

Java classes, methods, and member variables can have public, private, protected, and package access levels; for example:

public class Tax {

private double grossIncome;

private String state;

private int dependents;

protected double calcTax(){

// the method code goes here

}

}

The keyword public means that this element (a class, a method, or a variable) can be accessed from any other Java class. The keyword protected makes the element “visible” not only in the current class but also to its subclasses, even if they are located in different packages.

The keyword private is the most restrictive one, as it makes a member variable or a method accessible only inside this class. For example, our class Tax may need some additional methods that could be internally called from the method calcTax(). The users of the class Tax do not need to know about these methods, and they should be declared as private.

If you do not specify any access level, the default is package (it’s not a keyword), which means that only classes located in the same package will have access to this method or variable. Java classes should expose only the methods that outsiders have to know, such as calcTax().

If you are not sure which access level to give to methods or variables, just make them all private; as you’re doing later development, if some other class needs to access them, you can always change the access level to be less restrictive. This will protect all the internals of your application from misuse. Think of it this way: “I want to sell my class Tax to various accounting firms across the country. If their software developers integrate my class with their existing systems, what are the methods that they must know about to be able to calculate tax?” If car designers did not ask themselves similar questions, drivers would need to press dozens of buttons just to start the engine.

The Keyword final

The keyword final can have different meanings depending on the context. It’s explained in the next sections.

final Variables

You can use the keyword final while declaring variables, methods, and classes. A final variable becomes a constant (see Chapter 3) that can be initialized only once and can’t change its value during the run time. Some people may argue that a constant and a variable that can get initialized only once are not the same thing, but the fact that you can’t change their values makes them very similar.

Even though you can declare constants inside a method, it’s more common to declare them on the class level so they can be reused by several methods of the same class:

final int BOILING_TEMP = 212; // in Fahrenheit

final Methods

If you declare a class method with a final keyword, this method can’t be overridden if someone decides to extend the class. At the moment it may seem obvious to you that a particular method will never ever need to be overridden. What are the chances that the formula to convert Fahrenheit to Celsius will be changed any time soon?

static final double convertToCelsius(double far){

return ((far - 32) * 5 / 9);

}

But in many cases developers create reusable libraries of Java classes, finalizing functionality that’s not written in stone. Although it may seem to you that a particular method will never need to be overridden, you might not have predicted all use patterns of this class. If this happens, some other developer will have to jump through hoops to create another version of your method in a subclass.

Many years ago the Java compiler optimized (inlined) final methods. It doesn’t do that anymore — all methods are optimized by the Hotspot JVM. Just think twice before making a method final

final Classes

If you declare a class as final, no one will be able to subclass it. For example, the class String has been created as immutable and therefore was declared as final (see http://docs.oracle.com/javase/8/docs/api/java/lang/String.html). If a class is declared as final, all its methods become implicitlyfinal.

final in Exception handling

There is one more use of the final keyword. You can use it in the error-handling blocks (try-catch). You see an example of using finally in Lesson 10 covering error handling

Interfaces

There are different approaches to how to start designing a class. Most people start with thinking over the behavior it should support. For example, an employee should get paid; hence the class Employee should implement Payable interface. The name Payable is an arbitrary one. What represents payable behavior? Let’s say that you want to implement it in a method with a signature boolean increasePay(int percent).

Of course, you can just add such a method straight to the class Employee and implement business logic right there. The other choice is to just declare this method in a separate entity called an interface, and then have your class implement this interface:

class Employee implements Payable{

// the implementation goes here

}

Listing 6-1 shows an interface with one method declaration.

You should have a reason for declaring some methods separately from the class that will implement them, and you see these reasons in the next lesson when you learn about polymorphism. For now, just get familiar with the syntax of defining and using interfaces. Interfaces before Java 8 didn’t allow for any method implementations — just declarations. Let’s start with the case when an interface has only declared behavior.

When a class declares that it implements a certain interface, it guarantees to provide implementation for all methods declared in this interface. And a class can implement more than one interface: just separate their names with commas.

Let’s say there are two types of workers in your organization — employees and contractors — and that you create the classes Employee and Contractor to implement functionalities that reflect specifics of these different groups. Each person is entitled to a pay raise, though for employees this means a salary increase and for contractors it’s an increase of an hourly or daily rate.

Instead of creating two different methods in these classes (for example, increateSalary() and increaseRate()), it’s better to define an interface called, say, Payable that contains the declaration of the method increasePay(), and to have both classes implement it, as in Listing 6-1, Listing 6-2, andListing 6-3. Every method declared in the interface automatically becomes public.

Listing 6-1: Payable interface

public interface Payable {

boolean increasePay(int percent);

}

Listing 6-2: Class Employee

public class Employee implements Payable {

public boolean increasePay(int percent) {

// implement salary raise here

}

}

Listing 6-3: Class Contractor

public class Contractor implements Payable {

public boolean increasePay(int percent) {

// implement hourly rate increase here

}

}

Because both Employee and Contractor contain the clause implements Payable, you must implement the increasePay() method in each of the classes, or your code won’t compile. Creating classes with common interfaces leads to a cleaner design of your application and makes the code more readable. But what’s more important is that, with the help of interfaces, you can introduce polymorphic behavior to your program, which is illustrated in Chapter 7.

Besides method signatures, Java interfaces can contain declarations of final variables. For example, you can create a final variable in the Payable interface for the maximum percentage of a pay increase (all variables declared in the interface automatically become public static final):

int INCREASE_CAP = 20;

Because both the Employee and Contractor classes implement Payable, they can both (or just the Contractor) include if statements in the implementation of increasePay() to ensure that the provided percentage increase is less than INCREASE_CAP. If the cap changes in the future, you need to change it in only one place — the Payable interface. Moreover, if a new type of worker is introduced later (for example, ForeignContractor), the implementation of increasePay() may be completely different.

Some software developers create Java interfaces that contain only final variables storing important application constants. Implementing such interfaces will make these constants available in the class that implements the interface(s). Not everyone approves of such usage of interfaces because it can create a messy situation when a class that implements interfaces with static constants exposes a new set of public APIs (those final variables) rather than just using these values internally. The code readability suffers, too; it’s not immediately clear where a certain variable was declared, especially if there are several layers of inheritance, where classes implement multiple interfaces. You can read more about this Constant Interface Antipattern at http://goo.gl/WBQm9d.

Marker Interfaces

Marker interfaces are those that don’t have any methods declared. One example of such an interface is Serializable, which is covered in Chapter 15. You don’t need to write any implementation of these interfaces; the Java compiler takes care of this for you. Objects of a class that implement a marker interface support a certain functionality. For example, if a class implements Serializable, JVM is able to serialize it — turn it into a string of bytes (in the server’s JVM) in such a way that the string can be sent to another JVM (on the client’s machine), which will be able to re-create the instance of the object, or de-serialize it. Marker interfaces are used internally by the Java compiler to generate the byte code that will implement the functionality required by such interfaces.

Also, Java has an operator instanceof (see the next section) that can check during the run time whether an object is of a certain type. You can use the operator instanceof, as shown in the following code, to check whether the object implements a marker interface, or any other type of interface for that matter:

if (receivedFromServerObj instanceof Serializable) {

// do something

}

This may look strange — is it an instance of an interface? The proper way to read it is if the variable receivedFromServerObj points to the object that prompts Serializable to do something.

Default Methods in Interfaces

A new keyword, default, was introduced in Java SE 8. Now you can provide a default method implementation in interfaces, too. For example, you can create an interface Payable with default implementation of the method increasePay():

public interface Payable {

default boolean increasePay(int percent){

System.out.println(

"The default code implementing pay increase goes here");

return true;

};

}

Now, if a class that implements Payable doesn’t have its own implementation of the increasePay() then the default method implementation is used. The compiler will not complain. Default methods are also known as defender methods.

Compiler Compliance Level in Eclipse IDE

If the preceding code doesn’t compile in your Eclipse IDE, most likely your project is set to support syntax that’s older than Java 8. To fix it, right-click the project name, select the Project Properties menu, and then select Java Compiler. Make sure that the JDK Compliance is set to support Java 1.8.

You need to handle default methods with care as sometimes you might run into the name conflicts. Consider the following situation:

package defendermethods;

class Employee extends Person implements Payable, Promotionable{

public static void main(String[] args){

Employee emp = new Employee();

emp.increasePay(10);

}

}

What if there is the method increasePay() in the class Person and in the interfaces Payable and Promotionable? How is such a conflict resolved? If a program creates an instance of the class Employee and invokes the method increasePay() on it, which version of this method is invoked?

If a method with the same signature exists in the ancestor class and the interface, the one in the class is invoked. If the defender methods with the same signature exists only in the interfaces Payable and Promotionable, the compiler complains with the following error:

Duplicate default methods named increasePay with the parameters (int) and (int) are inherited from the types Promotionable and Payable Employee.java

Static Methods in Interfaces

As of Java 8, you are allowed to include static methods in interfaces. For example:

public interface Payable {

default boolean increasePay(int percent){

System.out.println(

"The default code implementing pay increase goes here");

return true;

};

static double checkThePayIncreaseLimit(){

// do something

return 12345.00;

}

}

If a static method is declared in a class, it’s invoked by using a class name followed by the dot and the method name, as shown in Lesson 4: WeatherReport.convertToCelsius(98.7).

Accordingly, if a static method was declared in the interface, a class can invoke it using the interface name. For example, this is how you could do it from our class Employee:

double limit = Payable.checkThePayIncreaseLimit();

Casting

All Java classes form an inheritance tree with the class Object on top of the hierarchy — all Java classes are direct or indirect descendants of Object. When you eclare a non-primitive variable, you are allowed to use either the exact data type of this variable or one of its ancestor data types. For example, if the class NJTax extends Tax, each of the following lines is correct:

NJTax myTax1 = new NJTax();

Tax myTax2 = new NJTax(); // upcasting

Object myTax3 = new NJTax(); // upcasting

Java is smart enough to automatically cast an instance of the class to its ancestor. When the variable has a more generic type than an instance of the object, it’s called upcasting. Let’s say the class Object has 10 methods and class variables defined, the class Tax (an implicit subclass of Object) adds five more methods and variables (making 15), and NJTax adds another two (totaling 17). The variable myTax1 has access to all 17 methods and variables, myTax2 sees only 15, and myTax3 just 10. Why not always use exact types in variable declarations?

Say you need to write a program that will process data about workers of a certain company. Some of them are full-time employees and some are contractors, but you’d like to read their data from some data source and store them in the same array. Arrays can store only objects of the same type, remember? Because Java can automatically upcast the objects, you can create a class called Person with two subclasses, Employee and Contractor, and then read the records from a database. Based on the employment type you can then create an appropriate object instance and put it into an array of type Person:

Person workers[] = new Person [20];

workers[0] = new Employee("Yakov", "Fain");

workers[1] = new Employee("Mary", "Lou");

workers[2] = new Contractor("Bill", "Shaw");

...

Of course, you could’ve created two separate arrays, one for employees and one for contractors, but I’m laying the foundation here for explaining polymorphism — a powerful concept in object-oriented languages. You see a concrete example of polymorphism in Chapter 7.

At some point you need to process the data from the array workers. In a loop you can test the data type of the current element of the array with the operator instanceof, downcast the object (it can’t be done automatically) to Employee or Contractor, and process it accordingly:

for (int i; i<20; i++){

Employee currentEmployee;

Contractor currentContractor;

if (workers[i] instanceof Employee){

currentEmployee = (Employee) workers[i];

// do some employee processing here

} else if (workers[i] instanceof Contractor){

currentContractor = (Contractor) workers[i];

// do some contractor processing here

}

}

Placing a data type in parentheses in front of another object means that you want to downcast this object to the specified type. You can downcast an object only to one of its descendant data types. Even though the preceding code has correct syntax, it doesn’t represent the best practice for processing similar objects. In the next lesson you see how to use polymorphism in a more generic way.

If a class implements an interface, you can cast its instance to this interface. Say that a class called Employee implements Payable, Insurable, and Pensionable interfaces:

class Employee extends Person implements

Payable, Insurable, Pensionable {

// implementation of all interfaces goes here

}

Assume you have an array of workers of type Person. If you are interested only in the Insurable behavior of employees, there is no need to cast each element of this array to the type Employee. Just cast them to the Insurable type, as shown in the following code fragment. However, keep in mind that if you do so, the variable currentEmployee exposes access only to those methods that were declared in the Insurable interface:

Insurable currentEmployee;

if (workers[i] instanceof Insurable){

currentEmployee = (Insurable) workers[i];

// do some insurance-specific processing here

}

Try It

The goal of this assignment is to start using packages, protect data using private variables, and define first interfaces. You create a simple program that will increase pay, which is implemented differently for employees and contractors. After completing this assignment you’ll have working but not perfect code. What can be improved is explained in Chapter 7.

Lesson Requirements

For this lesson you should have Java installed.

NOTE You can download the code and resources for this “Try It” from the book’s web page at www.wrox.com/go/javaprog24hr2e. You can find them in Lesson6.zip.

Step-by-Step

1. In the Eclipse IDE, create a new project called Lesson6.

2. Create the Payable interface as per Listing 6-1 in the package com.practicaljava.lesson6 — you can enter the name of the package in the Eclipse New Java Class window. Declare a final variable there:

int INCREASE_CAP = 20;

3. Create a class called Person:

4. package com.practicaljava.lesson6;

5. public class Person {

6. private String name;

7. public Person(String name){

8. this.name=name;

9. }

10. public String getName(){

11. return "Person's name is " + name;

12. }

}

13.Create the classes Employee and Contractor in the package com.practicaljava.lesson6. Each class should extend Person and implement Payable. While creating a new class, click the Add button in Eclipse to automatically include the Payable interface in declarations of the classesEmployee and Contractor.

14.Check your filesystem to ensure that the files were created in your workspace in the directory com/practicaljava/lesson6.

15.Create a class called TestPayIncrease with a method called main(). Don’t specify any package; this class will be created in a different directory.

16. Try to create an instance of the class Employee in the method main() of TestPayIncrease:

Employee myEmployee = new Employee();

You’ll get an error: Employee can’t be resolved to a type. No wonder — it’s located in a different package. Move the mouse over Employee and Eclipse will offer you a fix. Add an import statement:

import com.practicaljava.lesson6.Employee;

Select this fix and later add the import statement for all required classes.

17. In the main() method of the class TestPayIncrease, create an array of employees and contractors and call the function increasePay() for each element of the array:

18. public static void main(String[] args) {

19. Person workers[] = new Person[3];

20. workers[0] = new Employee("John");

21. workers[1] = new Contractor("Mary");

22. workers[2] = new Employee("Steve");

23. Employee currentEmployee;

24. Contractor currentContractor;

25. for (Person p: workers){

26. if (p instanceof Employee){

27. currentEmployee = (Employee) p;

28. currentEmployee.increasePay(30);

29. }else if (p instanceof Contractor){

30. currentContractor = (Contractor) p;

31. currentContractor.increasePay(30);

32. }

33. }

}

34. Implement the increasePay() method in Employee — don’t put any restrictions on pay increases. Here’s the body of increasePay():

35. System.out.println("Increasing salary by " + percent + "%. "+

36. getName());

return true;

37.Implement the increasePay() method in the class Contractor. If the percentage of the increase is less than INCREASE_CAP, print a message similar to the one in the preceding code. Otherwise, print a message explaining that you can’t increase a contractor’s rate by more than 20 percent.

38. Run the TestPayIncrease program. It should produce output similar to the following:

39. Increasing salary by 30%. Person's name is John

40. Sorry, can't increase hourly rate by more than 20%. Person's name is Mary

Increasing salary by 30%. Person's name is Steve

TIP Please select the videos for Lesson 6 online at www.wrox.com/go/javaprog24hr2e. You will also be able to download the code and resources for this lesson from the website.