12.5 Implementation Attributes Defined in Java

 < Day Day Up > 



12.5 Implementation Attributes Defined in Java

While many details of implementing a program have been left purposely ambiguous in Java, the language designers felt that some aspects were important enough to include in the language definition. This section deals with some of the implementation attributes that are included in Java.

12.5.1 Source File Names

As difficult as it is to believe, often the source code for a program gets lost. One cause of this is good source code control not being in place; as programmers come and go on a project, files get moved to the wrong places. Another problem is that programmers jealously protect their right to name programs creatively or to store them in places such as their homes or other directories. Some languages even allow programmers to name files with any extension they choose or allow arbitrary files to be included inside of a source program. When this happens, often all that is left is the executable code (in Java the ".class" files) used to run the program.

For example, C/C++ allows a programmer to include a header file (".h" file) in a program to allow the variables and function prototypes to be defined; however, the include feature does not limit a programmer to including valid C/C++ file type. In fact, any arbitrary file can be included in a C/C++ source program. To see how this results in lost files, consider the following example. A programmer decides that standard library functions should be in files named "lib1.std" and "lib2.std," where "std" means standard definitions. These files can now be included in a C/C++ source program that compiles all libraries, and a file that compiles all the libraries for a system, called library.c, could look as follows:

   #include <lib1.std>   #include <lib2.std> 

This is all that is needed in library.c, as it includes the two source files that contain the actual C source for the program. It is now impossible, though, based on the file names alone, for a programmer unfamiliar with the project to even guess where the source code for the system is stored.

To make matters worse, the programmer can now effectively hide where these source files are located. They do not have to be in the current directory, as the format of the #include used here allows the files to exist anywhere on the system as long as the directory is included in an "-I" command line option (e.g., cc -I/usr/anywhere). The programmer who has done this then finds another job, and someone else must make sense of this, which is nearly an impossible task. C/C++ leaves the issue of how to store and control source code open, which allows great flexibility for programmers, for good or ill.

To avoid this situation, an implementation attribute of Java requires that the format for the name for all source files must have the extension ".java." Also, the #include statement has no equivalent in Java. This forces all files that are potential Java source files to have the suffix ".java" and makes it easier to find Java source files. The formal naming scheme in Java is internally verified so it cannot be circumvented. This does not mean that source code cannot be lost if a good source code control system is not in place; it simply means that programmers have to try harder to lose the programs.

12.5.2 Definition Files

One problem in every modern programming language is defining a way to include program definitions for objects and methods that are defined outside of the current source file. Ada has a formal definition, called a specification, that defines how this works. C/C++ has an informal mechanism, called a header (".h") file. The concept behind a header file is that functions and classes are defined in the header file, and the implementation is defined in C/C++ source files. In the hands of a disciplined and experienced programmer, this approach works well. The problem is that not all (in fact, not even most) programmers are disciplined and experienced.

The C/C++ header files rely on the programmer using them properly, but nothing in the language forces the name to have an ".h" suffix, and programmers can often become creative with their naming. Even worse is that no good rule exists for what goes in the files. They should contain prototypes and definitions only; implementation details should not be in them. This informal rule, though, not only is not enforced, but in some cases cannot be achieved; for example, #define and inline methods must be defined in a header file so that they can be included in the source program when it is compiled. This all leads to a situation where it is easy to misuse header files and implement poor programs.

In Java, this problem was handled by including the definition as part of the executable (".class") file. When a program is compiled, prototypes for all of the methods and definitions for all of the structures are written and maintained in the ".class" files, so header files or specifications are not needed.

12.5.3 Stateless Methods

Stateless methods are methods that do not maintain any data between executions but use only the parameters for the current invocation to do their calculations. Examples are Integer.parseInt or Math.sin. In Java, even though the class the methods are in does not maintain any state for them, they must still be included as static methods within a class, and the name of the class is necessary to resolve their references. Because stateless methods do not need a context, many languages, including C/C++, allow them to exist simply as freestanding methods. These methods can exist in any file that is compiled as part of a valid C/C++ source file, and they do not need to reference anything but their name. This makes "grep" (a UNIX utility for searching for strings in a file) an absolute necessity for maintaining any reasonably sized C/C++ program. The need to store a stateless method with a class and to reference that class might seem an unnecessary hassle in Java, but it makes it easier to find the actual definitions of methods.

12.5.4 Final Classes and Methods

Sometimes when implementing a class, we want to limit the ability of a programmer to extend a class or redefine a method. For example, an immutable class might be declared final to ensure that no subclass can be created that could break the encapsulation and make the class mutable. One example of this is the String class. Some programmers want to extend the String class in order to directly call methods that use strings as parameters; however, the classes that they create are not always immutable. As was pointed out in Chapter 11, one way to make aggregate data thread safe is to use immutable classes; however, this type of design breaks encapsulation and can make an aggregate relationship into an association relationship, thus causing problems when the class is used in a concurrent program. Methods might be declared final for a number of reasons. For example, methods that perform essential services in a base case could be declared final to ensure that they are not overridden, with the result that their behavior is inadvertently (or maliciously) changed. Another reason why a method could be declared final is if it is to be sent through the network to execute on a remote computer. Making a method final ensures that a class does not override the existing methods and thus fool the remote programming into executing unsafe code.

12.5.5 Inner Classes

Inner classes are Java classes that are defined inside of other program structures, such as other classes or methods. Because they are defined as part of another program, they have scope just like a variable. For example, an inner class can be local, instance, or static. These classes can also have scooping resolution modifiers, such as private and public (these scope resolution modifiers are covered in more detail in the section on packages). This leads to a number of interesting uses for inner classes. For example, consider Program11.8 (see Chapter 11, Exhibit 9), where only the Operator class is meant to be accessible. The specific type of operator (AddOperator or SubtractOperator) is declared as a private inner class inside of the operator and cannot be used outside of the Operator class. Likewise, in Program11.9 (see Chapter 11, Exhibit 10), the interface Evaluator is declared private so classes implementing it must be inner classes of the Operator class.

The scope resolution modifier can also be used to get around the rule that only a single public class can be inside of a ".java" source file. Consider Exhibit 3 (Program12.1), where a Stack class is defined that can throw Stack.Empty and Stack.Full exceptions. In a normal Java program, these exceptions would only be related informally to the Stack class that throws them by their name (for example, they might be called StackEmpty and StackFull). Further, they must be public classes themselves and so must be contained in a source code file separate from the Stack class; however, because these exceptions are public inner classes of the public class Stack, they are now formally associated with the Stack class, are contained in the same physical file as the Stack class, and have internal system validation to ensure that they are used correctly. While these uses of inner classes to structure a program seem useful, they do not follow standard coding practices in Java and should only be used after careful consideration of the impacts on the overall implementation of the system. Generally, this type of use of inner classes to structure a program should probably be avoided.

Exhibit 3: Program12.1

start example

 import java.awt.*; import java.awt.event.*; import javax.swing.*; public class InnerClassExample {      static int counter;      public static void main(String args[]) {           JFrame frame = new JFrame();           Container container = frame.getContentPane();           container.setLayout(new FlowLayout());          // Note that counter cannot be declared here as it          // will not exist when the main method is exited.          // Since the button uses this value, and possibly          // exists after the method is exited, the variable          // must be given a lifetime that outlives this method.          // This can be done by declaring the variable final,          // or declaring it as part of the class (as is done          // in this example). Both actions move the variable          // off of the stack, allowing this variable to be          // used in the listener. Note however the declaring          // the variable final here means that it cannot be          // incremented, hence the decision to declare it as          // part of the class itself. int counter = 0;           JButton incrementButton = new JButton("Increment");           container.add(incrementButton);           incrementButton.addActionListener              (new ActionListener() {                public void actionPerformed(ActionEvent ae) {                     counter++;                }           });           JButton exitButton = new JButton("Exit");           container.add(exitButton);           exitButton.addActionListener(new ActionListener() {                public void actionPerformed(ActionEvent ae) {                     System.exit(0);                }           });           frame.setSize(300,300);           frame.show();      } } 

end example

12.5.6 "Making" a Program

One of the most common questions asked by students and faculty who have programmed in C/C++ is how to create a makefile for compiling a Java program. Make is a program utility in C/C++ that allows the program dependencies to be defined in a file, normally named makefile. It also ensures that, when a program is created, any dependency needed to create the program that has been changed since the last time the program was made is recompiled and thus current. For example, consider the makefile example below:

   test.exe: test.c libs.o     cc -o test.exe test.c libs.o   libs.o: libs.c     cc -c libs.c 

This simple makefile states that the program test.exe is dependent on two other files, test.c and libs.o. If either has changed, then the command "cc -o test.exe test.c libs.o" must be rerun to remake test.exe; however, the file libs.o also has a dependency on libs.c. So, before test.exe can be made the make program checks to see if libs.o must be recompiled.

This makefile utility is extremely useful in C/C++, thus most programmers coming from a C/C++ environment want to know how to apply it to Java. The simple answer is that it has no place in Java because Java already (correctly) handles dependencies. The problem with a makefile is that it once again represents an informal way to handle an implementation problem that is externally validated. First, we do not have a standard as to how the dependencies in the makefile must be encoded for a language. What actually makes up dependencies might be generally agreed upon, but there is no guarantee that a program will follow those guidelines. Even more problematic is the fact that a programmer must maintain the dependencies in the makefile, and that is often a prescription for something that will not happen consistently. Finally, the makefile is not required to make a program, or programmers will keep multiple makefiles around for development, testing, production, etc. The validation that a program is correct for a particular environment is thus an externally applied one and is dependent on the programmer updating and using the correct makefile.

Java has borrowed from Ada to correct these problems with the make utility. At its core, the problem with make is that it relies on external factors to ensure that a makefile is current and used correctly. At best, however, the makefile is only a guess regarding the actual dependencies, and the only place where the actual dependencies are present is in the source code for a program. Because the program does contain the actual dependencies, the compiler is able, as part of the compiler process, to build the dependency list and ensure that it is correct and current. This is a formal definition of the dependencies, and it is internally verifiable. The dependencies are generated and checked as part of the Java compilation, and Java will automatically do the equivalent to a "make" on a Java program.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net