In Chapter 3, you started writing simple classes. However, all those classes had just a single main method. Now the time has come to show you how to write the kind of "workhorse classes" that are needed for more sophisticated applications. These classes typically do not have a main method. Instead, they have their own instance fields and methods. To build a complete program, you combine several classes, one of which has a main method. An Employee ClassThe simplest form for a class definition in Java is:
NOTE
Consider the following, very simplified, version of an Employee class that might be used by a business in writing a payroll system. class Employee { // constructor public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } // a method public String getName() { return name; } // more methods . . . // instance fields private String name; private double salary; private Date hireDay; } We break down the implementation of this class in some detail in the sections that follow. First, though, Example 4-2 shows a program code that shows the Employee class in action. In the program, we construct an Employee array and fill it with three employee objects: Employee[] staff = new Employee[3]; staff[0] = new Employee("Carl Cracker", . . .); staff[1] = new Employee("Harry Hacker", . . .); staff[2] = new Employee("Tony Tester", . . .); Next, we use the raiseSalary method of the Employee class to raise each employee's salary by 5%: for (Employee e : staff) e.raiseSalary(5); Finally, we print out information about each employee, by calling the getName, getSalary, and getHireDay methods: for (Employee e : staff) System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" + e.getHireDay()); Note that the example program consists of two classes: the Employee class and a class EmployeeTest with the public access specifier. The main method with the instructions that we just described is contained in the EmployeeTest class. The name of the source file is EmployeeTest.java because the name of the file must match the name of the public class. You can have only one public class in a source file, but you can have any number of nonpublic classes. Next, when you compile this source code, the compiler creates two class files in the directory: EmployeeTest.class and Employee.class. You start the program by giving the bytecode interpreter the name of the class that contains the main method of your program: java EmployeeTest The bytecode interpreter starts running the code in the main method in the EmployeeTest class. This code in turn constructs three new Employee objects and shows you their state. Example 4-2. EmployeeTest.java1. import java.util.*; 2. 3. public class EmployeeTest 4. { 5. public static void main(String[] args) 6. { 7. // fill the staff array with three Employee objects 8. Employee[] staff = new Employee[3]; 9. 10. staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15); 11. staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1); 12. staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15); 13. 14. // raise everyone's salary by 5% 15. for (Employee e : staff) 16. e.raiseSalary(5); 17. 18. // print out information about all Employee objects 19. for (Employee e : staff) 20. System.out.println("name=" + e.getName() 21. + ",salary=" + e.getSalary() 22. + ",hireDay=" + e.getHireDay()); 23. } 24. } 25. 26. class Employee 27. { 28. public Employee(String n, double s, int year, int month, int day) 29. { 30. name = n; 31. salary = s; 32. GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); 33. // GregorianCalendar uses 0 for January 34. hireDay = calendar.getTime(); 35. } 36. 37. public String getName() 38. { 39. return name; 40. } 41. 42. public double getSalary() 43. { 44. return salary; 45. } 46. 47. public Date getHireDay() 48. { 49. return hireDay; 50. } 51. 52. public void raiseSalary(double byPercent) 53. { 54. double raise = salary * byPercent / 100; 55. salary += raise; 56. } 57. 58. private String name; 59. private double salary; 60. private Date hireDay; 61. } Use of Multiple Source FilesThe program in Example 4-2 has two classes in a single source file. Many programmers prefer to put each class into its own source file. For example, you can place the Employee class into a file Employee.java and the EmployeeTest class into EmployeeTest.java. If you like this arrangement, then you have two choices for compiling the program. You can invoke the Java compiler with a wildcard: javac Employee*.java Then, all source files matching the wildcard will be compiled into class files. Or, you can simply type: javac EmployeeTest.java You may find it surprising that the second choice works because the Employee.java file is never explicitly compiled. However, when the Java compiler sees the Employee class being used inside EmployeeTest.java, it will look for a file named Employee.class. If it does not find that file, it automatically searches for Employee.java and then compiles it. Even more is true: if the time stamp of the version of Employee.java that it finds is newer than that of the existing Employee.class file, the Java compiler will automatically recompile the file. NOTE
Dissecting the Employee ClassIn the sections that follow, we want to dissect the Employee class. Let's start with the methods in this class. As you can see by examining the source code, this class has one constructor and four methods: public Employee(String n, double s, int year, int month, int day) public String getName() public double getSalary() public Date getHireDay() public void raiseSalary(double byPercent) All methods of this class are tagged as public. The keyword public means that any method in any class can call the method. (The four possible access levels are covered in this and the next chapter.) Next, notice that three instance fields will hold the data we will manipulate inside an instance of the Employee class. private String name; private double salary; private Date hireDay; The private keyword makes sure that the only methods that can access these instance fields are the methods of the Employee class itself. No outside method can read or write to these fields. NOTE
Finally, notice that two of the instance fields are themselves objects: the name and hireDay fields are references to String and Date objects. This is quite usual: classes will often contain instance fields of class type. First Steps with ConstructorsLet's look at the constructor listed in our Employee class. public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } As you can see, the name of the constructor is the same as the name of the class. This constructor runs when you construct objects of the Employee class giving the instance fields the initial state you want them to have. For example, when you create an instance of the Employee class with code like this new Employee("James Bond", 100000, 1950, 1, 1); you have set the instance fields as follows:
There is an important difference between constructors and other methods. A constructor can only be called in conjunction with the new operator. You can't apply a constructor to an existing object to reset the instance fields. For example, james.Employee("James Bond", 250000, 1950, 1, 1); // ERROR is a compile-time error. We have more to say about constructors later in this chapter. For now, keep the following in mind:
C++ NOTE
CAUTION
Implicit and Explicit ParametersMethods operate on objects and access their instance fields. For example, the method public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } sets a new value for the salary instance field in the object on which this method is invoked. Consider the call number007.raiseSalary(5); The effect is to increase the value of the number007.salary field by 5%. More specifically, the call executes the following instructions: double raise = number007.salary * 5 / 100; number007.salary += raise; The raiseSalary method has two parameters. The first parameter, called the implicit parameter, is the object of type Employee that appears before the method name. The second parameter, the number inside the parentheses after the method name, is an explicit parameter. As you can see, the explicit parameters are explicitly listed in the method declaration, for example, double byPercent. The implicit parameter does not appear in the method declaration. In every method, the keyword this refers to the implicit parameter. If you like, you can write the raiseSalary method as follows: public void raiseSalary(double byPercent) { double raise = this.salary * byPercent / 100; this.salary += raise; } Some programmers prefer that style because it clearly distinguishes between instance fields and local variables. C++ NOTE
Benefits of EncapsulationFinally, let's look more closely at the rather simple getName, getSalary, and getHireDay methods. public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } These are obvious examples of accessor methods. Because they simply return the values of instance fields, they are sometimes called field accessors. Wouldn't it be easier to simply make the name, salary, and hireDay fields public, instead of having separate accessor methods? The point is that the name field is a read-only field. Once you set it in the constructor, there is no method to change it. Thus, we have a guarantee that the name field will never be corrupted. The salary field is not read-only, but it can only be changed by the raiseSalary method. In particular, should the value ever be wrong, only that method needs to be debugged. Had the salary field been public, the culprit for messing up the value could have been anywhere. Sometimes, it happens that you want to get and set the value of an instance field. Then you need to supply three items:
This is a lot more tedious than supplying a single public data field, but there are considerable benefits:
Of course, the accessor and mutator methods may need to do a lot of work and convert between the old and the new data representation. But that leads us to our second benefit.
CAUTION
Class-Based Access PrivilegesYou know that a method can access the private data of the object on which it is invoked. What many people find surprising is that a method can access the private data of all objects of its class. For example, consider a method equals that compares two employees. class Employee { . . . boolean equals(Employee other) { return name.equals(other.name); } } A typical call is if (harry.equals(boss)) . . . This method accesses the private fields of harry, which is not surprising. It also accesses the private fields of boss. This is legal because boss is an object of type Employee, and a method of the Employee class is permitted to access the private fields of any object of type Employee. C++ NOTE
Private MethodsWhen implementing a class, we make all data fields private because public data are dangerous. But what about the methods? While most methods are public, private methods are used in certain circumstances. Sometimes, you may wish to break up the code for a computation into separate helper methods. Typically, these helper methods should not become part of the public interface they may be too close to the current implementation or require a special protocol or calling order. Such methods are best implemented as private. To implement a private method in Java, simply change the public keyword to private. By making a method private, you are under no obligation to keep it available if you change to another implementation. The method may well be harder to implement or unnecessary if the data representation changes: this is irrelevant. The point is that as long as the method is private, the designers of the class can be assured that it is never used outside the other class operations and can simply drop it. If a method is public, you cannot simply drop it because other code might rely on it. Final Instance FieldsYou can define an instance field as final. Such a field must be initialized when the object is constructed. That is, it must be guaranteed that the field value has been set after the end of every constructor. Afterwards, the field may not be modified again. For example, the name field of the Employee class may be declared as final because it never changes after the object is constructed there is no setName method. class Employee { . . . private final String name; } The final modifier is particularly useful for fields whose type is primitive or an immutable class. (A class is immutable if none of its methods ever mutate its objects. For example, the String class is immutable.) For mutable classes, the final modifier is likely to confuse the reader. For example, private final Date hiredate; merely means that the object reference stored in the hiredate variable doesn't get changed after the object is constructed. That does not mean that the hiredate object is constant. Any method is free to invoke the setTime mutator on the object to which hiredate refers. |