RPG programmers who have already made the journey to Java report that the transition to Java is made far easier if you have a footing in RPG IV, rather than RPG III. This is because new constructs exist in RPG IV and its ILE (Integrated Language Environment) definition that are more modern and a closer match to Java and its environment than RPG III. Many of the new concepts you need to learn in Java, such as modularity, free-format expressions, methods, and method calling, are easier learned first through their counterparts in RPG IV.
You should also be continuing your code migration to ILE RPG IV from RPG III, even if you are eventually destined for Java. Moving from RPG III to IV will give you short-term quality, maintainability, functionality, and performance benefits. Such a progression will position your code base to make selective replacements with new Java code, and give you more options for intermingling RPG and Java. You will have a far better base than if you have a monolithic RPG III application model.
For these reasons, we have chosen to teach you Java by comparing it to RPG IV. However, we realize you might not have made the transition to this greatly improved version of the language yet, so the beginning of this chapter reviews the overall concepts of ILE and RPG IV. These are the "outer shell" concepts, not the internal language definition details. As we go deeper into the Java language, we will continually contrast it to appropriate details in RPG IV, and where there are new functions in the RPG language, we will briefly review them before diving into their Java counterparts. In this way, this book will make you more comfortable not only with Java, but also with RPG IV. You end up with two very important skill sets, so start thinking about that salary increase now! If you are familiar with RPG IV and ILE, you can safely skip these RPG reviews, of course. And presumably you have already received half of your deserved salary increase!
This is an extensive chapter that covers a good deal of ground, but when you are done, you will be well on your way to being familiar with the architecture and interfaces of Java applications, and of RPG IV as well. (Note that the terms RPG IV, ILE RPG, and ILE RPG IV terms are used interchangeably here.)
In a typical, traditional RPG III application, you might have a CL program that gets initial control and then calls the appropriate RPG program or programs. The RPG program, of course, is huge. It contains mainline code and includes dozens, if not hundreds, of subroutines. For large applications, there may be dozens or even hundreds of these RPG programs, but due to the overhead of calling them, each does a significant amount of work when called, usually driving an entire piece of the application. The CL program might put up a main menu and process the selected option by calling the appropriate RPG program. It also, of course, performs file overriding and library list manipulation. The application is "scoped" to the job, so that common resources, such as open data paths and file overrides, are available and applicable to all the programs in the job.
Now, contrast this with what a typical ILE RPG application might look like. First, ILE redefines the compilation unit: rather than compiling a source member directly into a program object (*PGM), in ILE you compile a source member into an intermediate module object (*MODULE) using the CRTRPGMOD command. These modules are not shipped with your application, and you cannot call them from the command line as you call a program. Rather, they are designed to be subsequently "bound together" into a program object using the CRTPGM command.
There is also a CRTBNDRPG (Create Bound RPG) command for creating single-module program objects in one step, but you should only use this as a short-term solution as you convert to RPG IV. The power of ILE is that modules compiled from different languages, such as ILE CL and ILE RPG (or ILE C, ILE C++, or ILE COBOL), can be bound together to form a single program object. This program object is no different than what you are used to—you call it from the command line or via a command (*CMD) object. The question is, which module gets control from the command line? The answer is that by default, control is given to the first one specified on the CRTPGM command that created it. (You'll learn more about this later.)
Why, in RPG III, did we write such large programs? Performance, of course. It is simply too expensive to make calls between programs using the external CALL op-code. The downside to using these large programs includes the following disadvantages:
ILE set out to improve the development and application model. It strikes at the very heart of why we write large, monolithic programs—the performance of inter-program calls. It allows us to write smaller pieces of code that get compiled into intermediate modules, which then are glued or bound together to form the final program object. These modules can call each other without the performance penalty commonly encountered with program calls. Here is how ILE solves the problems just mentioned:
You are probably wondering how all of these modules "call" each other. You can simply call the module by name, in which case the mainline C-specs of that module will get control. This is very much like calling another program, but faster, since the address of that module will have been determined and recorded when the modules were bound together with CRTPGM. However, you can also call directly into a specific procedure in another module. Procedures resemble subroutines, but unlike subroutines, they can be directly called by code in another module, as shown in Figure 2.1.
Figure 2.1: ILE modules
Procedures are "grown up" subroutines that allow multiple entry points into a module, as opposed to only a single mainline entry point. They also offer numerous other advantages over subroutines, such as local fields. (Finally, RPG has local fields!)
Once you have all your modules with their associated procedures, you bind the modules together to form a single program. If you want to facilitate reuse of your procedures by more than one program object, you can either bind the same module into multiple program objects or, preferably, put them into a service program instead. Service program objects (*SRVPGM) are new entities in ILE. Unlike programs, they cannot be called from the command line. Rather, they are intended to be called programmatically by code inside one or more program objects. Once again, it is the procedures inside the service programs that are called. (Note that like programs, service programs can consist of one or more modules.)
An important aspect of having these modules inside programs and service programs is that, if you change the source code for one module, you can recompile that module and selectively replace or update it inside the program or service program, using the UPDPGM and UPDSRVPGM commands. This avoids the need to recompile all of the source code that comprises the program or service program. Indeed, for service program updates, often you do not even need to re-link all the programs and service programs that use it. They get the benefit of the change without requiring a re-build or re-ship.
You can take commonly needed subroutines, turn them into procedures, and package them into a single service program object that all program objects can link to. The link is done by specifying the service program(s) on the BNDSRVPGM parameter of the CRTPGM command. Compare this to the old tricks of copy-and-paste or /COPY that lead to maintenance problems and bloated programs.
The inter-module procedure calls within the program and service programs are done using a new CALLP op-code, which provides better performance than a full external program call using CALL. This is because at bind time (CRTPGM or CRTSRVPGM) all calls between modules or service programs are resolved to actual addresses. The overhead to resolve addresses is done once at build time, rather than every time when running the application.
In traditional workstation or PC languages like C or C++, the concepts of ILE programs and service programs correspond to executables (.exe) and dynamic link libraries (.dll). The concept of modules corresponds to object (.obj) files. (All of these new ILE constructs will be described in greater detail later, as we compare them directly to their counterparts in Java.)
Finally, in your new ILE application, you have an activation group inside a job. These are also new constructs for ILE. You can have more than one activation group per job, and all of your application's resources can be scoped to one activation group, as opposed to the entire job. Resources in one activation group do not get shared with another activation group. This allows for more fine-grained control over important aspects, such as file overrides and open data paths. Activation groups can be named so that they persist for the life of the job; they can also be defined so that they are created and destroyed with the life of the program.
For performance reasons, it is very important to use named activation groups, which is not the default. You define this on the ACTGRP parameter of the CRTPGM or CRTSRVPGM commands. Activation groups are very important to understanding and mastering ILE, and all ILE books and manuals discuss them. However, they have no relevance to learning Java, so we defer detailed discussion of activation groups to these other ILE books.
The sections that follow "peel the onion," looking at RPG IV and Java from the outside in. (No crying, now!)
In ILE RPG IV, your applications have one or more programs linked to zero or more service programs, as in Figure 2.2. Each *PGM and *SRVPGM object is comprised of one or more modules.
Figure 2.2: ILE service programs
In Java, your application is comprised simply of a number of class files (.class).Class files contain Java bytecode, and are compiled from Java source. Recall that bytecode is not machine code that a traditional compiler produces, but simply a more efficient way to represent your source.
You will have a primary .class file that gets invoked from the command line, and many additional class files that are used by this first class file, as shown in Figure 2.3.
Figure 2.3: Java classes
In RPG IV, source members are compiled into modules (the compilation unit) using CRTRPGMOD (Create RPG Module). Modules are bound together using CRTPGM (Create Program) or CRTSRVPGM (Create Service Program) commands, specifying the modules on the MODULE parameter. If you use other service programs, you specify them during the bind step, on the BNDSRVPGM parameter.
When you link in service programs during the bind step, you are not gluing them onto your object like you do when binding modules together. Rather, you are simply recording the name and address of the service program. You still get fast calls to the procedures in the service program, but your object size does not grow. Further, if two programs use the same service program at runtime, only one copy of the service program will be resident in memory.
You compile Java source code (.java) into bytecode (.class) using the javac compiler. There is no bind step equivalent to ILE's CRTPGM that benefits runtime performance, but compile-time checks are made on the referenced classes to ensure that they do indeed exist and have the referenced entry points.
In Java, there are no programs or service programs, only classes. Classes are much like ILE modules, which instead of being further processed into an executable object, are simply loaded into memory and interpreted at runtime. This is a consistent analogy to the Java compiler (javac) as only the first half of a traditional compiler. Where RPG and other traditional compilers will continue past that intermediate representation to produce machine code, Java stops at the intermediate phase, so that the result is still operating-system independent.
At runtime, when a Java class file being interpreted refers to, or "calls," another class file, that class file is searched for and found. There is no linking step at development time to perform this. This dynamic loading of referenced code might seem like a throwback to pre-ILE, and indeed, it does have performance implications. However, dynamic loading has significant productivity and maintenance advantages, and Java chooses to focus on these attributes. When you start to work with Java, you will also notice the performance overhead of finding all referenced classes at the startup time of the application. All referenced classes are found and loaded into memory up front when the first class is called from the command line. This makes startup slower, but makes the subsequent running of the application faster.
An important note about AS/400 Java is that you typically do not compile with javac. You certainly can, but this step will usually be performed on your workstation, such as on Windows using either the Sun JDK or VisualAge for Java. The resulting .class file will then be copied or transferred to the AS/400 IFS (Integrated File System), where it will reside. Thus, you do not use SEU (Source Entry Utility) to create and edit your Java source, but rather workstation-based tools.
Figure 2.4 shows at a high level what an RPG IV module might look like. As you can see, modules contain global fields and often one or more procedures.
Figure 2.4: Inside an ILE RPG module
Procedures are a very important addition to the RPG language, and were introduced in V3R2. Essentially, they are beefed up subroutines. However, when compared to a subroutine, a procedure offers the following significant advantages:
RPG procedures and ILE go hand-in-hand. What happened to subroutines, you might ask? Nothing! They still exist, and you are still welcome to use them. With the advantages of procedures, however, you will find using subroutines a step backward. In general, using subroutines is a sub-standard practice, even though it is still routine.
Figure 2.5 shows what a typical procedure looks like. (The terms variable and field are used interchangeably in the book, as you see in the figure.)
Figure 2.5: Inside an RPG IV procedure
You call an RPG IV program from the command line by using the CALL command and passing the parameters:
CALL PROGRAM1(100 200);
This passes control to your program. Specifically, it passes control to the entry module that you specified on the ENTMOD (Program Entry Procedure Module) parameter of the CRTPGM (Create Program) command. The ENTMOD parameter defaults to the first module specified in the MODULE parameter list, or more precisely, to the first module that does not have NOMAIN specified on the H-spec. (The NOMAIN keyword is used to create modules that have only procedures and no mainline code.) The entry module gets control from the command line in its initial calculation specifications (C-specs). These initial C-specs are referred to as the main procedure in the ILE RPG/400 Programmer's Guide. This is a bit of a misnomer, in that there is no formal procedure declaration for them. We simply refer to it as the mainline code. Further, we only use the term procedure for user-defined procedures, not sub-procedure as the manual describes them.
From the mainline code, control is passed to subroutines in the same module or to procedures in one of the following:
Procedures are called using the CALLP op-code or as part of an expression in an EVAL statement. The latter is used when you are interested in the returned value.
You see that procedures can become the primary mechanism for control flow within an application. The reality, however, is that many have not discovered the joys of procedures yet, and in that case they are simply calling each module directly. When you call a module directly from another module, you are calling the mainline code of that module (or officially, the main procedure of that module). The compiler creates this main procedure for you, with the same name as the module, unless you specify the NOMAIN keyword on the H-spec. If you do so, then you can only call the exported procedures in this module. Further, you cannot call such a module from the command line.
We pay so much attention to RPG IV's procedures not only because they are so great, but also because they are a direct match to Java's methods. Methods in Java are equivalent to subroutines and procedures in RPG. They contain executable code that is callable by others. Like procedures, they have local variables (you don't call them fields in Java), they take parameters, they return values, they can be called recursively, and they can be called by code outside of the class file they are in. Further, since Java does not have mainline code like RPG does, they are indeed the only place you write executable code in Java.
Figure 2.6 shows at a high level what the inside of a Java method looks like. Unlike RPG procedures, the variables inside a Java method can be declared anywhere, as long as it is prior to their use. Given our RPG background, we prefer they all go at the top of the method. You will see the syntax of methods soon.
Figure 2.6: Inside a Java method
You call a compiled Java source file from the command line by using the java command and passing the parameters:
java MyClass 100 200
This passes control to your class file. Specifically, it passes control to the main method in your class file. You are required to code a method named main if you want to call your class file from the command line like this. We'll dissect this important method later in this chapter.
Notice the use of a PC-style call, versus an AS/400-style CALL command. A PC-style call is used even on the AS/400, where you have to run Java code not from the usual CL command, but from the QShell environment, which is similar to an MS-DOS prompt on Windows. However, there is also a CL JAVA command, if you prefer. (Using Java on the AS/400 is covered in Appendix A.)
Java class files are compiled from Java source files. What is in a class file? Methods, as you know. But these are actually inside a class, which is a Java language construct with explicit syntax that you will soon see.
Control flows from one class to another by means of method calls. The initial main method might call other methods in this same class, or methods in another class. Since Java has no mainline code, it is not possible to call a class directly, just a method in a class. This is different than RPG, where you can call a module directly, as long as it is not coded with the NOMAIN keyword.
Figure 2.7 shows what a class named Class1 looks like, at a high level, inside a compiled .class file. The variables declared inside the class, above the methods, are available to the code in all the methods. The variables declared inside the methods themselves are local to that method.
Figure 2.7: Inside a Java class file
The variables inside a class can be defined before or after the methods. We prefer before, so programmers understand the context when they see the variables in use later. It is simply a style choice, though, and is entirely up to you.
Java classes contain global variables that all methods can access. They also contain local variables inside each method, which only that method can access. The same applies to RPG modules; they can contain global fields at the top, defined with the new definition specification (discussed in Chapter 5) and local fields inside each procedure. Table 2.1 provides an initial comparison of RPG constructs to Java constructs.
RPG |
Java |
Comments |
---|---|---|
*PGM |
Application |
Program object = Application |
*MODULE |
Class |
Module object = Class file |
Fields |
Variables |
Global fields or variables |
Procedures |
Methods |
Functions |
Fields |
Variables |
Local fields or variables |
Code |
Code |
Executable code |
This mapping will evolve, and it is not perfect. However, it should help you get your bearings in this new world of Java.
Before delving into a comparison of Java and RPG syntax, it is important to review RPG IV's syntax at a high level, for the benefit of RPG III readers. Although we don't go into great detail here, many subsequent examples will make it all clear.
RPG IV source members are now 100 characters wide versus 80, giving more room in each area. (Comments are entered from column 80 on.) You can code blank lines wherever you wish. Further, you can enter the text in all-uppercase, all-lowercase, or mixed case, although the compiler folds it all to uppercase.
An RPG IV program consists of specifications, which must be in the correct order. The specifications are Control (H), File (F), Definition (D), Input (I), Calculation (C), and Output (O), as well as Procedure (P) specifications. The E and L specifications are gone, and there is a new D-spec. Further, the H-spec is now completely keyword driven, and you can specify keywords on the F-spec, eliminating the need for F-spec continuation lines (although keywords can continue on the subsequent line). The new D-spec is for explicitly defining fields up-front. The C-spec has been widened, and there are even a number of new op-codes that allow a very wide factor-two area and no result area. These op-codes, like the new EVAL op-code for field assignments, use free-format expressions in factor-two, which can be easily continued on subsequent C-specs without continuation characters.
There are also an exploding number of built-in functions of the form %xxx(parameter1:parameter2), which you can call from inside any free-format expression. Their return values are substituted right into the expression, as in:
12345 *890123456789012345678901234567890123456789012345678901 D*Name++++++++++++Ds++++++++++Len+IDc.Keywords++++++++++++ D aField S 9P 2 INZ(0) D aFieldLen S 5P 0 C* Factor1+++++++Opcode(E)+Extended-Factor2++++++++++ C EVAL aFieldLen = %LEN(aField)
Here, two "standalone" (S in column 24) fields are declared: aField, which is packed-decimal 9,2 and aFieldLen, which is packed decimal 5,0. The former is initialized to zero using the INZ keyword. Then, we have a C-spec that uses the new EVAL op-code to assign the length of aField to the field aFieldLen, using the built-in function %LEN. Note you can also code built-in functions as parameters to the INZ keyword.
One really big enhancement to the language in V5R1 is the ability to code completely free-format calculation specifications, using the new /FREE and /END-FREE compiler directives to delimit the free-format lines of source. The details of this aren't covered here, but it is an exciting addition to the language, and you will find it makes your RPG and Java code look more similar than ever.
As well as the specifications already mentioned, there are, optionally, procedures made up of a beginning and ending Procedure (P) specification, within which are D-specs and C-specs.
The following sections show you how to define and prototype procedures in RPG. We teach this for two reasons: procedures are a very powerful and under-used tool in RPG, and learning them will help you learn methods in Java.
To use a procedure in RPG, you must prototype it first. This involves the use of the new D-spec with the PR definition-type. A prototype is essentially a pre-declaration of a procedure's name, return value (if any), and parameters (if any). This prototyping is done in your source prior to the definition of the procedure, and must be placed in the D-spec area at the top of the file. Most commonly, it is in a copy member that all users of the procedure copy into their source, using /COPY. Here is an example of a prototype for a procedure Swap that returns nothing and takes in two parameters, each length 5, internal type integer (new RPG IV type), and zero decimals:
D* 1 2 3 4 5 6 D*89012345678901234567890123456789012345678901234567890 D*Name++++++++++++PR++++++++++Len+IDc.Keywords+++++++++ DSwap PR D parm1 5I 0 D parm2 5I 0
Here is another example of a prototype of a procedure Max that returns a value (packed-decimal 7,2), and which takes in two parameters (also packed-decimal 7,2). Because these parameters are not changed in the procedure, the keyword VALUE is coded to indicate they are pass-by-value:
D* 1 2 3 4 5 6 D*89012345678901234567890123456789012345678901234567890 D*Name++++++++++++PR++++++++++Len+IDc.Keywords+++++++++ DMax PR 7P 2 D parm1 7P 2 VALUE D parm2 7P 2 VALUE
The name of the procedure is defined on the PR D-spec, as is the return type information (if any) in positions 36 through 42. You can specify keywords in positions 44 through 80 that apply to callers of the procedure, such as EXTPROC('name') for giving a different name for external callers. You can also specify keywords that apply to the returned type, such as DIM if it is an array. Note there is no name associated with a return value.
The parameter definitions follow. The compiler takes as parameter definitions all subsequent D-specs with blanks in positions 24 and 25 (where PR stands for prototype). The parameter variable names are optional, and need not be the same as those specified in the actual procedure definition. All names can float anywhere between positions 7 and 21. One or more keywords can be specified for each parameter, such as VALUE to pass a copy of the field or CONST to pass a read only reference, or OPTIONS(*NOPASS) if it is an optional parameter. (All subsequent parameters must also specify this keyword.)
The purpose of the prototype is to allow the compiler to verify, for each procedure call, that you have specified the correct number of parameters and the correct type, and are assigning the returned value to a field that has an appropriate type.
Having prototyped an RPG procedure with its parameters and return type, the next step is to actually define the procedure. RPG procedure definitions start with a new specification type—the P (Procedure) spec. A begin-procedure specification and an end-procedure specification are located at the beginning and end of the procedure. These are indicated by a B and an E in position 24 of the P-spec, respectively. In columns 7 to 21, you place the name of the procedure (leading blanks are okay), which must exactly match the name specified on the prototype:
P* 1 2 3 4 5 6 P*89012345678901234567890123456789012345678901234567890 P*Name++++++++++++B+++++++++++++++++++Keywords+++++++++ PSwap B EXPORT * ... PSwap E
On the begin-procedure specification, you can specify keywords in positions 44 to 80. At this time, the only valid keyword for a procedure is EXPORT, meaning this procedure is to be accessible by code inside other modules. You'll probably use this one a lot!
Next, you simply copy all the prototype D-specifications to be after the first P-spec. However, you must change the PR on the first one to PI for procedure interface. It is always a complete duplication; that is, you once again define the D-spec, which identifies the return type information (and uses the procedure name again), and follow that with the D-spec for each parameter. You also repeat any keywords:
P*Name++++++++++++B+++++++++++++++++++Keywords+++++++++ PSwap B D*Name++++++++++++PI++++++++++Len+IDc.Keywords+++++++++ DSwap PI D parm1 5I 0 D parm2 5I 0 * ... PSwap E
Having defined the return value and parameters (if any) with the procedure-interface specs, you define any needed local variables with D-specs and an S (for standalone) or DS (for data structures) in positions 24 and 25. Then, the logic follows in the C-specs and finally in the RETURN statement. You cannot define any other specs, such as an F-spec, locally in a procedure; these are defined only at the top of the module.
Listing 2.1 shows the two procedures. (Including comment prologs; aren't we good?)
Listing 2.1: RPG IV Procedure Examples
P* ————————————————————————————————————————————————————— P* Definition for Swap procedure P* Function.: swap the values inside two integer fields P* Returns..: nothing P* Parameter: parm1 => 5 digit integer field P* Parameter: parm2 => 5 digit integer field P*——————————————————————————-————————————————————————˝——— PSwap B EXPORT DSwap PI D parm1 5I 0 D parm2 5I 0 D*Local fields D temp S 5I 0 C*Local code C EVAL temp = parm1 C EVAL parm1 = parm2 C EVAL parm2 = temp C RETURN PSwap E P* ————————————————————————————————————————————————————— P* Definition for Max procedure P* Function.: determine the larger of two packed fields P* Returns..: nothing P* Parameter: parm1 => 7,2 packed field P* Parameter: parm2 => 7,2 packed field P* ————————————————————————————————————————————————————— PMax B EXPORT DMax PI 7P 2 D parm1 7P 2 VALUE D parm2 7P 2 VALUE D*Local fields D bigger S 7P 2 C*Local code C IF parm1 > parm2 C EVAL bigger = parm1 C ELSE C EVAL bigger = parm2 C ENDIF C RETURN bigger PMax E
Procedure definitions go at the end of your mainline C-specs, but before any O-specs or array compile-time data. The procedure name can be up to 4,096 characters long! If it is more than 15, use ellipses to extend it onto the next line (using as many lines as necessary), like this:
PGenerateEndOfWeekPayroll... P B EXPORT
If you are a CODE/400 user, you will find in the CODE/400 editor a SmartGuide or wizard that prompts you for the name, parameter, and return information, and generates the prototype and shell of the procedure for you. It also handles long names correctly.
How you call a procedure depends on whether you want to use the returned value or not. If not, you simply call it with the CALLP op-code. If so, you call it in the right side of an EVAL expression, much the same as you would call a built-in function. In all cases, parameters are placed inside parentheses and separated by colons. You can add spaces wherever you like. Listing 2.2 shows the mainline code to call our procedures. (For your information, the output of this program is 15, 25, and 25.99, as expected.)
Listing 2.2: Calling Procedures in RPG IV
* ===================================================== * Global fields * ===================================================== D integer1 S 5I 0 INZ(25) D integer2 S 5I 0 INZ(15) D packed1 S 7P 2 INZ(25.99) D packed2 S 7P 2 INZ(15.06) D biggest S 7P 2 INZ(0) * ===================================================== * Mainline code * ===================================================== C* call Swap procedure C CALLP Swap(integer1 : integer2) C integer1 DSPLY C integer2 DSPLY C* call Max procedure C EVAL biggest = Max(packed1: C packed2) C biggest DSPLY C* end the program C EVAL *INLR = *ON
If the procedure does not take any parameters, no parentheses are specified. However, in V5R1 or later, you are at least allowed to code empty parentheses, which makes it clear to other programmers that this is a method call versus a field name.
The following sections give your first introduction to the syntax of Java code. We will build on this in this chapter and subsequent chapters. Get ready, your Java journey begins...
What does a Java class look like, exactly? Its syntax is shown in Figure 2.8.
Figure 2.8: The anatomy of a Java class
You declare the class in the .java source file using explicit syntax to start and end the class. It optionally starts with the keyword public (a modifier), followed by the required keyword class, followed by the name you wish to give it. Finally, curly braces { and } start and end the body of the class. All variables and methods are defined between the braces, and the closing brace will always be the last non-blank line of the .java source file.
An interesting thing to note is that the name of the class must match exactly, including case, the name of the Java source file, and therefore, the name of the compiled .class file. You can legally define multiple classes per file, as long as only one is defined with public, but each becomes a separate dot-class file after using javac to compile. However, it is considered bad form to define multiple classes per source file, except when defining inner or nested classes (discussed in Chapter 14).
The public modifier in Figure 2.8 indicates this class is freely accessible to all other classes that want it. This modifier is also required for a class to be run from the command line.
Here is the syntax for defining a Java class:
class Name { ... }
Specifically, Java class syntax involves the following:
Here is an example of an empty class:
public class SalariedEmployee { }
This class is named SalariedEmployee and is accessible by all other classes, since it is public.
Inside the braces of a Java class are the variables and methods of that class. Variables are declared using the following syntax:
type name <= initial-value>;
The optional modifiers include public and private, which dictate whether or not other classes have access to this variable. With no modifiers, only this class and other classes in this package (described later) have access.
The type is one of eight predefined data types, discussed in detail in Chapter 5. They are byte, short, int, long, char, boolean, float, and double. The first four are numeric signed integers of increasing size, while char is for single characters, boolean is for true/false variables, and float and double are single- and double-precision floating-point variables.
The convention for the name is the same as for methods, discussed in the next section. After the name, you can optionally assign an initial value. The declaration ends in a semicolon, as indeed all statements do in Java.
Here are a few example Java variable declarations:
private int commissionRate; public float taxRate = 0.08; char unitType = 'H';
The variable commissionRate is only accessible by code inside this class (private), and is of type integer (int). The variable taxRate is accessible by code in all other classes (public), with a single-precision floating-point type (float), initialized to 0.08. The variable unitType is a character (char) initialized to H, and is accessible by all classes in this package, which is the default if you do not specify public or private. Note that you do not declare the number of digits in Java; you just state the type. Each type has a predefined byte-length allocated for it: one byte for byte, two for short, four for int, eight for long, two for char, one for boolean, four for float, and eight for double.
In addition to these eight primitive data types, variable types can be the name of a class (which you will see lots of soon). Indeed, Java supplies a number of classes, to make up for the limited number of primitive data types. For example, the all-important String class holds character data that is more than one character long.
Here is the syntax for defining a Java method inside a class:
return-type name() { ... return return-variable; }
Specifically, the Java method syntax involves the following:
There are two predominant styles for Java braces delimiting a method. In the first style, the first brace is placed right on the method declaration line, like this:
public void myMethod() { ... }
In the second style, the first brace is placed on its own line, like this:
public void myMethod() { ... }
We prefer the latter because it is easier to line up the braces in an editor to ensure that you have not missed one. C programmers prefer the former, though.
Note there is no prototyping of methods in Java, unlike RPG, which requires you to prototype your procedures (as C and C++ do, also). How does the compiler verify the parameters are correct on a call? It actually goes out to disk and finds the class file containing the method being called, and looks in it!
To help bring together the syntax discussions from the previous sections, let's look at a complete Java class, shown in Listing 2.3. This class, named ItemOrder, holds three pieces of information about an order: its ID, the quantity of the order, and the unit cost of the items ordered. This means three variables. Two methods are defined in the class, one for setting the order information, and the other for returning the total cost of the order. The former requires three parameters, one for each piece of information, and sets the values of the variables from those parameters. The latter takes no parameters, but returns the unit cost multiplied by the quantity.
Listing 2.3: A First Class in Java: ItemOrder
public class ItemOrder { private int id; private int quantity; private double unitCost; public void setOrderInfo(int newId, int newQuantity, double newUnitCost) { id = newId; quantity = newQuantity; unitCost = newUnitCost; } public double getTotalCost() { double totalCost = quantity * unitCost; return totalCost; } }
Don't worry about understanding everything about this class yet; we just want to start acclimating you to seeing Java code. It will all become clearer as you go. You should, however, be able to identify the declaration of class ItemOrder; the declarations of the variables id, quantity, and unitCost; and the methods setOrderInfo and getTotalCost. Also notice we declare and initialize a local variable named totalCost inside the method getTotalCost, and initialize it to an expression.
If you have typed this code in, you might as well finish the job by compiling it:
>javac ItemOrder.java
Please note that you must use the exact same case even here, when specifying the name of the Java file!
Of course, this class by itself is of little value. It is designed to be used by other code, not be called from the command line. You will see soon how one class in Java uses another.
It is a convention in Java and other OO languages to define all of your global variables as private (or protected, as discussed in Chapter 9) so that code in other classes are not permitted to read or write those variables directly. Rather, if you want to allow others access to them, you supply getXxx and setXxx methods. For example, you might have a private variable named name, supply a public method named getName to retrieve it (it would return name), and another named setName to set it (it would take the new name value as a parameter assigned to the private name variable).
These methods are affectionately known as getters and setters, or more officially as accessors and mutators. Variables are almost always implementation details that are best left hidden, which is what using private does. This gives you the flexibility to later change the variable type, length, name, and so on, while keeping the get and set method interface constant, and therefore not disrupting any users. Also, it is a matter of data integrity. Only your class code should have direct access to the variables; you do not want to run the risk of someone changing them to unexpected values. This is similar to your database—end users are not able to edit the data directly, using SEU, say. Instead, they must call your RPG program to access the data. Only by restricting access to your data and your variables can you ensure that the state of the data is always valid.
Before you learn how to use classes in Java, you should learn something relatively new in RPG IV that will make the subsequent Java discussion easier for you. We are going to define a data structure in RPG IV that will hold information about an item order, just like the Java class in Listing 2.3 does. However, we are going to use the keyword BASED when we define it. This keyword tells the RPG compiler not to allocate memory for this data structure. Instead, the memory for it will be pointed to by the pointer field passed as a parameter to the BASED keyword. So, we will also create a memory pointer field, using the data type character * to identify it as a pointer. Then, we will have to actually allocate the memory and set that pointer field to point to it. With that, we will be able to access the subfields of the data structure because it will be assigned a memory location finally.
To allocate the memory, we use the new ALLOC keyword, which requires the amount of memory to allocate in factor-two, and the pointer field to set in the result field. The amount of memory we want is enough to hold the data structure, so we will use the %LEN built-in function in RPG IV to return that size, and put the result in a field, which we will then pass to the ALLOC operation. Got it? Let's have a look at it, in Listing 2.4.
Listing 2.4: Managing Memory for an ItemOrder Data Structure in RPG IV
D*Name++++++++++ETDs++++++++++Len+IDc.Keywords++++++++++ D ItemOrder DS BASED(order@) D id 5P 0 D quantity 5P 0 D unitCost 9P 2 D orderLen S 5U 0 INZ(%LEN(ItemOrder)) D order@ S * INZ(*NULL) C* Factor1+++++++Opcode(E)+Factor2+++++++Result++++++ C ALLOC orderLen order@ C* Factor1+++++++Opcode(E)+Extended-Factor2++++++++++ C EVAL id = 1 C EVAL quantity = 10 C EVAL unitCost = 35.99
Do you follow it? The D-specs you see are how we explicitly declare fields in RPG, and the name of the field can float anywhere from column 7 to column 21. Columns 24 and 25 require DS for a data structure, blanks for a subfield, and just S for a standalone field (that is, not a data structure and not a subfield). Columns 36 to 39 hold the length, column 40 holds the data type, and columns 41 and 42 hold the number of decimals. Keywords go in columns 44 to 80. One popular keyword is INZ for initializing the field. (More of the D-spec is covered in Chapter 5.) This example also shows the new EVAL op-code for assigning variables. The assignment expression, of the form field-name = expression, goes in free-format factor-two (column range 36 to 80).
Syntax aside, the point of this example is to show how you can define a structure with the BASED keyword and allocate your own memory for that structure. While this is not required in RPG IV, and indeed, is rarely done, it does allow you to write some interesting applications. For example, you could allocate multiple occurrences of the same data structure, and thus simultaneously store information about a number of orders. To access any one order, you would just assign the pointer field on which the data structure is based to the particular pointer field for the particular order's allocated memory, for example:
D* Item Orders D order1@ S * INZ(*NULL) D order2@ S * INZ(*NULL) C* ALLOCATE ORDER ONE: C ALLOC orderLen order1@ C EVAL order@ = order1@ C EVAL id = 1 C EVAL quantity = 10 C EVAL unitCost = 35.99 C* ALLOCATE ORDER TWO: C ALLOC orderLen order2@ C EVAL order@ = order2@ C EVAL id = 2 C EVAL quantity = 5 C EVAL unitCost = 26.15
This is really just like using multiple occurring data structures and using the OCCUR op-code to set the current occurrence. Indeed, this is just what the RPG compiler and runtime do to support multiple occurring data structures. We show you how to do this so you understand in "RPG-speak" the idea of allocating memory and storing information in the memory according to a template or data structure.
When you think about it, this is not unlike a database. When you create a new physical file with a record format, the format is like a based data structure, in that it defines the fields that all memory (records) allocated in this database file will have. The allocation of that memory is done for you, though, each time you write a new record into the database.
Are you thinking that this code would benefit from a procedure? It would! The procedure could take as input the ID, quantity, and unit cost as parameters, and then allocate the memory, set the order@ field, and assign the parameter values to the data structure fields, and return a pointer to the allocated memory. If you named the procedure MakeOrder, the C-specs above would simply become this:
C EVAL order1@ = MakeOrder(1:10:35.99) C EVAL order2@ = MakeOrder(2:5:26.15)
Yes, that's a good idea you had! We'll get to it.…
Classes in action are called objects. With objects, we firmly leave the world of RPG and other 3GLs (third-generation languages). After discussing objects, we will describe in more detail how program flow happens in Java by way of interoperations among objects.
When you define a class, you are merely defining what the class will look like to the compiler. You have not defined something directly usable by other programmers. A class is merely a template; it does not reserve any memory or storage, just like a data structure in RPG IV with the BASED keyword, and just like a record format in a database. To use a class (put or get data in it or call methods in it), you must first allocate memory for it. You do this in two steps:
To do step 1, you simply define a variable whose type is the name of your class, like this:
ItemOrder order1;
To do step 2, you use the new operator in Java, which requires you to specify the class name to allocate memory for, followed by parentheses, like this:
order1 = new ItemOrder();
The new operator allocates enough memory for all of the variables in the identified class, and returns the address of that memory. In this example, that address is then assigned to the variable order1. The rule is that the class named on the new operator must be the same class named as the type of the variable, otherwise you will get a compiler error. Note that like all assignments of data to variables, you can do the assignment at the same time as you declare the variable:
ItemOrder order1 = new ItemOrder();
You can also define more than one variable with the same class type:
ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder();
Here, new is used twice to create two separate allocations of memory, using the same template for the memory—the class ItemOrder. Each memory allocation is called an instance of the class, or an object of the class. Allocating the memory using new is called instantiating an object. In this example, because each instance is unique, you can assign one set of values to the variables in order1 and another set of values to the variables in order2. Just like each allocated data structure in the RPG example held unique data, so too does each allocated or instantiated object in Java.
Any variable you declare in Java with a type that is a class name (instead of one of the eight primitive types) is called an object reference variable. These variables are always four bytes long and are always destined to hold a memory address acquired using the new operator. The allocated memory is called an object, remember, and the address of that object is known as a reference in Java. An object reference variable, like order1, contains a reference to an object.
This concept of an object variable being a reference to an object carries the implication that you can change, on-the-fly, which instance of the class the variable points to. For example, suppose you did the following:
order2 = order1;
This copies the memory address in order1 to order2, meaning both variables now refer to the same object. Any changes made to the data in that object via order1 will be reflected when you read the data via object2. What happens to that second object in memory, originally referenced by object2? If no other references exist to it, it will be vacuumed up by the garbage collector, which always runs in the background, looking for dead objects like this to reclaim. This is unlike RPG IV, where memory allocated with the ALLOC op-code must be explicitly freed up when you are done with it, using the FREE op-code. This tedious but important task is done for you in Java, automatically.
You have not yet seen how to read and write the variables inside an object, or call a method inside an object, but you will soon. For now, you should be comfortable with the following ideas:
The classic analogy is that a class is a cookie cutter and an object is a cookie. From one cutter, you get as many cookies as you want, all with the same shape. Not to worry though, you won't be writing crumby code!
There are two final terms you need to know. The variables defined in a class are called instance variables because their value depends on the instance of the class, since each instance gets unique memory for those variables. So, the three variables id, quantity, and unitCost defined at the top of class ItemOrder in Listing 2.3 are instance variables. On the other hand, variables declared directly inside a method are called local variables, since their value and access is local to the method. So, the variable totalCost (of type double) in method getTotalCost in class ItemOrder is a local variable.
Remember the analogy of classes to based data structures in RPG IV? And of objects to allocated memory for a based data structure? The one thing missing from that analogy is the methods in the class. RPG data structures cannot have procedures, but objects have methods. For your information, when you allocate an object using new, the Java runtime also allocates extra memory to hold a table of pointers to the methods defined in the class. Then, when you call a method in an object, the runtime uses the pointer to find the method and run it. It also sets the registers internally, so that the method will use the unique data for this object whenever it references instance variables. (Don't worry; these concepts will become clear as you read the many examples in this book. You will be leaving a trail of cookie crumbs in no time!)
With an object, you can now access the variables and methods inside that object. Collectively, the variables and methods in a class are referred to as the class members.
Let's go back to the ItemOrder class in Listing 2.3. It has three variables, id, quantity, and unitCost, and two methods, setOrderInfo and getTotalCost. Let's write some code to instantiate (allocate) an object of that class and call the methods in that class. First, where to write this code? Well, all code in Java must exist in a method, and all methods must exist in a class. So, let's write another class! Call it PlaceOrders, in file PlaceOrders.java, as in Listing 2.5.
Listing 2.5: Creating the Java Class PlaceOrders that Uses the Class ItemOrder
public class PlaceOrders { public void placeAnOrder() { ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder(); double order1TotalCost; double order2TotalCost; order1.setOrderInfo(1, 10, 35.99); order1TotalCost = order1.getTotalCost(); order2.setOrderInfo(2, 5, 26.15); order2TotalCost = order2.getTotalCost(); } }
The method in the class is named, arbitrarily, placeAnOrder. You need a method, any method, to write the code in. Since this method does not return anything, the void keyword is coded for the return type, and a return statement is not necessary. Both the class and the method are public, so later perhaps you can use both from other classes. If you did not make the class public, you would have restricted who could use the new operator to instantiate this class. If you did not make the method public, you would have restricted who could call this method.
Speaking of calling methods, let's look inside the code to see how that is done. Two object reference variables are declared, order1 and order2, of type ItemOrder, and set to refer to separate objects created with the new operator. Then, two variables of type double are declared, order1TotalCost and order2TotalCost, with the intention of later assigning these to the returned value from the getTotalCost method. That method in ItemOrder is declared to return a type of double, so it is important the variables have the same type.
Now to the actual calls. For each object, two methods are called in it. The first is a call to the setOrderInfo method, passing in literal values for the three parameters it expects: the item ID, quantity, and unit cost. This method returns nothing, so you don't assign the result to anything (much like using EXSR or CALLP in RPG). Notice that the parameters are passed separated by commas (versus RPG, where parameters to a procedure call are separated by colons), and you can put as many blanks between the parameters as you like (or none at all).
The second method call for each object is to the getTotalCost method, which takes no parameters, so you have to just code empty parentheses (versus RPG, where you can code no parentheses). However, this method returns a value that needs to be saved, so the entire method call is assigned to the variables order1TotalCost and order2TotalCost. Assigning an entire method call to a variable is pretty cool, and you can do the same thing in RPG when calling procedures that return values.
The syntax for calling methods, then, is to simply code the method name and qualify it by prefixing the method name with the name of an object and a dot. You do not have to set the "current occurrence" as you do in RPG, because this qualification tells Java explicitly the occurrence you wish to access. The dot between the object reference variable name and the method name is called a dot operator in Java.
Java parses these calls at runtime by first recognizing the method call because of the parentheses, and then finding the method by going to the memory address stored in the object reference variable used to qualify the call. This finds the method in the method table there and finds the data there as well for this instance. At compile time, Java will verify that the method named in the call exists in the target class. It does this by tracing back to where the object reference variable was declared, getting the name of the class used as the type there, and then finding that class's .class file on disk. It then opens the file and looks inside it to see if the method exists there. If not, you get an error:
Method badMethodName() not found in class ItemOrder. order1.badMethodName(); ^
As with RPG procedures, you have three ways to call a method:
Table 2.2 compares the three invocation options in both languages.
RPG |
Java |
---|---|
EVAL myVar = myProc(p1 : p2) |
myVar = myObject.myProc(p1,p2); |
IF myProc(p1 : p2) = 10 |
If (myObject.myProc(p1,p2) == 10) |
CALLP myProc(p1 : p2) |
myObject.myProc(p1, p2); |
While Table 2.2 shows examples that take parameters, Table 2.3 shows, for the sake of completeness, examples that do not take parameters.
RPG |
Java |
---|---|
EVAL myVar = noParms |
myVar = myObject.noParms(); |
IF noParms = 10 |
if (myObject.noParms() == 10) |
CALLP noParms |
myObject.noParms(); |
You can access variables inside an object in exactly the same manner as you access methods, using the dot operator. The difference is that you don't use parentheses for variable names as you do for method names. For example, here is how you might change the variable id in the instance of ItemOrder referred to by order1:
order1.id = 3;
Because the compiler can distinguish between a method call and a variable reference by the existence or nonexistence of parentheses, you are allowed to have variable names that are the same as method names in Java. This is not true in RPG, where procedure names must be unique from field names. This is because calls to procedures in RPG do not require the empty parentheses, so they look identical to method references in an EVAL expression.
This is pretty easy stuff, we hope. If not, it will become easy! Note, however, that the line of code just shown will not compile. Do you know why? Because we happened to define all the variables in ItemOrder using the private modifier, so code in other classes is restricted from accessing it:
Variable id in class ItemOrder not accessible from class PlaceOrders.
Notice that code you write to access variables and methods in your current class does not require qualification with the dot operator. That is, the code you see in ItemOrder in Listing 2.3 uses the variables defined at the top of that class, so it just names them without qualification. When the Java compiler sees variable and method names without qualification, it looks in the current class, and only the current class, for them. If they are not found there, you will get an error.
Are you wondering how and when you are finally going to call your class from the command line? We are building up to it!
Using instance variables without qualification is equivalent to telling the compiler that you are referring to a variable in this class—in this class instance, actually. In fact, there is a special predefined object variable in Java named this. Although you can always live without it, sometimes you might want to use it to distinguish between local variables and class instance variables, if they have the same name.
You should never define variables local to a method that have the same name as instance variables declared globally at the class level, but sometimes you do give parameters the same name as instance variables. For example, in the ItemOrder class in Listing 2.3, you have a method named setOrderInfo, which defines three parameters. These parameter values are simply assigned, one by one, to corresponding instance variables for posterity. We chose in Listing 2.3 to use names for the parameters that did not conflict with the instance variables, as you recall:
public void setOrderInfo(int newId, int newQuantity, double newUnitCost) { id = newId; quantity = newQuantity; unitCost = newUnitCost; }
For parameters like this, where we simply intend to assign them to instance variables, we often find it too much work to come up with parameter names that are similar to their corresponding instance variable names, yet different. Instead, these types of parameters are named exactly the same as their instance variables, with the use of the this qualification to distinguish the instance variables from the local parameter variables of the same name. Listing 2.6 provides an example.
Listing 2.6: Updating the Java ItemOrder Class Using the this Keyword
public class ItemOrder { private int id; private int quantity; private double unitCost; public void setOrderInfo(int id, int quantity, double unitCost) { this.id = id; this.quantity = quantity; this.unitCost = unitCost; } public double getTotalCost() { double totalCost = quantity * unitCost; return totalCost; } }
When the Java compiler finds a variable name that is not qualified with anything via the dot operator, it looks for that variable first as a local declaration or local parameter in the current method. If it does not find the variable locally, it looks up to the class level to see if it is declared there. Think of parameter variables as being the same as local variables. So, in setOrderInfo in Listing 2.6, the use of the variable id results in a match with a parameter definition. To actually access the instance variable with the same name, you must qualify with this to force the compiler not to look locally.
Java allows you to have multiple methods with the same name, in the same class, as long as the number or type of parameters is different. This is a key concept in object-oriented languages like Java, and it is called method overloading. You are effectively overloading the name of the method with multiple definitions. This allows you to imply to users of your class that in each case, the operation to be performed is the same—only the inputs are different. You might, for example, have a String class that has two methods for substring: one that takes a single "position" number and returns the substring from that position until the end, and another that takes a position number and a "length" number and returns the substring from the given position for the given length. This makes the substring method more intuitive to use than having two methods named, say, substring and substringWithLength. Under the covers, the single-parameter version of substring will likely just invoke the multiple-parameter version, with the remaining length of the string supplied as the second parameter.
Methods of the same name can be made unique by differing the number or type of parameters. For example, you might have a method named max that returns the maximum of two given integer values, and another method in the same class named max that returns the maximum of two given float values. The compiler and runtime will determine which one you mean to use on any call to max, by comparing the number and type of parameters on the call.
Method overloading is a particularly important feature of Java, since the language does not allow you to define methods that take a variable number of parameters (or optional parameters). RPG procedures do allow for this, offsetting somewhat the need for overloading in RPG.
Both RPG and Java have a static keyword that can be used on variables. Java even allows it on methods. This is an important keyword that fundamentally changes the variable or method it applies to, as you will soon see.
When you define local fields in your RPG procedures, you can use the D-spec keyword STATIC to change the field content's lifetime. Rather than the field being reinitialized on every call to the procedure, it retains its value from call to call. This effectively makes it the same as a global field declared at the top of your program, outside of any procedure, but this field can still only be accessed by code in this procedure. In other words, it is still encapsulated to this procedure.
When would you use static fields? Whenever you want a local field to retain its value. Let's go back to the RPG memory-allocation example in Listing 2.4. We said at the time it would be nice to turn the memory allocation code into a procedure, which allocates the memory for the ItemOrder data structure and initializes its three fields to values passed in by parameter. We will do that, but first consider the first field, id. This field is the unique identifier for this order, and really should be generated automatically. We will do that by using a static field in the procedure initialized to one and incremented after every call. The code, including the procedure and the code to call it, is shown in Listing 2.7.
Listing 2.7: Testing Local Fields versus Local Static Fields in RPG Procedures
D* Item Order Data Structure D ItemOrder DS BASED(order@) D id 5P 0 D quantity 5P 0 D unitCost 9P 2 D* Item Order Data Structure Length D orderLen S 5U 0 INZ(%LEN(ItemOrder)) D* Item Order Data Structure Pointer D order@ S * INZ(*NULL) D* Individual Orders D order1@ S * INZ(*NULL) D order2@ S * INZ(*NULL) D*—————————————————-————————————————— D* Prototype for procedure: MakeOrder D*—————————————————-————————————————— D MakeOrder PR * D quantity_parm 5P 0 VALUE D unitCost_parm 9P 2 VALUE C EVAL order1@=MakeOrder(10:35.99) C id DSPLY C EVAL order2@=MakeOrder(5:26.15) C id DSPLY C EVAL *INLR = *ON P*——————————————————————————————————————————-———-— P* Procedure definition for MakeOrder P* Function..: Allocate and initialize a new order P* Parameter: quantity => number of items to order P* Parameter: unitCost => cost of each item P* Returns..: Pointer to ItemOrder datastruct memory P*——————————————————————————————————————————————— P MakeOrder B D MakeOrder PI * D quantity_parm 5P 0 VALUE D unitCost_parm 9P 2 VALUE D* local fields D nextId S 5P 0 INZ(1) STATIC D* local code C ALLOC orderLen order@ C EVAL id = nextId C EVAL nextId = nextId + 1 C EVAL quantity = quantity_parm C EVAL unitCost = unitCost_parm C RETURN order@ P MakeOrder E
Do you follow this? The procedure nicely encapsulates the effort of creating another order, given just two pieces of information: the quantity and the unit cost. The unique ID is simply derived by the procedure code itself, making this a very convenient procedure for you and your fellow programmers. The output of running this program is 1 and 2, indicating the nextId field was indeed incremented after each call. Notice, if the STATIC keyword were not specified, the field's value would be re-initialized to one on every call.
If you want to actually try this, assuming you put the source into file QRPGLESRC in library LEARNRPG, you would compile it with this:
CRTRPGMOD MODULE(LEARNRPG/ITEMORDER) SRCFILE(LEARNRPG/QRPGLESRC) CRTPGM PGM(LEARNRPG/ITEMORDER) MODULE(LEARNRPG/ITEMORDER)
In Java, you can also have static variables, by specifying the static modifier, like this:
static int counter = 10;
However, you cannot specify this modifier on local variables in a method, the way you can for local procedure fields in RPG. Rather, it can only be specified for variables defined at the class level. The semantics of it are a little bit different, too. Static variables in Java always have only one value, regardless of the number of instances (objects) declared with that class. In other words, all objects of the class share the same value for that variable. So, rather than being a local field modifier that means to retain values between calls, in Java, static is a class-level variable modifier that means to retain values between objects.
Is this similar to RPG's STATIC keyword? Actually, it is. Let's update the ItemOrder Java class, last shown in Listing 2.6, to automatically compute and assign a unique ID value for each order. This is done by changing the setOrderInfo method, which sets this information, to only take two parameters instead of three. The new version is shown in Listing 2.8.
Listing 2.8: Updated the Java ItemOrder Class Using the static Variable
public class ItemOrder { private static int nextId = 1; private int id; private int quantity; private double unitCost; public void setOrderInfo(int quantity, double unitCost) { this.id = nextId; nextId = nextId + 1; this.quantity = quantity; this.unitCost = unitCost; } public double getTotalCost() { double totalCost = quantity * unitCost; return totalCost; } }
Furthermore, static allows code in other classes to directly access the variable without requiring an object of this class. You can simply qualify the reference with the name of the class, like this:
int nextAvailableIdNumber = ItemOrder.nextId;
Of course, this won't compile because the variable is declared private, but otherwise it would.
Static variables are referred to as class variables rather than instance variables. The memory is allocated for them only once per class, not once per object or class instance. Indeed, Java allocates the memory for all static variables in all classes that are used at the time the application starts. Compare this to instance variables, where unique memory is allocated by you, per object, when you use the new operator.
With the latest version of the ItemOrder class in Listing 2.8, the PlaceOrders class must also be updated to only pass two parameters to the setOrderInfo method, instead of three. This is shown in Listing 2.9.
Listing 2.9: Updated the Java PlaceOrders Class Using the Updated ItemOrder Class
public class PlaceOrders { public void placeAnOrder() { ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder(); double order1TotalCost; double order2TotalCost; order1.setOrderInfo(10, 35.99); order1TotalCost = order1.getTotalCost(); order2.setOrderInfo(5, 26.15); order2TotalCost = order2.getTotalCost(); } }
We created the PlaceOrders class to show how to instantiate the original class, ItemOrder, so that we could show how to use that class. But then this PlaceOrders class itself will have to be instantiated by some class in order to run its placeAnOrder method. And then that class will have to be instantiated by another, and so on. If every line of code has to exist in a method, and every method has to exist in a class, and every class has to be instantiated to call any method in it, what instantiates the very first class?
The clue to answering this riddle is that not every method requires you to instantiate the class that contains it! The exception is methods defined with the static modifier. Static methods, like static variables, can be accessed by simply qualifying their names with the class name. No instantiation of the object is required. So, static methods are called class methods, for the same reason that static variables are called class variables. Such methods have some restrictions, though, related to the fact that they are called in the context of a class, not the context of an object. Specifically, they cannot access any instance variables because the memory for those instance variables might not exist, if no object has been instantiated.
Can either method in ItemOrder (Listing 2.8) be made static? No, because both of them use (read or write) one or more of the instance variables id, quantity, and unitCost. Can the placeAnOrder method in class PlaceOrders (Listing 2.9) be changed to a static method? Yes, it can! This method uses only local variables, not instance variables. Indeed, the class does not even have any instance variables. Listing 2.10 shows how to make the method placeAnOrder static.
Listing 2.10: Updating the Java PlaceOrders Class with the Static placeAnOrder Method
public class PlaceOrders { public static void placeAnOrder() { ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder(); double order1TotalCost; double order2TotalCost; order1.setOrderInfo(10, 35.99); order1TotalCost = order1.getTotalCost(); order2.setOrderInfo(5, 26.15); order2TotalCost = order2.getTotalCost(); } }
Just by adding the keyword static, code in other classes can call this method by simply using this:
PlaceOrders.placeAnOrder();
You do not declare an object reference variable and you do not instantiate an object via new. You just call the method, qualified by its class name. Thus, static methods (class methods) are just like procedures in RPG or functions in C. They are not "object" oriented; they are "procedural." If they need to act on variables, they simply take in what they need as parameters instead of relying on instance data (or use class variables). The fact that static methods are in a class at all is really just to satisfy Java's rule that all methods must exist in a class. However, we often group related static methods into one class. For example, Listing 2.11 shows a class with a couple overloaded versions of a max method that returns the maximum number of two given parameters.
Listing 2.11: Static Methods for Computing Maximums in Java
public class MaxMethods { public static int max(int parm1, int parm2) { if (parm1 > parm2) return parm1; else return parm2; } public static float max(float parm1, float parm2) { if (parm1 > parm2) return parm1; else return parm2; } }
You might imagine this class having methods for all Java's numeric data types (byte, short, int, long, float, and double). However, the particular methods already exist, as you will see in Chapter 3. Java supplies a class named Math that is full of many static methods for doing mathematical operations on all Java's data types, including these max methods.
Use but don't abuse static methods
Static methods are often called "helper methods" because they offer services that are easily accessible (no object instantiation required). If your method does not use any instance variables, you should make it static so that it is easier for callers to use. On the other hand, if you find yourself writing only static methods that do not use instance variables, you are probably still thinking too "procedurally." Instead, you need to think about how you can better utilize instance variables in your class to store state information.
For example, do not design a static method named getYearsOfService that takes an employee ID and returns that employee's years of service, and another named getSalary that takes an employee ID and returns that employee's salary. Rather, design a class named Employee that has instance variables for years of service and salary, which are set once by reading the database. Then supply non-static methods that return this information for this employee instance. Users of your class would simply instantiate one instance per employee and use the methods on each instance to do all the actions related to that employee, such as getYearsOfService, getSalary, and printPayCheck.
Use and abuse objects
The true power of classes, which you will only come to appreciate with time and use, is the ability to encapsulate in one place all the data (instance variables) and all the actions (methods) that relate to one topic. The fact that you can have many instances of the same class active in memory at once is a bonus that gives you more flexibility in designing applications. You don't have to write RPG-like applications that define one data structure and then iteratively populate it and process it, then repopulate and process again for the next record of data. Rather, you have the option of having many populated instances "active" at once.
Imagine, for example, the graphical equivalent of a subfile that is listing all of your customers. Each item in that list might actually be a populated Customer object, and each of the popup-menu items (what used to be options) for the selected item in the list simply invokes a method on the selected object to perform that action, such as displayDetails, editDetails, printMailingLabel, and so on. All the data needed by that action is right there, in the instance variables. It's nice clean code and design. If all your applications use this Customer class when they need to read or manipulate customer information, then you get re-use as well. If you decide to add a new field to the customer database, such as for email addresses, you only have to subsequently change that one Customer class definition, and all code in all applications will automatically be up-to-date. They will not even have to be re-compiled! Encapsulation—'tis a good thing. Think about what classes you might design in your next application. How about Employee, Manager, Company, Customer, Address, ItemOrder, Item, and Account?
Don't worry, it is much too early in your Java journey to fully understand and appreciate this conversation now. We just want you to start thinking of classes, and their objects, as powerful ways to group in one place all data and all methods that act on that data. Now, back to the basics!
The next several sections cover interacting with the console: calling programs, passing parameters from the command line, and writing to the console.
Now that you have been introduced to static methods, we can finally fill in the last piece of information you need to be able to "run" that PlaceOrders class you last saw in Listing 2.10. Recall that it was designed simply as a test case for the ItemOrder class last shown in Listing 2.9. You want to be able to run the class from a command line, like this:
>java PlaceOrders
However, if you try this now, you will get an error message:
Exception in thread "main" java.lang.NoSuchMethodError: main
When you try to run a Java class from the command line, the Java runtime (interpreter) will look for a specific method in that class. It will look for a method named main. Not just any method named main, but one that specifically looks like this:
public static void main(String args[]) { ... }
Java looks for exactly one such method with exactly this signature. Remember, a signature is the method name, and the number and type of parameters. In this case, you even need to specify the modifiers exactly as shown. The breakdown of the main signature is shown in Table 2.4; all parts are mandatory.
Part |
Description |
---|---|
public |
This method is publicly accessible. |
static |
This method does not require an instance of the class to be created first. |
void |
This method does not return any values. |
main |
This is the name of the method and is case-sensitive. |
String args[] |
This is an array of strings, one for each parameter specified on the command line. Any name will do, but args is the convention. |
Note that String is a Java-supplied class that is equivalent to RPG character fields. The square brackets, [], indicate this parameter is an array of String objects. The Java interpreter calls the main method automatically, and then the interpreter passes this parameter to you. Each element in the array represents a parameter typed in by the user on the command line.
You'll see more details about this shortly. In the meantime, since we don't care about command-line parameters yet, we now have enough information to make the PlaceOrders class "runnable." We simply have to code a main method as shown, and in it, call the static placeAnOrder method, as shown in Listing 2.12.
Listing 2.12: A Runnable Version of the Java PlaceOrders Class, with a main Method
public class PlaceOrders { public static void placeAnOrder() { ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder(); double order1TotalCost; double order2TotalCost; order1.setOrderInfo(10, 35.99); order1TotalCost = order1.getTotalCost(); order2.setOrderInfo(5, 26.15); order2TotalCost = order2.getTotalCost(); } public static void main(String args[]) { placeAnOrder(); } }
Alternatively, we could have just changed the placeAnOrder method to be the main method, but it amounts to the same thing. So, now you can run the class from the command line:
>java PlaceOrders
You don't get any output because the class doesn't write any output to the console. You will soon see how to do this, though. In the meantime, rest assured the code has executed and all is well in Java land!
In RPG, you specify the parameters to your main program object on the CALL command. The RPG entry module will define the parameters that it expects, either by using the traditional *ENTRY PLIST C-spec statement, or by using the new RPG IV way of using D-spec prototyping. For example, if you have a program in which the entry module accepts a six-character string, you could define the parameters traditionally as follows:
C *ENTRY PLIST C PARM KEYCHAR 6
You could also define the parameters with new ILE procedure prototyping syntax like this:
DMAIN PR EXTPGM('PRTCUST') D KEYCHAR 6A DMAIN PI D KEYCHAR 6A
As when prototyping procedures, the PR specification defines the prototype, while the PI specification defines the actuals. Why use this technique over our trusty *ENTRY technique for defining command-line parameters? The advantage is that you could place the PR specifications inside a /COPY member and use it when calling this program from another program, using the new CALLP op-code. This gives you the advantage of letting the compiler verify that you have gotten the parameters correct in the call.
Both options are equivalent. In RPG, you can define command-line parameters to be of any valid type, and each are explicitly defined in your *ENTRY or PR D-spec.
In Java applications, you pass parameters on the command line after the name of the Java class to invoke, with blank delimiters. Each word or token specified becomes one parameter. Each parameter is accessible to your Java main method as an entry in the args array of strings. Unlike RPG, every parameter is passed to Java as a string (a String object, actually), and you must explicitly convert it to the required data type yourself. (This will be covered in more detail later.) To pass a string with embedded blanks as a single parameter, you must enclose the string in quotes on the command line:
java Bob hi there "George and Phil"
This fills the String args[] array entry 0 (arrays are zero-based in Java) with hi, array entry 1 with there, and array entry 2 with George and Phil.
Very often, it is either necessary or worthwhile to write information out to the command line. It is a time-honored debugging trick, for example, to spit out "I am here" messages to track your flow of control in a program. (This is not so widespread anymore, thanks to the proliferation of source-level debuggers, but we all still do it occasionally.) Also, almost all language introduction books start with a simple little "hello world" program that writes this string out to the screen. The first page of the book is rarely the place to introduce display files or GUI constructs!
In RPG, a common way to write simple output is by using the DSPLY op-code. This has not changed for RPG IV, so we will not cover well-worn ground here. However, by using DSPLY, you could write the obligatory "hello world" RPG IV program, with the additional twist that it prints whatever is passed on the command line, as shown in Listing 2.13.
Listing 2.13: The RPG Main Entry Point Prototype
* Prototype of this program main entry DMAIN PR EXTPGM('HWORLD') D STRING 1000A OPTIONS(*VARSIZE) * Definition of this program main entry DMAIN PI D STRING 1000A OPTIONS(*VARSIZE) * Global variables DOutString S 52A * Main logic C EVAL OutString = 'Input: ' + C %SUBST(STRING:1:45) C OutString DSPLY * End of program C EVAL *INLR = *ON
The DSPLY op-code has the nasty restriction of only being able to display a maximum of 52 characters. Using the OPTIONS(*VARSIZE) keyword when declaring the character field parameter to the program lets it accept strings of any length. To build the output string OutString, the concatenation operator (+) is used to append the literal 'Input: ' to the first 45 characters of the input parameter. We get the first 45 characters by using the %SUBST (substring) built-in function. Finally, DSPLY prints the OutString field to the console.
Calling this program with CALL HWORLD 'hi there' results in the following:
Input: hi there
To write out the values of fields that are not character type fields, such as packed decimal, you first have to convert and store their values into a character field. This can be done easily in RPG IV by using the %CHAR built-in function, which takes any field as a parameter and returns a character field value that is immediately printable.
In Java, the equivalent to DSPLY is called "writing to standard output." It involves using a Java-supplied class named System, and a static object variable inside of that class named out, and finally a method of out named println, which is short for "print line." Here is an example:
System.out.println("Hello world");
By now, you are familiar with static variables, and you see here that object reference variables can also be static. In this case, out is a static object reference variable in the Java-supplied System class. You can tell it is static because it is qualified with a class name, not an object reference variable name. You can't know what the class type is, but you can deduce the System class must look something like this:
public class System { public static XXX out = new XXX(); }
where XXX is the unknown class type of the out object. You know println is a method because there are parentheses after it. It must be a method in whatever class out is a type of. (You can't tell from this line of code, but it doesn't really matter.) The println method prints a line of text to a special "file" called standard output. This is not a file on disk. Typically, the contents of the standard output file get displayed on the invoking command line (the console). To see this output in graphical applications or in applets where there is no command line, however, you have to be running inside a shell that captures and displays it. For example, the VisualAge for Java product has a console window that displays any information written to standard output with the println method. So do Web browsers.
To write a blank line, just call println with no parameters. A similar method named print writes the given text to standard output, but without advancing the carriage return, so subsequent text will be written to the same line.
You will quickly become very familiar with System.out.println as you begin to learn Java—and as you continue to read this book! Most simple, starter applications use it to display dynamic information. The method println takes any number of strings or variables, separated by plus signs (+), and displays them. This means that println automatically converts variable contents from their native type (such as int or float) to string format, which is very useful. For objects, println does this by trying to call a method in the object named toString. You must code this method for this to work with objects of your classes. Most Java-supplied classes come with this method.
A Java class that echoes the passed-in string is shown in Listing 2.14. Running this class results in this:
>java HelloWorld ""hello world!" Input: hello world!
Listing 2.14: A Java Version of the "Hello World" Program
public class HelloWorld { public static void main(String[] args) { System.out.println("Input: " + args[0]); } }
There are a few things to note about this example:
A more robust version of the class would check to make sure that there is at least one parameter, concatenate all of the parameters in a temporary string, and then print that string. The code to do this would exceed your current knowledge of Java, but if you are interested, you can see this version on the CD-ROM included with this book, in file HelloWorld2.java.
To put to work what you've just learned, let's display some stuff from the PlaceOrders class. We will first enhance the evolving ItemOrder class by adding a method named displayOrder, which will write to the console the information in this object.
This updated class, shown in Listing 2.15, writes to the console all the variables and also the total cost, which is retrieved by calling the getTotalCost method. Notice that System.out.println knows how to convert anything you give it to displayable format. Also notice the string literals are double-quoted, versus single-character literals, which are single-quoted.
Listing 2.15: The Updated ItemOrder Class with the displayOrder Method
public class ItemOrder { private static int nextId = 1; private int id; private int quantity; private double unitCost; public void setOrderInfo(int quantity, double unitCost) { this.id = nextId; nextId = nextId + 1; this.quantity = quantity; this.unitCost = unitCost; } public double getTotalCost() { double totalCost = quantity * unitCost; return totalCost; } public void displayOrder() { System.out.println(); System.out.println("Order Information"); System.out.println("————————-"); System.out.println(" id........: " + id); System.out.println(" quantity..: " + quantity); System.out.println(" unit cost.: " + unitCost); System.out.println(" total cost: " + getTotalCost()); } }
Next, you need to update the evolving PlaceOrders class to call this new method, in both ItemOrder objects, as shown in Listing 2.16. (This listing shows just the placeAnOrder method, as the main method is unchanged.)
Listing 2.16: The Updated PlaceAnOrder Method in the PlaceOrders Class, Now Calling the displayOrder Method
public static void placeAnOrder() { ItemOrder order1 = new ItemOrder(); ItemOrder order2 = new ItemOrder(); double order1TotalCost; double order2TotalCost; order1.setOrderInfo(10, 35.99); order1TotalCost = order1.getTotalCost(); order1.displayOrder(); order2.setOrderInfo(5, 26.15); order2TotalCost = order2.getTotalCost(); order2.displayOrder(); }
When you run the updated class, you get this:
>java PlaceOrders Order Information ———————— id........: 1 quantity..: 10 unit cost.: 35.99 total cost: 359.90000000000003 Order Information ———————— id........: 2 quantity..: 5 unit cost.: 26.15 total cost: 130.75
Do you follow it all? If so, you are on your way! If not, don't worry, you will within a few chapters. By the way, do you see that huge number for total cost? That's a problem with using float or double numbers in mathematical computations. Chapter 5 introduces a Java class, BigDecimal, which solves this problem.
Classes can have a special method called a constructor. A constructor is a method in the class, which the compiler and runtime recognize, that has the same name as the class. Unlike regular methods, constructors do not specify any return type on their definition line because they cannot return values. What does a constructor do? It is called by the Java runtime when an object is instantiated with the new operator, if it exists. This is your opportunity to initialize instance variables and do any setup that your methods assume.
Because you can initialize your instance variables when you declare them, with "= xxx;" syntax, and this initialization is also performed each time new is used, you might wonder what the value of a constructor is. There are at least three reasons why you might use a constructor:
You can also have more than one constructor. Because they must all have the same name as the class, the difference is in the number and type of parameters. A default constructor takes no parameters, but you can define other constructors with as many parameters as you need. Recall that Java allows you to overload methods—you can supply more than one method with the same name as long as the number or type of parameters are different. This applies nicely to constructors. You may have versions of your constructors that take differing parameters, giving users the freedom to use defaults or specify explicit initial values.
Imagine, for example, that you have defined a class named AS400, which connects to an AS/400 from a Java client. It might have a number of constructors: one that takes a user ID and password as parameters and assigns the parameters to instance variables used later in the connect method; one that takes a user ID and hard-codes the password; and the default constructor that defaults both the user ID and password. This gives users of this class flexibility in how they use it.
Users specify the parameters to a constructor as part of the new operator statement, like this:
AS400 myHost1 = new AS400(); AS400 myHost2 = new AS400("BOB"); AS400 myHost3 = new AS400("BOB", "ABABA");
One constructor can call other constructors, allowing you to abstract-out common code. You do this by calling the Java-supply method named this with the appropriate parameters, if applicable. For example, Listing 2.17 shows a common practice: one constructor takes parameters for all the instance variables you want to make user-initializable, and all other constructors simply call it and hardcode one or more of the parameters.
Listing 2.17: A Java Class with Three Constructors
class AS400 { private String userId; private String passWord; AS400() { this("QUSER", "QUSERPWD"); } AS400(String id) { this(id, "QUSERPWD"); } AS400(String id, String password) { userId = id; passWord = password; } }
The key here is that you do not explicitly code a call to a constructor. Rather, you simply code the new operator as usual, possibly passing in parameters, and the Java runtime implicitly calls the appropriate constructor for you. That is, the runtime looks for a constructor method in your class that matches the number and type of parameters specified with the new operator and calls it with the parameters specified on the new operator. A constructor is somewhat similar to the *INZSR subroutine in RPG, in that if you code it, the runtime will call it at initialization time.
Constructors are really just a short form equivalent to supplying a method named init, say, that you would instruct users to call in order to initialize your object's state or variables. Consider, for example, the setOrderInfo method in the ItemOrder class. It really would be better to turn this method into a constructor, so that users of this class do not have to create their ItemOrder object in one step, and populate the object in a separate step by subsequently calling setOrderInfo. Listing 2.18 shows the ItemOrder class after the method setOrderInfo has been turned into a constructor.
Listing 2.18: The Updated Java ItemOrder Class with a Constructor
public class ItemOrder { private static int nextId = 1; private int id; private int quantity; private double unitCost; public ItemOrder(int quantity, double unitCost) { this.id = nextId; nextId = nextId + 1; this.quantity = quantity; this.unitCost = unitCost; } public double getTotalCost() { double totalCost = quantity * unitCost; return totalCost; } public void displayOrder() { System.out.println(); System.out.println("Order Information"); System.out.println("————————-"); System.out.println(" id........: " + id); System.out.println(" quantity..: " + quantity); System.out.println(" unit cost.: " + unitCost); System.out.println(" total cost: " + getTotalCost()); } }
Of course, having changed ItemOrder, you now have to change the PlaceOrders class to reflect the change. Listing 2.19 shows the updated takeAnOrder method, where you pass the order information into the constructor via parameters on the new operator after you have turned the method setOrderInfo into a constructor.
Listing 2.19: The Updated Java PlaceOrders Class Calling the ItemOrder Constructor
public static void placeAnOrder() { ItemOrder order1 = new ItemOrder(10, 35.99); ItemOrder order2 = new ItemOrder(5, 26.15); double order1TotalCost; double order2TotalCost; //order1.setOrderInfo(10, 35.99); order1TotalCost = order1.getTotalCost(); order1.displayOrder(); //order2.setOrderInfo(5, 26.15); order2TotalCost = order2.getTotalCost(); order2.displayOrder(); }
The old calls to setOrderInfo are shown in Listing 2.19 as comments. The "//" characters in Java identify a comment: all code from there to the end of that line is considered a comment. Comments in this book appear in italics, similar to what any good editor will do.
Constructors, like methods, allow you to specify access modifiers. However, they have an additional responsibility in a constructor: they affect who is allowed to instantiate your class (that is, use new). If you specify public, this class can be instantiated by anyone. If you specify protected, this class can be instantiated only if it is extended by another class (discussed in Chapter 9). If no modifier is specified, the default is whatever is specified or defaulted for the class itself.
There is a subtlety to constructors: if you do not define any, Java will supply a default one with no parameters that does nothing. If you supply even one constructor, however, it will not do this. Therefore, the ItemOrder class now cannot be instantiated using the default constructor. Anyone who tries will get this error:
JAVACE No constructor matching ItemOrder() found in class ItemOrder.
If you want to allow a no-parameter new operation, you must explicitly supply a no-parameter constructor as well, even if it is empty. (By the way, a common short form you will see for "constructor" in the comments of code is "ctor.")
The following sections discuss the constructs and mechanisms for reusable code in RPG IV and in Java, and how to distribute that reusable code.
Recall that RPG IV applications can consist of an RPG program created by binding multiple module objects together, and that service programs are also created by binding multiple module objects together. This step is done by using CRTPGM or CRTSRVPGM, specifying the modules to bind on the MODULE parameter. Programs and other service programs that use a particular service program link to it during this bind step by specifying it on the BNDSRVPGM parameter. The service programs are not physically copied into the program object; they are simply linked to it.
Suppose an ORDER program object in library ORDERLIB is comprised of the following modules: ORDERDSP, ORDERDB, and ORDERRPT. Also suppose you use a service program TAXPROCS, comprised of the modules TAXTBLS and TAXRTNS. To build the application, after all the modules have been compiled with CRTRPGMOD, you do this:
CRTSRVPGM SRVPGM(ORDERLIB/TAXPROCS) MODULE(TAXTBLS TAXRTNS) EXPORT(*ALL) ACTGRP(*CALLER) CRTPGM PGM(ORDERLIB/ORDER) MODULE(ORDERDSP ORDERDB ORDERRPT) ENTMOD(*FIRST) ACTGRP(ORDERAG) BNDSRVPGM(ORDERLIB/TAXPROCS)
You create the service program first, so that you can subsequently link to it when creating the program. For the service program, you use EXPORT(*ALL) to allow all procedures in the modules in the service program to be callable from the linking programs. Well, not all procedures, only those that specify the EXPORT keyword on their beginning procedure specification. Finally, you use the *CALLER (the default, actually) for the activation group parameter, so that this service program always runs in the activation group of the program object that is using it, at runtime. Remember the same service program can be used by more than one program object, so with *CALLER, each active instance of the same service program gets its own memory for fields when multiple applications are running simultaneously.
After creating the service program, you create the program object, binding the three module objects into it. You specify *FIRST for the entry module (this is the default anyway) and give an explicit name of your choosing for the activation group. Finally, you link to the service program from the first step.
If you subsequently update any one of the modules, you can use UPDPGM and UPDSRVPGM to selectively replace it inside the *PGM or *SRVPGM objects.
Once you have an ILE RPG service program that contains a number of useful procedures (inside modules), the time will come when you want to sell it or at least package it up for reuse by others. This is easily done. For anyone who wants to use your service program in their own program, you would need to supply two things:
A user of your service program would then:
For your own maintenance purposes, you might want to investigate the use of the binder language. This is a source member language for naming all the exported procedures in your service program, and is an alternative to using EXPORT(*ALL) on CRTSRVPGM, if you do not want all procedures with the EXPORT keyword to be available outside of the service program. You can then specify this on CRTSRVPGM's SRCFILE and SRCMBR parameters. Here is the binding language source for exporting a procedure named RJUSTIFY:
STRPGMEXP SIGNATURE('RJUSTIFY') EXPORT SYMBOL('RJUSTIFY') ENDPGMEXP
Actually, making procedures available to users of the service program involves two steps, and both are required:
Another ILE construct worth investigating is the use of binding directories (*BNDDIR). When creating programs and service programs, you can either specify all of the modules and service programs explicitly with the MODULE and BNDSRVPGM parameters, or externally, by specifying a binding directory on the BNDDIR parameter.
Binding directories list modules and service programs. You create a binding directory with the CRTBNDDIR (Create Binding Directory) command, and populate it with module and service program names using the ADDBNDDIRE (Add Binding Directory Entry) command. This is a good way to ensure that your programs and service programs are always recreated properly, without having to rely on all programmers knowing exactly what module and service programs are needed when recompiling and recreating the objects.
Java, like all object-oriented languages, is big on code reuse. Java classes are all about code reuse. As we have discussed, Java classes are like individual RPG IV modules, but can be reused simply by instantiating them multiple times. Any class in any application can instantiate any other class, assuming of course the class is accessible according to the modifiers specified for it. There is no binding step in Java, and no other file system objects like RPG's *PGM and *SRVPGM. You just use whatever class you want where and when you want! If you update a class, all applications using it will pick up the latest version the next time they run. Blissfully simple!
However, the problem you will soon run into is managing the hundreds of classes you will end up with and dealing with name collision when two classes have the same name. To help with these problems, there is a higher-level grouping mechanism in Java. It is called a package, appropriately enough. You can place multiple classes into a single package.
Usually, you put classes related to each other or to a certain concept into their own package. For example, the package orders might contain the classes OrderGUI, OrderDatabase, and OrderReport. This is similar to programs and service programs in RPG IV, where you group related modules into a single entity. By identifying that a class is part of a package, you add a new qualification to the class name—that is, the class name can be qualified with the package name, meaning two classes can have the same name, as long as they are in different packages. A further advantage of packages is they allow you to give classes and methods inside the same package access to each other, while restricting access by code in other packages. Indeed, if you do not specify any modifiers on a class, instance variable, or method, they have package access. This means all code in this package, and only code in this package, can access this class, instance variable, or method.
In Java, packages are a defined part of the language, as opposed to a new file system object such as *SRVPGM objects. Indeed, they actually map to directories, as you will see soon. You'll also see soon how to define what package your class is part of, and how to use or access classes in other packages. When writing production Java code, all your classes will be inside packages.
Defining a Java package
In the first line of the .java source file, you can optionally define the package of which the class is part, using the package statement. If present, this must be the first non-blank line in the source file:
package orders; public class OrderGUI { ... }
This statement informs the javac compiler that this class is to be considered part of the orders package. You would add this package statement at the top of every class you want to be in this package.
Using Java packages
The opposite of defining a Java package is using it. If you have source code that needs to use classes defined in a package other than the package the class is part of, you have two choices:
orders.OrderGUI orderWindow = new orders.OrderGUI();
import orders.*; // import all classes in orders package
Remember, the "//" characters start a comment in Java. The import statement can also be used to import a single class from a package, like this:
import orders.OrderGUI; // just import OrderGUI class import orders.OrderDatabase; // just import OrderDatabase class import orders.OrderReport; // just import OrderReport class
There is no performance implication to importing all classes or importing one class, so in most cases, you just import them all. The only reason to import classes individually would be as a documentation aid to your fellow programmers, so that they can see what classes you are using from a package.
When you import a package, you have full, unqualified access to all its classes. This frees you from the tedium of prefixing the package name to all class names used in the package. You still can fully qualify each class name with its package name, if you wish. Why would you want to do this? If you are importing two packages, and want to use a class that has the same name in both, then you would have to qualify the references to the class name to resolve the ambiguity.
If you do not specify a package statement in your class source file, your class is considered to be part of the default package—that is, the "unnamed" package. All other packages are considered to be "named" packages. There is exactly one unnamed package, and all classes without package statements are placed there. This package is always imported implicitly for you by Java.
Here is a tip that will save you some grief: if your class is in a package (there is a package statement), you cannot access any classes in the unnamed package. We are not sure of the reason for the rule, but we are sure it will cause you some grief if you try to mix classes in the unnamed package with classes in the named package, in the same application. The reverse is allowed, though. That is, classes in the unnamed package can access classes in named packages.
Java package-naming conventions
Java package names are all lowercase. It is important to choose package names that are as unique as possible, to minimize the chance for collision with other package names. This becomes particularly important as Java packages become available on the market for global reuse and resale. To this end, the Java language allows package names to have multiple parts, separated by dots. This allows for some clever naming conventions for all your packages, for example:
accounts.receivable accounts.billable taxes.usa.california taxes.usa.florida
Java has a standard convention for naming packages that ensures universal uniqueness. The general rule is to start all your package names with the reverse of your company's internet domain name, minus the "www." part. For example, all packages supplied by IBM start with "com.ibm," and add on to that, so AS/400 related packages start with "com.ibm.as400," and VisualAge for Java supplied packages start with "com.ibm.ivj." Your package names should start with "com.yourcompany," as in:
com.yourcompany.accounts com.yourcompany.taxes com.yourcompany.hr
As you will discover throughout this book, Java supplies all of its classes in packages, and these all start with java, as in java.io, java.math, and java.util. Almost all Java classes you look at will import one or more of these JDK-supplied packages. An all-important package, containing critical core classes, is the java.lang package. Because of its importance, this package is always imported for you, just as the unnamed package is. All JDK-supplied classes are in named packages. Other than java.lang, you have to explicitly import the packages containing the classes you wish to use.
Packages and the file system
When you compile a class after adding the package statement at the top to identify the package it is in, there is nothing different about the resulting .class file. No new file system object is created for you. However, there is something you have to do!
As mentioned earlier, packages are not file system objects the way service programs are. However, Java does need a way of segregating packages on disk and a way of finding classes by package, on disk. To this end, there are rules for the location where classes inside a package must exist in the file system. Each dot-separated part of a multi-part package name must correspond to a subdirectory in the file system. This is the convention that the Java compiler and runtime will follow when looking for package-qualified classes. So, for example, if you have package com.acme.orders with three classes in it, your file directory structure must look like this:
com acme orders OrderGUI.class OrderDatabase.class OrderReport.class
All of the classes inside com.acme.orders must exist in the directory tree comacmeorders. (Notice that each dot-separated part of the package name becomes a separate subdirectory of the same name.) These names are very case-sensitive. The Java runtime will look for subdirectories with exactly the same case as the package name, just as it does when looking for a class.
Where is this tree "anchored?" In your current directory. When you compile from your current directory, the compiler looks for these subdirectories under it when it encounters an import com.acme.orders statement, or when it encounters a package com.acme.orders statement and you reference other classes in this package. For this reason, your current directory is always the parent of subdirectory com, and you always keep your .java files there. After compiling, copy the resulting .class file to the subdirectory appropriate for its package, so compiles of other classes can find it. Who creates these subdirectories? You do! Who copies the .class files there? You do! Of course, none of this is required when using only classes in the unnamed package, or when using tools like VisualAge for Java.
A classpath versus a library list
If you followed that last discussion, you see that to successfully compile any .java file that uses classes from any other packages, those classes must exist in appropriately named subdirectories under the current directory. This is fine for a while when writing and using your own stuff, but it becomes a pain quickly. For example, it would seem to imply that you need to copy all of Java's supplied classes to directories under your current directory. Also, what happens if you want to compile code in another directory? Do you have to copy all the subdirectory trees to it? Of course, the answer is no.
Java offers a way to identify a list of directories that will be searched when it is looking for classes, both at compile time and runtime. Indeed, this is just what the library list does when compiling and running RPG.
The equivalent to the AS/400 library list in Java is the CLASSPATH environment variable. This is where you list all the directories that the Java compiler and virtual machine will search when looking for a class, separated by semicolons on Windows or colons on UNIX, OS/400, and OS/390. On OS/400, this will be a list of Integrated File System (IFS) directories, and you can use the new ADDENVVAR command to set it or the CHGENVVAR command to change it. On Windows 95 and Windows 98, you set it in the autoexec.bat file, while on Windows NT and Windows 2000, you set it via the System icon in your Control Panel folder. So in Windows 98, you might have this:
SET CLASSPATH = .;c:myJava;c:myOtherJava
Java searches these directories only after searching the current directory, but we still prefer to put the current directory as the first entry to be sure. When looking for classes in the unnamed package, Java simply searches all the directories in the order they are specified, looking for the .class file.
When searching for classes in named packages, however, Java searches first for the subdirectories (e.g., comacmeorders) underneath each directory listed on the classpath, and only if it finds those will it search inside the final subdirectory (c:myJavacomacmeorders) for the .class file. It searches underneath each directory specified in the CLASSPATH environment variable until it finds the file in the appropriate subdirectory or reaches the end of the list of directories. Thus, you do not have to copy all package directory trees all over your system; you can put them in one spot and point to their root directory via CLASSPATH.
Distributing Java packages
When your packages are ready for distribution, you may distribute them in their directory structure. Users can then copy that structure intact (using xcopy –s, say) to any parent directory they wish. They then add that parent directory to their classpath and away they go, running your application or writing new code that uses the classes in your package.
The problem with this, though, is the inconvenience of shipping and installing many files. To make distribution easier, Java has always supported compressing many .class files into a single .zip file using ZIP compression utilities like PKWare's PKZIP or the commonly used WINZIP utility by Nico Mak Computing, Inc. (Check www.shareware.com for these.) The really cool thing about this is that your users do not have to unzip these files! The Java class loader can read classes directly out of them. We love this. Thus, you often find that companies ship their Java code in a file like classes.zip.
After copying a .zip file into the file system, users must set up their CLASSPATH environment variable to point directly to it. For example, on Windows 98, you might edit your autoexec.bat file to contain the following:
SET CLASSPATH = .;c:myJava;c:myJavaclasses.zip
It is not enough to just point to the directory containing the file, you must point directly to the .zip file itself, fully qualified by path name.
The only downside to .zip files is that they are really a Windows-only technology, and Java runs on every operating system. It also runs in Web browsers and PDAs and toys. To solve this, Sun invented .jar files. These use the same technology to do the compression, but use an all-Java tool that comes in the JDK to compress and decompress. Like .zip files, classes can be read directly out of .jar files. Indeed, Java can read any file directly out of a .jar file, such as any image and audio files your Java code might use. As with .zip files, you simply name the fully qualified .jar file on your classpath:
SET CLASSPATH = .;c:myJava;c:myJavaclasses.jar
The term "JAR" is short for "Java archive." While commonly used for all Java distribution requirements today, they were initially invented to speed up download time of applets by allowing all the classes and auxiliary files to be put into one compressed file and downloaded over the Internet to the Web browser. Prior to .jar files, all files came down separately, which was too slow. You do not use CLASSPATH for applets; rather, you use an APPLET tag inside your HTML Web page source, with a CODE parameter pointing to the applet class, a CODEPATH parameter identifying the host directory, and an ARCHIVE parameter naming the .jar files:
Here is how you would use the jar command to "jar up" all the classes in your current directory (unnamed package) and all the classes in the comacmeorders directory (com.acme.orders package):
java -cvf myClasses.jar *.class comacmeorders*.class
The -cvf options tell the command to create a new .jar file, with verbose output messages, and name the file myClasses.jar. After that, one or more file names are specified. These can be simple names or generic wildcard names. To "unjar," even though it is not necessary, you can do this:
java -xvf myClasses.jar
This extracts all the contents, preserving the relative subdirectory names, if present. To simply see what files are in the JAR file, use the -tvf (type, verbose, file) options. To see all the options, just type jar without parameters.
Because .jar and .zip files use the same technology, you can use tools like WINZIP to work with a .jar file, and the jar command to work with .zip files.
Now that you have seen all of the Java constructs (variables, methods, classes, and packages), we can formally summarize what the access modifiers are and what they do. You can divide the world up into the classes that make up your package, and other classes that use your package. By default, all classes, class variables, instance variables, and methods are accessible by all the code in this package—and only the code in this package. This is called "package" access control, and it's what you get if you don't specify any access modifiers. This is equivalent to specifying the EXPORT keyword on your procedures in RPG, but not specifying EXPORT(*ALL) on CRTSRVPGM or identifying the procedure in your binder language source. You can further qualify the level of accessibility by using access control modifiers:
Table 2.5 compares each of these access rights to RPG IV procedure access rights. Remember that in RPG, all procedures (and fields) that specify the EXPORT keyword are available to all other modules in the program or service program (package access), while only the subsets listed in the binding source are available for users of the service program (public access), unless EXPORT(*ALL) is used, in which case all procedures with the EXPORT keyword are available for users of the service program. If you do not even specify the EXPORT keyword, only code in this module can call the procedure.
Java |
RPG |
---|---|
package (default) |
EXPORT keyword, but not in binding source or no |
public |
EXPORT keyword, and in binding source or EXPORT(*ALL) |
protected |
not applicable |
private |
no EXPORT keyword |
Phew! You've covered a lot of ground in this chapter, both from an RPG IV point of view and from a Java point of view. For Java, you have learned that:
Foreword