Data Types And Variables
Overview
Most languages today offer a variety of standard data types, such as binary, integer, float, and character. As with RPG fields, in Java, the type of every variable used must be declared at compile time. A data type defines the set of values that an expression can produce, or that a variable can store or contain. (Again, the terms field and variable are interchangeable.) As you will see when arithmetic and string manipulations are discussed, the data type of a variable establishes the operations that may occur on the variable. For example, if you declare a field or a variable as a character, you cannot perform arithmetic operations on it. If you declare a field as numeric (no matter what type of numeric it is), you cannot do string manipulations on it, such as substringing, concatenating, or scanning. If you attempt to manipulate a data item in a way that is inconsistent with its type, the compiler will inform you of the problem by issuing an error message at compile time. Both languages have different forms of declarations, which are discussed shortly.
Both RPG and Java are strongly typed languages. This means every variable and every expression has a type that must be known at compile time. This allows the compiler to determine what operations can be used with certain variables, and what values can be stored in them.
A Review of RPG Types
RPG has a number of data-type categories. We use the term category to refer to one or more data types that support the same operations and initialization characteristics. Often, there is only a single data type within a category, but for some, there are multiple data types, as shown in Table 5.1.
Category |
Data Types |
Description |
---|---|---|
Numeric |
Binary, Zoned-Decimal, Packed-Decimal, Integer, Unsigned, Float |
Numeric data with or without decimals |
Character |
Character, Character Varying, Indicator |
One or more characters or indicators that hold only zero or one |
Graphic |
Graphic, Graphic Varying |
One or more double byte (DBCS) characters |
UCS-2 |
Unicode, Unicode Varying |
Character data that holds two-byte, Universal Character Set (Unicode), standard characters |
Date |
Date |
Date data type |
Time |
Time |
Time data type |
Timestamp |
Timestamp |
Date and Time combined |
Basing Pointer |
Pointer |
A memory address |
Procedure Pointer |
Procedure Pointer |
An address to a procedure |
Within any one category, you can move data between fields of the data types and perform the same operations on fields of the data types. There is also a higher-level view called a classification, or class, which is a grouping of categories with similar characteristics and support. The classes are numeric, character, date/time, and pointer. This idea of sorting data types by operations into classifications is the heart of the term "class" in Java and other OO languages. When you define a class, you define a new classification for data. Data can be declared to be of that new class type, and any data of that type supports all the operations (methods) defined for that class type.
Table 5.2 goes into more detail on all the RPG IV data types. The Char column is the character you place in column 40 (Internal Data Type) of the RPG IV definition specification to identify the field as this type. (The D-spec is described later in this chapter.) The new data types in RPG IV are integer, unsigned, float, graphic, UCS-2, date, time, timestamp, basing pointer, and procedure pointer. Also new is the ability to specify the varying keyword on the D-spec for character, graphic, and UCS-2 data types, indicating the length given is only a maximum, and the actual length is whatever the size of the current string is in the field.
Data Type |
Char |
Since |
Description |
---|---|---|---|
Binary |
B |
V3R1 |
Signed, two's complement data, with or without decimals |
Zoned-Decimal |
S |
V3R1 |
Signed, one byte per digit, with or without decimals |
Packed-Decimal |
P |
V3R1 |
Signed, one byte per two digits, with or without decimals |
Integer 5 and 10 |
I |
V3R2/6 |
Signed, integer-only data, two or four bytes, no decimals |
Integer 3 and 20 |
I |
V4R4 |
Signed, integer-only data, one or eight bytes, no decimals |
Unsigned 5 / 10 |
U |
V3R2/6 |
Unsigned, integer-only data, two or four bytes, no decimals |
Unsigned 3 / 20 |
U |
V4R4 |
Unsigned, integer-only data, one or eight bytes, no decimals |
Float |
F |
V3R7 |
IEEE standard, floating-point data, four or eight bytes |
Character |
A |
V3R1 |
Fixed-length, single-byte character data |
Data Type |
Char |
Since |
Description |
Character Varying |
A |
V4R2 |
Variable-length, single-byte character data, uses varying keyword to identify variable length |
Indicator |
N |
V4R2 |
One-byte data, eight *on (one) or *off (zero) |
Graphic |
G |
V3R1 |
Fixed-length, double-byte character set (DBCS) data |
Graphic Varying |
G |
V4R2 |
Variable-length DBCS data, uses varying keyword to identify variable length |
UCS-2 |
C |
V4R4 |
Fixed-length, two-byte Unicode data |
UCS-2 Varying |
C |
V4R4 |
Variable-length, Unicode data, use varying keyword |
Date |
D |
V3R1 |
Date data |
Time |
T |
V3R1 |
Time data |
Timestamp |
Z |
V3R1 |
Date and time combined data |
Basing Pointer |
* |
V3R1 |
Memory address from %addr or alloc operations |
Procedure Pointer |
* |
V3R1 |
Memory address of a procedure, procptr keyword identifies, %paddr sets |
Object |
O |
V5R1 |
Reference to a Java object for RPG-to-Java support |
Integer and unsigned data types are designed to replace the binary data type when used with zero decimal places. These types allow you to hold larger integral numbers with the same memory because there is no need to hold decimal information. Note you must still hard-code a zero in the decimal position of the D-spec. The memory requirements of these are one, two, four, and eight bytes depending on whether you specify a digit length of three, five, 10, or 20, respectively.
Floating-point fields hold single-precision (four-byte) or double-precision (eight-byte) values that have variable decimal positions. Float data is made up of a mantissa and an exponent, and can hold very large positive or negative numbers. Float data is most often used in scientific applications involving real-world measurements and number crunching.
Graphic data contains pure DBCS characters. Every two bytes define a single DBCS character according to the DBCS collating sequence for a particular codepage or CCSID. DBCS graphic data is used for Japanese, Korean, Chinese, and Simplified Chinese strings, and does not require shift-out/shift-in delimiters.
UCS-2 data also contains two-byte data, but it is an industry standard that is defined to hold every character from all codepages, including DBCS and non-DBCS data. With this all-encompassing character set, commonly referred to as Unicode, all complexities involved in supporting multiple language or country character sets are eliminated. Codepages and CCSIDs disappear. As you will see, all Java characters and strings are stored internally in Unicode format.
Date, time, and timestamp fields were added to the language in the first release of RPG IV. By explicitly telling the compiler that a field will hold a date, time, or timestamp, the compiler is able to support a multitude of special-purpose operations designed specifically for dates and times. This includes intelligent comparisons, formatting, extractions, and duration calculation, as covered in Chapter 8.
Basing pointers are fields that will point to a memory address. You assign this address by using the ALLOC op-code to allocate a block of memory, or by using the %ADDR built-in function to return the address of another RPG IV field. You can also define a non-pointer field with the BASED(basing-pointer-field) keyword. With this, the compiler does not allocate memory for this field, but rather sets the memory location to be that of the basing-pointer value. You can also do math on a basing-pointer field to move its location forward or backward in memory.
Basing pointers can be used to call system APIs that require pointers to data structures as input or output, or can be used to write complex applications that manage their own memory. For example, some programmers have coded their own multi-dimensional arrays by allocating the memory themselves and managing the contents using basing pointers. Until assigned, a basing pointer points to *NULL, a special value in RPG.
Procedure pointers are fields that will hold a pointer to a procedure, which, as you recall, are new in RPG IV and what we call "grown-up subroutines." Procedures use the new P-spec, and allow parameter passing, return values, recursive calls, and local fields. You assign a pointer to a procedure by using the %PADDR(procedure-name) built-in function. Once you have assigned it, you can call the procedure pointed to by the field by simply naming the field on the CALLP operation. Procedure pointers can be used to write reusable code, where the procedure to be called is determined at runtime instead of statically at compile time. Programmers have used these to write generic service programs that can be reused in many different situations, and leave it up to the caller to pass in procedure pointers that the generic code will subsequently "call back" to do situation-specific tasks. No math is allowed on procedure pointers. Until assigned, a procedure pointer points to *NULL.
Something new and exciting in V5R1 of RPG is the ability to instantiate Java objects from RPG code, and subsequently call the methods in that Java object from RPG. We don't go into that support here, but as part of the RPG language enhancements to support it, there is a new O data type that is used to hold the Java object reference after an object is instantiated.
An Introduction to Java Types
Variables in Java, like fields in RPG, must be defined with a type. Java has two distinct flavors of types:
- Primitive types are similar to RPG's data types, in that they are predefined in the language and have built-in support in the form of operators and conversion rules.
- Reference types are variables whose type is defined to be that of a class in Java, either Java-supplied or user-defined. Reference variables, as discussed in Chapter 2, are equated to an instance of the class type they are defined as, using the new operator. Because they contain the memory address of a class instance or object, they are said to refer to an object; hence, the term "reference variable." These reference variables distinguish Java as an object-oriented language, compared to the primitive-only data types in RPG. You can think of classes in Java as a mechanism for defining your own data types, with the methods in the class being the operators supported by all data defined with that type (class). Thus, the term "class" is really a short form for "classification."
We limit our discussion in this chapter to primitive types in Java, which compare to RPG's data types. Reference types were introduced in Chapter 2, and are discussed in more detail in Chapter 9 (and indeed, all the subsequent chapters). It is interesting to note, however, that all of Java's primitive types are also available as classes in the java.lang package. These classes are wrappers of the primitive types, with methods for converting to and from them. They are of value when the code expects only objects. (These are discussed later.) Also of interest is that arrays and strings are both implemented as objects in Java, so variables of these are, in fact, reference variables. (These are discussed in Chapters 6 and 7.)
Primitive data types in Java fall into the following categories:
- Numeric types are divided into those that do not have decimals and those that do. The former are in the integer subcategory, and include one-byte (byte), two-byte (short), four-byte (int), and eight-byte (long) types. The latter are in the float subcategory, and include four-byte (float) and eight-byte (double) types. Note that in both RPG and Java, numeric data types are signed, which allow both positive and negative numbers. However, there is an exception to this in RPG: the unsigned data type that allows only positive numbers. Thus, although otherwise identical to RPG and Java's integer data types, it does allow larger positive numbers.
- The character data type, unlike RPG, is always a single character long, unless you define an array of characters. Also unlike RPG, a character in Java is actually two bytes long, as it is based on the two-byte Unicode standard that encompasses all international characters. In contrast, a character in RPG is one byte long and is based on the EBCDIC encoding standard. In Java, then, the complex use of codepages is not required to support international characters properly.
- The boolean data type supports two values: true and false. These are reserved words in Java and are only assignable to boolean variables. Boolean is analogous to RPG indicators, where one is true and zero is false.
Table 5.3 lists the eight primitive data types in Java. As you can see, we are missing some critical types compared to RPG. Namely, there are no types for numeric data with fixed decimals, no types for character fields with a length greater than one, and no types for pointers. Not to worry, though, these apparent holes in the language are actually filled by Java-supplied classes, versus primitive types. The exception is pointers, which were explicitly left out of the language for security and ease-of-use reasons. However, you could think of object reference variables as pointers, since both contain memory addresses. The difference is that Java does not allow math on those addresses. Also, Java does not have procedure pointers, but you will discover in Chapter 9 that you can achieve the "call back" capability of procedure pointers using something called interfaces.
Java Type |
Declaration |
---|---|
byte |
Signed integers, one byte of storage, -128 to +128 |
short |
Signed integers, two bytes, -32768 to +32767 |
int |
Signed integers, four bytes, -2,147,483,648 to +2,147,483,647 (about two billion) |
long |
Signed integers, eight bytes, -2**63 to +(2**63)-1 |
float |
Single-precision, floating-point data, four bytes |
double |
Double-precision, floating-point, eight bytes |
char |
Single alphanumeric character, Unicode based, two bytes |
boolean |
Either true or false, one byte. |
Table 5.4 shows a brief comparison of the data types in the two languages. To make accurate comparisons, you have to consider the declared length (number of digits) of the RPG types.
RPG IV Type |
RPG Len |
Java |
Comments |
---|---|---|---|
Binary (no decs) |
1 to 4 |
short |
Two bytes, signed numeric integers |
Binary (no decs) |
5 to 9 |
int |
Four bytes, signed numeric integers |
Binary (decimals) |
1 to 9 |
BigDecimal |
BigDecimal is a class for emulating fixed-decimal numeric data. |
Zoned-Decimal |
1 to 30 |
BigDecimal |
BigDecimal is the only option for fixed-decimal numeric data. |
Packed_Decimal |
1 to 30 |
BigDecimal |
BigDecimal is the only option for fixed-decimal numeric data |
Integer, Unsigned |
3 |
byte |
One byte, signed/unsigned integers |
Integer, Unsigned |
5 |
short |
Two bytes, signed/unsigned integers |
Integer, Unsigned |
10 |
int |
Four bytes, signed/unsigned integers |
Integer, Unsigned |
20 |
long |
Eight bytes, signed/unsigned integers |
Float |
4 |
float |
Single-precision floating point |
Float |
8 |
double |
Double-precision floating point |
Character or Graphic or UCS-2 |
1 |
char |
Single character only |
Character or Graphic or UCS-2 |
2 to 32767 |
String |
The string class is used for multiple character strings (discussed in Chapter 7). |
Indicator |
1 |
boolean |
*on=true, *off=false |
Date |
Date |
Discussed in Chapter 8 |
|
Time |
Date |
Discussed in Chapter 8 |
|
Timestamp |
Date |
Discussed in Chapter 8 |
|
Basing pointer |
object reference |
Memory addresses, but Java does not permit address math |
|
Procedure pointer |
interfaces |
Discussed in Chapter 9 |
As you can see, there are some RPG types that the Java designers chose to implement as classes instead of primitive data types. These will be discussed in subsequent chapters. We will go into more detail on each of the primitive data types after covering some syntactical details.
Data Declaration
Data types are put to use in two places in a language: in explicit fields or variables that you define, and in implicit fields or variables the compiler generates to process expressions. Now that you have had a glimpse of what the types are in Java, it is important to understand how to define variables of those types. The syntax is completely different from RPG's, but arguably easier. Let's review both.
Defining fields in RPG
You declare a field in RPG IV in one of three ways:
- On the C- or I-spec, by supplying length information
- Using the DEFINE op-code on a C-spec
- On the D (Definition) specification, which is new in RPG IV.
Why so many? The first two are inherited from RPG III, and the third is new for RPG IV. Before RPG IV, you had no choice other than to define fields on the I-spec, E-spec, and the C-spec. The new D-spec adds structure to your RPG code. It allows you to define all your fields, data structures, arrays, and tables in one area in your source code. So why did RPG IV not drop the other two methods? The simple answer is compatibility. Existing applications written in RPG III must run "as-is" after they are converted to RPG IV. An example is shown in Listing 5.1.
Listing 5.1: Defining Fields in RPG IV
*89012345678901234567890123456789012345678901234567890 FQSYSPRT O F 80 PRINTER OFLIND(*INOV) D FIRST S 5A INZ('PHIL ') D AGE S 2B 0 INZ(25) D*—————————————————————————————— C *LIKE DEFINE FIRST LAST +5 C EVAL LAST = 'COULTHARD ' C MOVE 'ONCE WAS ' AGETEXT 9 C EXCEPT RESULT C EVAL *INLR = *ON C*—————————————————————————————— OQSYSPRT E RESULT O FIRST 5 O LAST 15 O AGETEXT 24 O AGE 26
The example in Listing 5.1 defines fields that use the three different methods:
- The fields FIRST and AGE are defined using the D-spec. The name goes in columns 7 to 21. Material is more readable because it is not necessary to left-justify it. Names in RPG IV can be uppercase or lowercase, although the compiler converts them to uppercase. The S in column 24 indicates this is a standalone field (compared to, for example, a data structure or constant). The length is defined in columns 33 to 39, the data type in column 40, and the decimals in columns 41 to 42. The keyword INZ is used to initialize the values of the variables.
- The field LAST is defined on a C-spec using the DEFINE op-code to pick up the attributes of the previously defined FIRST field. The length is incremented by five over FIRSTs.
- The field AGETEXT is defined on a C-spec at the time of first use by specifying a length.
The resulting output of this example is the following:
PHIL COULTHARD ONCE WAS 25
(Yes, and the AS/400 was once a new computer!) Newcomers to RPG IV are encouraged to do field definitions on the D-spec because the code is easier to read and maintain. (It is no longer necessary to hunt for field definitions because now they are all at the top of the program.) The RPG IV equivalent to the DEFINE op-code is the LIKE keyword on the D-spec; the field LAST in the example could have been declared like this:
D LAST S +5 LIKE(FIRST) INZ(*ALL' ')
This example also shows how to use the INZ keyword to initialize all positions of a variable to a specified value by using *ALL followed immediately by the value.
The data type column on the D-spec is in column 40, and must be a single character entry that represents one of the RPG built-in data types, as shown in the Char column in Table 5.2. If it is blank, and there is no LIKE keyword, the type defaults to character if the decimals column is blank as well, or packed otherwise (unless it is a subfield, in which case it defaults to zoned).
The D-spec is a tremendous addition to RPG IV; you will find it makes writing and maintaining code much easier. You'll soon come to frown on implicitly declared fields on C-specs! Table 5.5 describes each of the columns of the D-spec for simple program- described fields that are standalone instead of data structures.
Column Range |
Declaration |
---|---|
6 |
The spec type, D |
7 to 21 |
The name of the field being declared. The name can float within the column range. Names greater than 15 characters can use the whole spec line, but have to end in an ellipsis (..., and then the length, type, and decimal details go on the next line. |
24 |
An S, indicating this is a standalone field |
33 to 39 |
The length of the field (number of digits) |
40 |
The data type of the field, as described in Table 5.2 |
41 to 42 |
The decimal positions in the field |
44 to 80 |
One or more keywords that further define and describe the field |
The keywords allowed for standalone fields are STATIC, INZ, EXPORT, IMPORT, ALTSEQ(*NONE), NOOPT, and BASED. If you use CODE/400 for your RPG editing, you will find a SmartGuide ("wizard") that prompts you for the information and generates the D-spec for you. Here are some examples of field declarations in RPG:
1 2 3 4 5 6 123456*8901234567890123456789012345678901234567890123456789012 .....DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++ D BinaryField S 4B 0 D ZonedField S 9S 2 D PackedField S 7P 2 D IntegerOneByte S 3I 0 D IntegerTwoByte S 5I 0 D IntegerFourByte... D S 10I 0 D IntegerEightByte... D S 20I 0 D aField S 30A D namePtr S * INZ(%ADDR(aField)) D nameField S 30A BASED(namePtr)
Notice how the names start in column 8 instead of 7 to aid readability, and how the names are coded longer than 15 characters (14, if you start in column 8).
One recent enhancement (since V4R4) that you will love is the INZ(*USER) keyword to initialize a 10-character field to the current user profile name.
Defining constant fields in RPG
Good programming style dictates that you avoid hard-coding literal values as much as possible, and instead use named constants to hold these values. That way, you can subsequently change that value once by simply changing the value of the constant. RPG IV has explicit syntax for defining constants, which also uses the D-spec. The syntax is very simple: give it a name as usual (all uppercase is a good standard for constants), declare a C in column 24 (instead of an S), and define the value as the parameter to the CONST keyword. No length, type, or decimals are specified. Here are some examples:
D* Constants D TYPE1 C CONST(1) D MAXTYPES C CONST(10) D TYPENAME C CONST('DOORKNOB') D DFT_DISCOUNT C CONST(0.05)
Note that the CONST keyword is optional—you can just code the value in the keyword area.
Defining data structures in RPG
You don't always declare standalone fields in RPG. Often, you define data structures made up of one or more subfields. In RPG IV, you can also use the D-spec to define data structures and subfields. Table 5.6 describes each of the columns of the D-spec for data structures.
Column Range |
Declaration |
---|---|
6 |
The spec type, D |
7 to 21 |
The name of the data structure or subfield being declared. The name can float within the column range. The name is optional for both the structure and each subfield. |
22 |
An E if this is an externally described data structure. Use extname to define the name of the file and, optionally, the format name. |
23 |
An S if this is a program status data structure, a U if this is a data-area data structure, and blank otherwise |
24 to 25 |
DS for the structure, blank for the subfields |
33 to 39 |
The total length of the data structure (optional), or for subfields, the length of the subfield |
40 |
For subfields only, the data type of the field, as described in Table 5.2 |
41 to 42 |
For subfields only, the decimal positions in the field |
44 to 80 |
One or more keywords that further describe the data structure or subfield |
The keywords allowed for data structures are STATIC, INZ, EXPORT, IMPORT, ALTSEQ(*NONE), ALIGN, BASED, DTAARA, and OCCURS. The keywords allowed for data structure subfields are ALTSEQ(*NONE), INZ, PACKEVEN, DTAARA, and OVERLAY. Here is an example of a data structure field declaration in RPG:
1 2 3 4 5 6 123456*8901234567890123456789012345678901234567890123456789012 .....DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++ D* Widget Data Structure: Holds information about Widgets D Widget DS INZ D id 5U 0 D type 1A D unitPrice 9P 2 D name 30A
Notice how we specified only the length of the subfields, not the starting and ending positions. The latter is allowed in RPG IV, but is rarely used. We also indented the subfield names more than the data structure name to aid in readability.
Two recent enhancements (since V4R4) that you will enjoy are the INZ(*EXTDFT) keyword to initialize an externally described structure to the values specified in the DFT DDS keyword, and the OVERLAY(name:*NEXT) keyword to define overlaid subfields in successive positions.
Obviously, there is much more to say about all the details of defining fields in RPG, but we leave that topic to the RPG IV manuals and the many RPG IV books.
Defining qualified data structures in RPG V5R1
Something new and exciting in RPG IV V5R1 is the QUALIFIED keyword for data structures. If you specify this keyword, you can access the subfields in the data structure by using Java-like dot notation to qualify the name of the subfield with the data structure's name. This allows you to reuse subfield names in multiple data structures, because there is no longer any ambiguity about which data structure subfield you are referring to! Further, you can define other data structures to be exactly like a previously defined qualified one, and inherit all of its subfield definitions. This is done with the LIKEDS keyword. You can also use a new *LIKEDS special value for the INZ keyword to indicate you want to use the same initial values as the inherited subfields. Here is an example from the RPG IV Reference manual:
D sysName DS qualified D lib 10A inz('*LIBL') D obj 10A D userSpace DS LIKEDS(sysName) INZ(*LIKEDS) // The variable "userSpace" was initialized with *LIKEDS, so the // first 'lib' subfield was initialized to '*LIBL'. The second // 'obj' subfield must be set using a calculation. C eval userSpace.obj = 'TEMPSPACE'
This is very interesting and useful, and very much like objects in Java, only without method support.
Defining variables in Java
In Java, variables can be defined in two places: at the class level, such that they are available to all methods (similar to RPG fields declared at the top of the file), or inside a method, such that they are available only to the code inside that method (similar to RPG fields declared locally inside procedures). Unlike RPG, Java has only a single syntax for defining variables. There are, however, a number of optional parts. In its simplest form, a variable declaration looks like this:
type name;
The data type comes first and is followed by a name of your choosing. The statement ends with a semicolon, as do all Java statements. Here is an example:
int myVariable;
This defines a variable named myVariable, of type int (integer). It is really that simple. The valid types are the eight Java reserved words for built-in primitive data types, listed in Table 5.3. Note that for object reference variables, the type will be the name of a Java class or an interface (described in Chapter 9).
To initialize a variable when you declare it, just equate it to a value, as in:
int myVariable = 10;
The value to which it is initialized can be a literal or variable that is already defined, or it can be an expression (for example, myVar + 2). In either case, the resulting type must be the same or compatible with the defined type of the variable.
Compared with defining fields in RPG, in Java, you don't deal with any of the following:
- Columns. Java is free-form.
- Name length. There is no limit to the name length in Java.
- Digit length. You do not specify a length for your variables in Java because the length is predefined by the type.
- Location. With RPG IV, your D-spec variables must be declared either at the top of the program or at the top of a procedure. In either case, they must precede the C-specs. In Java, class-level variables can be declared before or after the methods (we prefer before, but others prefer after), while method-level variables can be declared anywhere in the code as long as they are declared before use.
On the other hand, you do have to worry about case. In Java, abc and ABC are different, while in RPG IV, they are the same.
Java variable names are not shortened by convention, but are as long as necessary to be descriptive. Lowercase is used except for the first letter of words other than the first word, for example, interestRate or variablePayRate. Java variables specified at the class level, versus inside a method, can also be specified with optional modifier keywords before the data type. Here is an example:
private int myVariable = 10;
This example specifies the modifier private, indicating this variable is not accessible (either to read or to write) by code outside of this class. The modifiers are shown in Table 5.7.
Modifiers |
Description |
---|---|
public, private, or protected |
Accessibility of the variable, where public means all can access, private means only this class, and protected means this class or those that extend it (as discussed in Chapter 9). The default is that this class or others in this package can access it. This default is referred to as package accessibility, but there is no language modifier keyword for it. |
static |
This variable is initialized only once and has only one value, regardless of the number of instances of this class. Can be accessed via classname.variable, versus object.variable. Static variables are known as class variables, since there is one value per class, not one per object. |
static final |
This variable is a constant and cannot be changed. |
Static and static final variables can also be defined as public, private, or protected. For classes that are not extendible, you should always define your non-static and non-constant variables as private, which will force users of your class to go through your methods. In Chapter 9, you will see that for extendible classes, you will often use protected versus private. Local variables inside a method are always private to that method and no modifiers are needed or allowed.
There are actually two other modifiers: transient and volatile. However, you will rarely use these, and you need not worry about them. Briefly, though, the former deals with the persistence of a variable when you serialize it to disk, while the latter deals with the behavior of a variable in a multithreaded application.
Defining constant variables in Java
In Java, as with RPG IV, there is explicit syntax for defining constants. As mentioned earlier, constants are identified by the modifiers final and static, and have to be initialized to a value at declaration time. The modifier final means this variable's value cannot be changed, while static means you need not waste memory by reserving memory in every object. Since it never changes value, you need only reserve space once at the class level. Often, constants are intended to be used by users of your class, so are made public. Java's convention is that constants are named all uppercase to make it easier to distinguish them. Unlike RPG, you must declare the types of your constants in Java. Here are some examples:
public static final short TYPE1 = 1; public static final int MAXTYPES = 10; public static final double DFT_DISCOUNT = 0.05; public static final String TYPE_NAME = "DOORKNOB";
Defining data structures in Java
What about data structures in Java? Sorry, but there is no syntax for defining these. Or is there? A data structure is a way to group multiple related fields into a single entity, while still allowing individual access to the subfields. This is just what a class is in Java! You group variables in Java by putting them into classes. Indeed, any one of those fields inside a class can itself be an instance of another class, so data-structure nesting is also possible. The differences between data structures in RPG and classes in Java are:
- Non-static subfields (instance variables) require you to instantiate the class before using it
- You can have multiple instances of a class active simultaneously, each with their own data for each of the subfields.
- You can include procedures (methods, in Java) directly inside classes.
- To access any subfield from another class, you must qualify its name with an object reference variable name for instance variables, or the class name for static variables. The benefit of this extra work is you can use the same subfield name in multiple classes.
Variable Scope, Lifetime, And External Access
The discussion of data types leads to the discussion of variables, which in turn leads to the discussion of variable scope (that is, which code inside the RPG compile unit or Java class has access to the variable), variable lifetime (when is the variable allocated memory and initialized, and when is that memory freed), and external accessibility (the question of whether code outside this RPG or Java compile unit can access the variable). The following sections discuss these three aspects of Java variables.
Variable scope
In RPG III, the question of field scope is an easy one. All fields are global to the program in which they are declared, and one source member equals one program after compiling. The only rule is that a field cannot be accessed prior to being defined, but after being defined, all code, whether global or in a subroutine, can access that field.
RPG IV now has procedures that allow local fields to be defined in the procedure itself (as discussed in Chapter 2). These fields are not accessible by any code outside of the procedure. However, global fields declared at the top of the module are accessible by all the procedures in that module, as they were for subroutines.
Compile units have also changed in RPG IV. Rather than being source members compiling into a program object directly, they are compiled into intermediate module objects, which are then bound into a program object or service program object. Code in one module cannot, by default, access global fields in another module—even if they end up in the same program object. However, you can make fields accessible outside of your module by defining them with the keyword EXPORT, and then redefining them with the keyword IMPORT in the other modules.
Remember that in Java, variables can be declared at the class level or at the method level. Basically, method-level or local variables are only accessible by the code contained in that method, while class-level variables are accessible by code in any of the methods in that class. In fact, within a method, you can also have parameter variables. These resemble locally declared variables, except that they are passed in by the caller rather than being defined within the code or body of the method. Otherwise, they are just like local variables. You can roughly equate global fields in RPG IV modules to class-level variables in Java, and local fields in RPG IV procedures to local variables in Java methods. Furthermore, since RPG IV procedures also support the passing in of parameters, you can equate RPG IV procedure parameter fields to Java method variables. (By equate, in this context, we are talking about the scope or direct accessibility of the variables.)
However, moving further beyond parameters, Java has blocks, which are one or more statements contained inside curly braces. You have seen these already in relation to class definitions and method definitions, which are special cases of blocks. However, you can also have blocks defined directly inside methods, and these blocks can have their own variables that are local to that block, for example:
int myVariable; myVariable = 10; { int mySecondVariable; mySecondVariable = 20; } System.out.println("mySecondVariable = " + mySecondVariable);
In this example, the variable mySecondVariable inside the braces (or block) has its own scope, so it is not accessible to the code outside of that block. Therefore, you will get an error, since the println method call is trying to access an inaccessible variable. Notice that Java does not allow variable "hiding" in blocks—that is, you cannot redefine myVariable from the outer block, as this could lead to subtle programming errors. Blocks like this are simply to aid program readability. There are other examples of blocks, such as the for statement, and you can declare variables in them that are local to that statement block, as in:
for (int index = 0; index < 10; index++) { System.out.println("index = " + index); }
Any attempt to reference the index variable after the last curly brace of the for statement will result in an error. A variable declared inside a block is only accessible inside the block in which it is defined. RPG has no equivalent to Java's local blocks. The general scoping rule in Java, then, is that code can only directly access any variable in the current scope (block) or higher, up to the class level. (By directly, we mean without qualification.)
Variable lifetime
When do variables come to life, and how long do they live? For RPG III, as you know, they come to life when the program is first called and they live until the program is ended (by either setting the LR indicator to 1 or by ending the job). Global fields in RPG IV have the same lifetime, although you can now subdivide a job into named ILE activation groups, and fields will not live past the life of these. The new local fields inside RPG IV procedures only come to life when the procedure is called, and they die immediately when it ends. These are sometimes called automatic fields, since their death is automatic with the ending of the procedure. However, if you specify STATIC for a local field in RPG, that field's value persists for the life of the program or service program it is in.
For Java methods and nested blocks inside methods, these automatic or local variables come to life when the method is called or the block is entered. If an initialization has been specified, it is applied at that time. When the block ends or method ends, the variable goes away, just as with RPG IV procedures. (By come to life we really mean when the variables get allocated actual storage in memory.)
For Java class-level variables, life is more interesting. Non-static class-level variables (that is, instance variables) come to life when an instance of the class (object) is instantiated with the new operator. Each class instance gets its own memory allocation for the instance variables, so they are totally independent of each other. If your instance variable declaration includes an initializer (for example, = 10), that initial value is applied at the same time as the object is created, and its instance variables come to life.
For static class-level variables (that is, class variables), there is only one allocated memory location per variable, regardless of the number of instances of the class. Since the life and values of a static variable are not dependent on, or even related to, instances of the class, it makes sense that static variables come to life or are allocated memory as soon as the Java runtime "knows about" the class. This typically occurs at pre-runtime, when the application is starting up, as it does for RPG global fields. The important thing to remember is that static variables will be alive as soon as you first possibly need them. Recall that in Java, unlike RPG, local variables in a method cannot be static.
Variable external access
When we talk about scoping of a variable, we are talking about what code has direct access to it. In this case, we are talking about code in the same compilation unit as the variable. In RPG III, a compilation unit is a program object; in RPG IV, it is a module object; in Java, it is a class. However, in both RPG IV and Java, code outside of a variable's compilation unit can access that variable.
In RPG IV, this is accomplished by specifying the EXPORT keyword on the field definition specification. This allows other modules in the final program or service program to access this field, providing they define it as well, but with the IMPORT keyword. This tells the compiler that this field is allocated in another module, but that you intend to access it in this module. In a service program, you can even go further and make your exported fields (and procedures) available to other programs, if you specify as much in the binding source for the service program (as described in Chapter 2).
The Java equivalent to RPG's EXPORT keyword is the use of the public modifier on your class-level variables. This tells the compiler that you want any code in any other classes to have access to this variable. Think of this as being equivalent to exporting your variable, via EXPORT and binding source, from an RPG IV service program for all the world to use. However, good form dictates that you will rarely, if ever, do this for non-static variables; you should prefer to force users to go through your getXXX and setXXX methods to access your instance variables. To prevent such global access to your variables, you can instead not specify any modifier, giving your variable package access. This gives access to your class only to other classes in this package. Since we equate a package to a service program, this is equivalent to EXPORTing your variable in RPG, but not specifying it in the binding source. To completely restrict other classes, use the private modifier, indicating only your class code has access. (There is also a protected mode, limiting access to classes that extend or subclass this class, a topic discussed in Chapter 9.)
Assuming that you have made a variable available to outside classes, that outside code cannot simply use the variable name as-is. RPG forces the external code to IMPORT the field, and Java forces the external code to qualify the variable name. For static variables, the variable name must be qualified with the class name separated by a dot. For non-static variables, the variable name must be qualified with an object reference variable name, again separated by a dot. This dot operator tells the compiler that the variable is not part of this class, and further, tells the compiler (and actually the runtime) in which class or class instance the variable can be found. The class itself is found by looking first in the current package, and then by searching the imported packages. Alternatively, the class name can be explicitly qualified with the package name.
Variables An example
At this point it would be helpful, no doubt, to see an example of Java variables in action. Consider Listing 5.2.
Listing 5.2: The Widget Class Example of Java Variables
public class Widget { public static final int TYPE1 = 1; // constant public static final int TYPE2 = 2; // constant public static int nextID= 0; // class private int id; // instance private int type = 0; // instance public Widget() // Constructor. Note name == class name { id = nextID; // references instance and class vars nextID = nextID + 1; } public boolean setType(int newType) { boolean inputOK = true; // local if (newType >= TYPE1 && newType <= TYPE2) type = newType; // references instance variable Else inputOK = false; return inputOK; } public String toString() { String retString; // local retString = "Type = " + type + ", ID = " + id; return retString; } } // end Widget class
This example class has two constant variables (TYPE1 and TYPE2), one static variable (nextID), and two instance variables (id and type). It also has a constructor method (Widget) and two other methods (setType and toString). All of these access one or more class-level variables. The last two methods also declare their own local variables (inputOK and retString), while setType accepts a method parameter (newType). Listing 5.3 shows another class that uses this class.
Listing 5.3: Testing the Widget Class
public class TestWidget { public static void main(String args[]) { Widget.nextID = 1000; // set class variable Widget myWidget = new Widget(); // object 1 myWidget.setType(Widget.TYPE1); // call method Widget myWidget2 = new Widget(); // object 2 myWidget2.setType(Widget.TYPE2); // call method System.out.println(myWidget); // calls toString method System.out.println(myWidget2); // calls toString method } } // end TestWidget class
Compiling and running this test class gives the following output:
>javac TestWidget.java >java TestWidget Type = 1, ID = 1000 Type = 2, ID = 1001
First, notice the toString method. If you supply this special method for Java in your class, Java will implicitly call it when you specify an instance of your class on the System.out.println method, which is done in the TestWidget class. This method simply creates a String object (covered in Chapter 7) that displays some interesting variable values. Including a toString method in your class is a good idea to make debugging easier, if nothing else.
This Widget class example highlights a few of the items under discussion. It represents an item that you'll need a lot of. Each unique Widget will be identified by a type (the constants TYPE1 or TYPE2 make this less error-prone) and by a unique identifier. The assignment of a unique identifier is solved by using a static variable (nextID), since there is only ever one value for a static variable. You simply use and increment this variable in a constructor so each Widget instance gets a unique value. The problem is this static variable need to be "seeded" at the beginning of the program, outside of the class. That is why it is public, and you see in the TestWidget class that it is seeded to 1000. The type is set per Widget by calling the method setType and passing in one of the defined constants for a value. Notice that the instance variable type is set to this value (if it is valid). Because id and type are instance variables, each Widget instance gets to maintain its own independent values for these, which is what you want. Also notice that these instance variables are made private, so there's no risk of external code (outside of this class) directly changing any of Widget's values.
Take note in TestWidget how the references to the static variables are qualified in the Widget class by qualifying them with "Widget." Without this, Java would look for these variables in the TestWidget class. Also note how two instances of Widget are instantiated by using the new operator, and then the setType method is invoked on each one to independently change the instance variables of each object.
As an RPG III programmer, you will not have used either local variables or parameters (as shown in the setType method), although as an RPG IV programmer, you have both, through the use of procedures (as described in Chapter 2). However, while RPG's global fields are directly equivalent to class-level static variables, the concept of instance variables that have different values per class instance is totally foreign to RPG. In essence, RPG gives you exactly one instance of each module in your programs.
Another thing to watch for in Java versus RPG is variable initialization. In RPG, the compiler supplies reasonable defaults for fields, on which we sometimes rely. Java has a similar supplied default value for class-level variables, but not for local variables. Rather, a local variable must be initialized or assigned a value before it is first referenced or the compiler will issue an error message. A good rule is to always initialize a variable explicitly, even if it is to zero. Even in RPG!
Literals by Data Type
You have already seen an example of an int (integer) variable initialization in Java, so you are familiar with the syntax:
int myVariable = 10;
However, let's round out this example with examples of initialization for each data type. It is important to know this, because any literal value you specify at initialization time (or assign later, for that matter) must be consistent with the variable's data type. Each data type, both in RPG and Java, has an explicit syntax for literal values. We humans learn by example, so we will teach the syntax of literals by example. First, the non-numeric data types are shown in Table 5.8.
RPG Type |
Example |
Java Type |
Example |
---|---|---|---|
character 1 |
'a' or X'7D' |
Char |
'a' or ''' |
character n |
'abc' or 'Bob''s Store' |
String |
"ABC" or "c:\mydir" or "Bob's Store" |
graphic |
G'oK1K2i' |
String |
"K1K2" where Kn is a DBCS character |
indicator |
'0' or '1' or *OFF or *ON |
Boolean |
false or true |
date |
D'2001/12/11' |
Date |
Discussed in Chapter 8 |
time |
T'11:33;01' |
Date |
Discussed in Chapter 8 |
timestamp |
Z'2001/12/11.33. 01' |
Date |
Discussed in Chapter 8 |
basing pointer |
*NULL or %ADDR(myVar) |
Object reference |
null or new MyClass() |
procedure pointer |
*NULL or %PADDR(myProc) |
n/a |
The character data type takes a literal enclosed in single quotes. In Java, this can be only a single character, while in RPG, it can be multiple characters. RPG allows hexadecimal numbers if they begin with the letter x. Java also allows special character values if they are preceded by a slash, which is called an escape sequence. Examples are shown in Table 5.9.
Escape Sequence |
Description |
---|---|
|
Newline character |
|
Tab |
Backspace |
|
|
Carriage return |
f |
Form feed |
\ |
Backslash |
' |
Single quote |
" |
Double quote |
ddd |
Octal number, not to exceed 377 |
uxxxx |
Unicode number, must be four digits |
For numerics, some examples are shown in Table 5.10. The non-float values in Java are simple integers that must not be larger than the capacity of the data type. However, long data type literals do allow an optional L character (either uppercase or lowercase) after them. This tells the compiler that these are indeed long values, which can be important in expressions, where casting between data types can happen implicitly. By default, these literals are assumed to be base-10 decimal numbers, but you can also specify octal (base-8) or hexadecimal (base-16). For octal, use a leading zero, as in 035. For hexadecimal, use a leading 0x or 0X, as in 0x1d.
RPG Type |
Example |
Java Type |
Example |
---|---|---|---|
integer 3 |
120 or -120 |
byte |
120 or -120 |
integer 5 |
20000 or -20000 |
short |
20000 or -20000 |
integer 10 |
1000000 or -1000000 |
int |
1000000 or -1000000 |
integer 20 |
9000000 or -9000000 |
long |
9000000 or -9000000L (optionally ends in l or L) |
unsigned |
10 or 20000 |
||
binary, package, zoned |
10 or 10.12 |
||
float 4 |
10 or .12 or 1234.9E12 |
float |
10f or 12.1f or 1.234E12F (always ends in f or F) |
float 8 |
10 or .12 or 1234.9E12 |
double |
10 or 12.1D or 1.234E12 (optionally ends in d or D) |
Float-type literals in Java are decimal numbers, followed by an optional decimal point, followed by an exponent. The literal can be followed by an F for single-precision float or a D for double-precision float. If no F or D is specified, any literal with a decimal point is assumed to be double-precision. You can also force an integer literal to be a float literal by simply appending an F or D to it, as in 123F. The exponent part consists of an E or e, followed by a positive or negative decimal number.
Numeric Data Type Ranges
When programming with numeric data types, it is important to know the limits to each data type, so that you can choose the appropriate data type to avoid overflow or truncation.
In RPG, when you define a numeric data type field and specify a length or decimal type, you do so by specifying how many decimal "digits" the field will hold. You do not specify how many bytes of memory to allocate—that is done implicitly by the compiler, based on the number of digits you indicate (the exception to this rule is the float data type, where you do specify the number of bytes, either four or eight). This digit length is a rather unique means of describing length, and underscores RPG's role as a business language, as opposed to a scientific or general language. Java, on the other hand, does not burden you with the business of specifying lengths at all. Rather, it supplies a number of different numeric types for you, each of which represents a different predefined length or capacity. This implies, however, that you need to know the limit for each data type, in order to choose the correct one for your particular case. You could simply always choose the largest—long for integers and double for floating-point—but for large numbers of variables, this will be a waste of computer memory, as these take up more space than their smaller cousins.
To determine the limits for each data type, you have to know the underlying size of each in bytes. Thus, in the end, you are forced to think in terms of numerical limits, rather than the number of digits, as you usually do in RPG. Let's review, then, the RPG data type ranges in Table 5.11.
Type |
Bytes |
Range |
---|---|---|
binary 4,0 |
2 |
-9,999 to 9,999 (four 9s) |
binary 9,0 |
4 |
-999 ,999, 999 to 999,999,999 (nine 9s) |
zoned 30,0 |
30 |
-30 9s to +30 9s |
packed 30,0 |
16 |
-30 9s to +30 9s |
integer 3 |
1 |
-128 to 127 |
integer 5 |
2 |
215 = -32,768 to 32,767 |
integer 10 |
4 |
231 = -2,147,483,648 to 2,147,483,647 |
integer 20 |
8 |
263 = -huge to +huge |
unsigned 3 |
1 |
0 to 255 |
unsigned 5 |
2 |
216 = 0 to 65535 |
unsigned 10 |
4 |
232 = 0 to 4,294,967,295 |
unsigned 20 |
8 |
264 = -huge to +huge |
float 4 |
4 |
1.175 494 4 E-38 to 3.402 823 5 E+38 |
float 8 |
8 |
2.225 073 858 507 201 E-308 to 1.797 693 134 862 315 E+308 |
Note how integer types allow a maximum value of 2n-1, where n is the number of bits in the allocated memory length. For unsigned types, since one bit is not required to store the sign, it is 2n. Thus, if you are still using a binary data type but specifying zero decimal positions, it is to your advantage to switch to the integer data type. Furthermore, it is more efficient in internal expression evaluations than is binary.
Let's now compare these ranges to those of Java's built-in numeric types, in Table 5.12. (Note that the ranges given for float and double in both languages are positive numbers. The negative number range is the same.)
Type |
Bytes |
Range |
---|---|---|
byte |
1 |
27 = -128 to 127 |
short |
2 |
215 = -32,768 to 32,767 |
int |
4 |
231 = -2,147,483,648 to 2,147,483,647 |
long |
8 |
263 = -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
float |
4 |
1.402 398 64 E-45 to 3.402 823 47 E+38 |
double |
8 |
4.940 656 458 214 465 44 E-324 to 1.797 693 134 862 315 70 E+308 |
Float Versus Packed
What are floating-point fields, and why would you use them? You can see that for Java, float and double are the only primitive data types that support decimal points. Since your RPG code is riddled with packed decimal fields, your first inclination might be to use float or double in your Java code, or to use int or long for zero-decimal packed numbers.
Indeed, for zero-decimal fields, this is exactly what we recommend, but floating-point fields (whether single-precision or double-precision) are somewhat less efficient than packed fields. This is because floating-point fields are designed to hold arbitrary values without benefit of previous knowledge about the number of decimals. That is, you do not tell the compiler at definition time how many decimals the data will contain; you simply specify whether it is to be four or eight bytes long. Any valid floating-point number can be assigned to that variable during runtime. This makes for great flexibility, but it also means that the internal storage of these numbers has to be flexible. That means, as always with flexibility, a performance cost in order to interpret the data at runtime; the internal format of the data essentially must be converted every time it is referenced. Compare this to packed decimal, where you tell the RPG compiler at field-definition time exactly how many decimals any data contained in the field will have. This allows the compiler to store the data in a fixed format every time, and allows mathematical operations to exploit this knowledge for very fast evaluations. (Mind you, most hardware today offers built-in support for floating-point manipulation.)
Therefore, in cases where you are dealing with a predetermined number of decimals (such as with amounts of money), floating-point is not a good choice. Floating-point fields are a good choice when dealing with data for which you cannot predict the exact decimal precision, such as with scientific data, real-world measurements like the size of a human hair, and so on. It is also of value even when the number of decimals can be hard-coded, but the size of the number cannot. For example, if the number may become extremely large or extremely small (well below one), float or double can handle it. It is hard to imagine what type of data would need to allow for numbers bigger than 1.7 * 10308!
However, this refers to RPG, where you have a choice between packed and float. In Java, you have no such choice, as there is no equivalent data type to packed. For business applications that deal with pay rates, unit costs, unit prices, and so on, it is hard to imagine the need for the power of floats, with their attendant costs. It is too bad Java did not see fit to support a built-in packed decimal data type, but clearly the designers were not coming from an RPG background!
On the surface, then, it appears you are stuck with Java's float or double. However, these have other, perhaps more serious, concerns than performance—that is, their propensity to produce high-precision results after a mathematical operation. You do not want to be billing your customers in ten-thousandths of a cent, for instance. Let's look at a Java example. Imagine you have widgets to sell for $1,234.56, and you decide to have a sale where you offer 23 percent off. What might the calculations for the new price look like? Try the code in Listing 5.4.
Listing 5.4: Using a Float for Monetary Math
public class TestFloat { public static void main(String args[]) { double unitCost = 1234.56; double discount = 0.23; System.out.println("unitCost = " + unitCost); unitCost = unitCost - unitCost * discount; System.out.println("saleCost = " + unitCost); } }
The result of the code in Listing 5.4 is the following:
unitCost = 1234.56 saleCost = 950.6111999999999
Can you imagine sending this bill to your customers? To circumvent this problem, you might try writing your own complicated code to force the decimal precision and make some decision on rounding, but this is ugly code. Really, then, float and double are not good choices for monetary values.
However, all is not lost! The Java designers did supply, at least, a Java class to emulate the mathematical behavior of a packed-decimal data type. While not a built-in type, it is at least something that can offer accurate results with methods for easily setting the decimal precision. The class is named BigDecimal, and you find it in the java.math package. This class offers you complete control over rounding behavior on divide and decimal-point adjustments, and offers a very complete set of mathematical and conversion functions in the form of methods. We recommend that you use this class, and in fact the AS/400 Toolbox for Java product maps packed-decimal database fields to this class in its two database packages. If your packed decimal operations on the AS/400 use half-adjust, then even that rounding option is available to you.
To understand this class and its documentation in the JDK, you have to be familiar with its terminology and architecture. An instance of a BigDecimal class is really the following:
- An integer value like 123456, with no limit on the number of digits (precision) in that integer value.
- A decimal position (scale) within that integer value. This is specified in terms of the number of digits to the right of the decimal point.
When you create a new BigDecimal object, you have three choices for the initial value: a string like 1234.56, a BigInteger object (another class in java.math), or a double value. If you get your value from the user, it will probably be as a string. Using a string sets both the integer value (123456) and the scale (two, in this example, since there are two decimal positions). Alternatively, you can use the static method valueOf(long val, int scale) to return a new BigDecimal object with the integer value and scale you specify:
BigDecimal unitCost = BigDecimal.valueOf(123456, 2);
Once you have a BigDecimal object, you can perform all the usual mathematical operations on it via methods like add, subtract, divide, multiply, abs, and negate. You can also use comparative methods like compareTo, min, and max to compare it to another BigDecimal value. (Note that compareTo ignores trailing zeros in the decimal portion, so 1234.00 is considered equal to 1234.0000. If you do not want this, use the equals method.) There are also methods for converting a BigDecimal object to other data types such as integer (intValue), long (longValue), double (doubleValue), BigInteger (toBigInteger), and, of course, string (toString).
How does BigDecimal deal with the precision problem, as in the example involving the 23 percent discount? It will, by default, give you more decimal points after a multiplication than you started with. (Specifically, it will give you back the sum of the decimal points of the two operators.) However, it supplies a very useful method named setScale to set the number of decimal points after an operation like multiply. This method simply moves the decimal point, but leaves the non-decimal part of the number unchanged. For example, if you start with 1234.5678 and call setScale(5), you will get 1234.56780.
But what if you call setScale(2)? This will give you back either 1234.56 or 1234.57, depending on the rounding behavior you ask for. This behavior can be specified as the second parameter to setScale, and must be one of the eight constants defined in BigDecimal. It's likely that you will only ever use two of these, however—either BigDecimal.ROUND_DOWN (which gives you 1234.56, i.e., a simple truncate), or BigDecimal.ROUND_HALF_UP (which gives you 1234.57, i.e., it rounds up if truncated digits are greater than five). Let's revisit the discount example in Listing 5.5, this time using BigDecimal instead of double.
Listing 5.5: Using BigDecimal for Monetary Math
import java.math.*; public class TestBD { public static void main(String args[]) { BigDecimal unitCost = new BigDecimal("1234.56"); BigDecimal discount = new BigDecimal(".23"); System.out.println("unitCost = " + unitCost); // unitCost = unitCost – unitCost * disCount unitCost = unitCost.subtract( unitCost.multiply(discount) ); System.out.println("unitCost after discount = " + unitCost); unitCost = unitCost.setScale(2, BigDecimal.ROUND_HALF_UP); System.out.println("unitCost after setScale = " + unitCost); } }
This gives the following result:
unitCost = 1234.56 unitCost after discount = 950.6112 unitCost after setScale = 950.61
There are only two methods in BigDecimal that may cause truncation of data from the decimal part of the number: setScale and divide. All other operations increase the number of decimal digits, if required. Note that the non-decimal part of the number is never truncated, but rather simply grows as much as necessary to hold the result. For setScale and divide, you should choose BigDecimal.ROUND_HALF_UP if you normally use the half-adjust operation modifier in RPG; otherwise, you should choose BigDecimal. ROUND_DOWN. However, there are six additional rounding options for you as well:
ROUND_CEILING, ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_EVEN, ROUND_UNNECESSARY, and ROUND_UP.
Note in the previous example a BigDecimal method call was nested as a parameter to another BigDecimal method:
unitCost = unitCost.subtract( unitCost.multiply(discount) );
This is possible because the multiply method returns a new BigDecimal object, which is then passed in as a parameter to the subtract method. This method also returns a new BigDecimal object, which equates back to the original unitCost object reference variable. In fact, all the mathematical methods in BigDecimal return new objects rather than changing the object on which they are invoked. That is why you must code object = object.method(xxx) when using the BigDecimal class. It is read-only or, in Java-speak, immutable. No method has any impact on the object on which it is directly invoked; rather, the method returns a new object altogether. If the returned object is equated to the source object variable, then the very original object in memory is lost and will eventually be swept up by the garbage collector. (You will learn about another immutable class, String, in Chapter 7.)
The Boolean Data Type
Since the boolean data type in Java is somewhat foreign to you as an RPG programmer, we believe that it is worth a brief description. In both RPG and Java, you have expressions that evaluate to true or false, and so affect the flow of control in your program, like this:
if (amountDue > 0) // do something
The notion of true or false is intrinsic to computing, and is the end result of any conditional expression, no matter how complex. In RPG and DDS, this notion is embedded in indicators, with their zero or one states (or *OFF or *ON). The Java boolean data type is roughly equivalent to RPG indicators, but rather than using zero or one to indicate state, the special keywords true and false are used. Therefore, you can initialize and set boolean variables like this:
boolean myFlag = true; myFlag = false;
This is very similar to RPG indicators. Indeed, in both cases, they can be used directly as expressions. For example, in Java all of the following are valid:
if (myFlag == true) // .... if (myFlag) // ... if (myFlag == false) // ... if (!myFlag) // ....
In RPG IV, you can do something similar:
D myFlag S N INZ(*ON) C IF myFlag
This is quite handy. Boolean variables can be used wherever an expression or sub-expression is allowed, including while and for loop conditionals. Most interestingly, a boolean variable can be assigned an expression. That is, it can be assigned the resulting true or false value of an expression, as in:
myFlag = (amountDue > 0); // will be true or false
You can do the same with indicator data types in RPG IV:
C EVAL myFlag = *INLR *OFF
This can all make for elegant and compact code.
Finally here is a little example to show how to convert between integer and boolean values:
// boolean test int y = 0; if (test) y = 1; // Boolean to integer y = (test) ? 1 : 0; // Boolean to integer (option 2) test = (y!=0); // Integer to boolean
Casting and Numeric Conversions
Casting is the process of converting a variable's value from one data type to another. RPG's way of converting numeric fields from one type to another is very straightforward, as Listing 5.6 illustrates.
Listing 5.6: Moving Data between Fields of Different Types in RPG
FQSYSPRT O F 80 PRINTER OFLIND(*INOV) D DS1 DS D INT5 5I 0 INZ(25) D BIN9 9B 0 INZ(22) D ZONE9 9S 0 INZ(30) D PACK9 9P 0 INZ(40) D*————————————————————————— C MOVE BIN9 INT5 C EXCEPT RESULT C MOVE PACK9 INT5 C EXCEPT RESULT C MOVE ZONE9 INT5 C EXCEPT RESULT C EVAL *INLR = *ON C*————————————————————————— OQSYSPRT E RESULT O INT5 15
This example declares a number of fields with different types, and initializes them. In the body of the code on the C-specs, you see multiple moves from one numeric field to another, followed by an exception output. The compiler determines the types of these fields and does the appropriate conversion as needed. In this example, then, each instance of binary, packed, and zoned is converted to integer. This example works the same if you use EVAL for the assignments, versus MOVE. What happens if the value being moved will not fit in the result? It is truncated.
Java is somewhat similar to RPG in this respect. That is, in most cases, you can simple move data between data types with no unique syntax required other than an assignment statement:
short myShort = 10; int myInt = 20; long myLong; myLong = myShort; myLong = myInt;
This is a perfectly legal and common practice. However, you will notice that we are only doing safe conversions here, from a smaller type to a larger type. Thus, the result will always safely fit in the target variable. What if the target type is smaller than the source type? For example, consider this:
long myLong = 40000; short myShort = myLong;
This will fail. In fact, it will not even compile. Contrast this to RPG, where it is legal at compile time, and at runtime the result is simply truncated if necessary. Sometimes, though, you'll want to do this because you know that the resulting value will fit in the target. There is a way to force Java to allow the conversion and simply truncate the result if necessary (but, of course, you should avoid these situations). It involves some simple casting syntax, which looks like this:
long myLong = 20000; short myShort = (short)myLong;
The syntax specifies the resulting type in parentheses before the variable or operation:
(target type) source-value
This is simply a directive to the compiler that you know what you are doing and you will take responsibility for the risk of truncation. Casting like this is required, as mentioned, for any conversion from one data type to another of lower precision. It is allowed in all other cases as well, but not required.
Table 5.13 summarizes where casting is necessary in numeric type-to-type conversions. Read this table by scanning across the rows. You see that converting from a data type specified in the left column to any data type in the columns across the top does not require a cast for those target data types that have higher precision. There are two apparent anomalies: a cast is required from byte to char and from char to short. The first anomaly occurs because, while char is two bytes and byte is one, byte is signed while char is not. Hence, the sign of the byte value could be lost. The second occurs because char is unsigned and byte is signed, so even though they are both two bytes, there is potential for corruption with negative or large positive values. So, these are not actually anomalies after all. To be safe, always cast—you have nothing to lose, if you know truncation will not occur.
byte |
Char |
short |
int |
long |
float |
double |
|
---|---|---|---|---|---|---|---|
byte |
Cast |
||||||
char |
Cast |
Cast |
|||||
short |
Cast |
Cast |
|||||
int |
Cast |
Cast |
Cast |
||||
long |
Cast |
Cast |
Cast |
Cast |
|||
float |
Cast |
Cast |
Cast |
Cast |
Cast |
||
double |
Cast |
Cast |
Cast |
Cast |
Cast |
Cast |
Note |
If you are using a BigDecimal object, you cannot use this primitive type casting technique. Instead, use the supplied methods in the BigDecimal class named xxxValue(), where xxx is each of the numeric primitive types, for example:double myDouble = myBigDecimalObject.doubleValue(); |
Now that we have demonstrated how RPG does conversion with the MOVE operation code, what about other operations, such as numeric operation codes or the EVAL op-code that allows expressions in factor two? Take a look at Listing 5.7
Listing 5.7: Expressions Involving Fields of Different Types in RPG
FQSYSPRT O F 80 PRINTER OFLIND(*INOV) D DS1 DS D INT5 5I 0 INZ(25) D FLT4 4F INZ(10) D FLT8 8F INZ(10) D BIN9 9B 0 INZ(28) D ZONE9 9S 0 INZ(80) D PACK9 9P 0 INZ(40) D*———————————————————————————————————————————————————————————— C EVAL INT5 = ((BIN9+ZONE9/PACK9)*2)+FLT4 C EXCEPT RESULT C EVAL *INLR = *ON C*———————————————————————————————————————————————————————————— OQSYSPRT E RESULT O INT5 15
As with the MOVE operation code, you do not have to tell the RPG compiler of your intentions for field conversions. The compiler will do the conversion and expression evaluation internally before placing the result into the target field. In this case, the target is INT5, an integer of length 5. Actually, RPG introduced in V3R7 a new set of built-in functions to allow you to have more control over converting fields. For example, the %INT and %INTH built-in functions convert a field to an integer value without and with half-adjust, and %FLOAT converts a numeric field to float.
What happens in Java? Suppose you want to add integer, short, and byte field values together, and divide the result by a float number, and perhaps even assign the result of the expression to a double field. This is shown in Listing 5.8.
Listing 5.8: Expressions Involving Variables of Different Types in Java
public class TestEXPR { public static void main(String args[]) { byte byt = 30; hort sht = 20; int int1 = 20; double dbl = 10.0d; float flt = (float)(((byt+sht-int1)*2.0)/dbl); System.out.println(flt); } }
This example declares different numeric fields. The highlighted line defines the final expression. It consists of adding the byte and short fields and then subtracting an integer field from the result. It then multiplies by 2.0, which is a double literal, and finally divides the result by a double field. The most important thing in this example is that it casts the final result to a float. The result of executing this is the value 6.0, which is the result of the operation. Had we not cast the result, the compile would have failed.
Under the covers, a series of rules is used to convert the intermediate results to a common format, usually that of the highest precision of all the operands:
- If at least one of the operands is of type double, the intermediate result is double (if the other operand is not also double, it is implicitly cast to double), and eight-byte math is used.
- If at least one of the operands is of type float, the intermediate result is float (if the other operand is not also float, it is implicitly cast to float), and four-byte math is used.
- If at least one operand is of type long, the intermediate result is long (if the other operand is not also long, it is implicitly cast to long), and eight-byte math is used.
- If none of the above, the intermediate result is integer (all operands are implicitly cast to integer), and four-byte math is used.
Casting is done often, both implicitly and explicitly, in Java. Another place this happens is on the return statement of a method. If you define a method's return type to be integer, your actual returned value can be a byte, character, short, or integer value without explicit casting. However, if you return a long value, you must explicitly cast it:
public int testThis() { long retValue = 20000; return (int)retValue; }
Java Data Type Class Wrappers
Java supplies class wrappers for its primitive data types. These are classes in the java.lang package that allow you to work with each of the data types as objects instead of primitives. All of the operators are emulated via methods in the class. Furthermore, the classes supply some interesting and worthwhile additional methods and constants. The classes are as follows:
- Boolean
- Byte
- Character
- Double
- Float
- Integer
- Long
- Short
They come in handy when an object is needed, yet you have a primitive. For example, you might have an array of mixed values—some integers and some objects. This is not legal, of course, so you would instead use the class wrappers to produce objects from your integer values. You would define the array to hold objects of class Object, a generic class discussed in Chapter 9.
All of these classes contain constructors that accept a primitive type value as input for conversion to an object. Most importantly, they all supply methods to convert to and from a string, such as the methods parseInt and valueOf in the Integer class. You will use these often, as Java only accepts user input in the forms of strings, so conversion is essential. Table 5.14 shows the methods supplied by each class to convert from a string, convert to a string, and convert to a primitive type.
Class |
From a String |
To a String |
To a Primitive |
---|---|---|---|
Boolean |
valueOf(String) |
toString() |
booleanValue() |
Byte |
valueOf(String) decode(String) |
toString() |
byteValue(), doubleValue(), floatValue(), intValue(), shortValue(), longValue() |
Character |
toString() |
charValue(), getNumericValue() |
|
Double |
valueOf(String) |
toString() |
doubleValue(), byteValue(), floatValue(), intValue(), longValue(), shortValue() |
Float |
valueOf(String) |
toString() |
floatValue(), byteValue(), doubleValue(), intValue(), longValue(), shortValue |
Integer |
valueOf(String) parseInt(String) |
toString() |
toBinaryString(long) toHexString(long) toOctalString(long) intValue(), byteValue(), doubleValue(), floatValue(), longValue(), shortValue() |
Long |
valueOf(String) parseLong(String) |
toString() |
toBinaryString(long) toHexString(long) toOctalString(long) longValue(), byteValue(), doubleValue(), floatValue(), intValue(), shortValue() |
Short |
valueOf(String) parseShort(String) |
toString() |
shortValue(), byteValue(), doubleValue(), floatValue(), intValue(), longValue() |
Note that the valueOf(String) methods all throw the exception NumberFormat Exception if the input string is not valid. This is Java's equivalent to sending an escape message on the iSeries, and you must "monitor" for these using try/catch-blocks. Although this concept is not discussed in detail until Chapter 10, we mention it now so that we can show you the example in Listing 5.9. It accepts a float value as a string from the command line, then converts it to a primitive float using the Float class.
Listing 5.9: Converting a String to a Float Object, and a Float Object to a float Primitive
public class TestConvertFloat { public static void main(String args[]) { Float floatObject; if (args.length != 1) return; try { floatObject = Float.valueOf(args[0]); } catch(NumberFormatException exc) { System.out.println("Invalid input!"); return; } float floatValue = floatObject.floatValue(); System.out.println("input = " + floatValue); } }
Note that args[0] represents the first parameter string passed in the command line. The result of compiling this and running it with various inputs is as follows:
>javac TestConvertFloat.java >java TestConvertFloat 1.2 input = 1.2 >java TestConvertFloat 1.02 input = 1.02 >java TestConvertFloat 0000.12 input = 0.12 >java TestConvertFloat 1.2e4 input = 12000.0 >java TestConvertFloat -1.2e4 input = -12000.0 >java TestConvertFloat abcdef Invalid input!
Another useful thing about these classes is that there are constants for the minimum and maximum values these data types allow—for example, Integer.MAX_VALUE and Integer.MIN_VALUE. Numerous other useful methods exist, including a number of isXXX() methods in the Character class, such as isSpaceChar. Many of these methods are static and take their target as a parameter, so they do not require an instance of the class. The class then just becomes a convenient place to group these related traditional-style functions. It is worth your time to peruse the JDK documentation pertaining to these classes.
Summary
This chapter introduced you to the following concepts:
- Java has two categories for data types: primitive and reference.
- Java has eight primitive data types: boolean, char, byte, short, int, long, float, and double.
- Java does not have an equivalent data type to RPG's binary, packed, or zoned. However, the class java.math.BigDecimal works well as a packed-decimal replacement.
- The character data type in Java represents one character, whereas RPG's character data type can be looked at as an array of one-byte characters. The equivalent in Java is the String class.
- Character and string data in Java is Unicode based, versus RPG's EBCDIC base. (Information on the Unicode standard can be found on the Web at www.unicode.org).
- The syntax type name; defines a Java variable.
- In both RPG IV and Java, you can declare a variable as either global or local.
- Only class-level variables can specify modifiers.
- The modifiers for variables are public, private, protected, static, final, transient, and volatile.
- In Java, you can initialize a variable when it is declared, using the assignment operator (=) and a literal, variable, or expression. This compares to the INZ keyword in RPG.
- Without qualification, code can access local variables, parameters, or class-level variables of the containing class only.
- With qualification, class-level variables in other classes can be accessed, depending on the modifiers specified for them.
- Static variables have a single value per class, whereas non-static variables have a unique value per class instance or object.
- Constants are defined in Java by specifying the modifiers static and final.
- Casting in Java is implicit when converting to a higher-precision data type, but requires explicit cast syntax when converting to a lower-precision data type.
- There are class wrappers for each of the primitive data types that offer string conversion and other useful methods.