10.5 Fields and Variables

A Java program keeps track of state in three different ways:

  • Local variables

  • Object fields

  • Static fields

Three different kinds of instructions are used. To read the state of local variables, the load instructions (fload, aload, iload, etc.) are used. For object fields, getfield is used. For static fields, getstatic is used. To change the state, corresponding store and put instructions are used.

To change the state of any of these variables, an assignment expression is used. An assignment expression is written using the = operator. On the right side of the = is an expression that gives the value of the assignment. The left side names some local variable, object field, or static field. Look at the following example.

 class Employee {    /** The total payroll */    static float payroll;    /** The employee's name */    String name;    /** The employee's salary */    float salary;    /** Give the employee a raise. This involves raising the     * employee's salary and increasing the total payroll     */    public void increase_salary(float raise_percentage)    {         float raise;         raise = raise_percentage * salary;         this.salary = this.salary + raise;         Employee.payroll = Employee.payroll + raise;    } } 

The increase_salary method uses all three kinds of state: raise and raise_percentage are local variables; salary is a field in the Employee object; payroll is a static field shared by all Employees.

Note that the left side of an assignment is not an expression. Rather, it designates the target of the assignment. In increase_salary, the code fragments raise, this.salary, and Employee.payroll are the targets of the assignments. The first names a local variable, the second a field, and the third a static field.

To compile these expressions, evaluate the equation on the right-hand side and then assign the results to the target. The instruction used for the assignment depends on the target. The increase_salary method translates to

 .method increase_salary(F)V .var 0 is this LEmployee; .var 1 is raise_percentage F .var 2 is raise F ; raise = raise_percentage * salary fload_1                        ; Push raise_percentage aload_0                        ; Push this getfield Employee/salary F     ; Get this.salary fmul                       ; Compute raise_percentage * salary fstore_2                   ; Store the result in raise 

The first four instructions in this method compute raise_percentage * salary. (For more about how to compile arithmetic operations, see section 10.8.) The fstore_2 instruction stores the result of the expression into raise, which is represented by local variable 2.

The Java declaration of raise states that any value stored in it must be a float. This is the case, so the method is type-safe so far.

 ; this.salary = this.salary + raise aload_0                   ; Push this as target of the assignment aload_0                   ; Get this.salary getfield Employee/salary F fload_2                       ; Push raise fadd                          ; Compute this.salary + raise putfield Employee/salary F    ; Store into this.salary 

An assignment to a field is of the form expression.fieldname = expression. The first expression results in the object that holds the target of the assignment. The result must be an object. The field name must be a field within that class of that object. In this case, the target expression is this and the field name is salary. The type of this is Employee, and salary is a nonstatic field within that class.

The code begins by evaluating this with the instruction aload_0. This will hold the target of the assignment. It is left on the stack until the assignment is performed.

Next comes the evaluation of the right-hand side: this.salary + raise. This compiles into code ending in fadd, which leaves the result of the computation on the stack. This is an expression, so it leaves exactly one value on the stack. The compile-time type of this expression is float.

Now the stack contains the target of the expression and the value to assign. The putfield instruction assigns the value to the field. As before, the type of the value must conform to the type of the target. In this case they are both floats, so the method is type-safe so far.

 ; Employee.payroll = Employee.payroll + raise getstatic Employee/payroll F   ; Get Employee.payroll fload_2                        ; Push raise fadd                           ; Compute Employee.payroll + raise putstatic Employee/payroll F        ; Store into Employee.payroll return                              ; Terminate the method .end method 

The final statement assigns to the static field payroll. Because a static field does not belong to any particular object, it isn't necessary to push an object onto the stack at the beginning of this assignment. After evaluating the expression Employee.payroll + raise, the putstatic instruction is used to store the value into the static field.

As before, the type of the value and the type of the field agree. Since that is the end of the method and all the statements are type-safe, the entire method is considered safe.

10.5.1 Abbreviations

The body of increase_salary might be rewritten as

 raise = raise_percentage * salary; salary = salary + raise; payroll = payroll + raise; 

This omits unnecessary parts of the expressions, like this. to indicate fields of the current object and Employee. to indicate static fields of the current class.

How does the compiler know to use fload/fstore with raise, getfield/putfield with salary, and getstatic/putstatic with payroll? It is necessary to deduce this from context during the compilation process.

When an identifier is used in an expression, the compiler looks for it in three places.

  • First, it checks to see if there is a local variable by that name. If so, the name is treated as a local variable.

  • Failing that, it checks to see if there is a nonstatic field with that name in the current class. If so, the target of the assignment or value of the expression is the field with that name in the object this. (This step applies only if the method being compiled is itself nonstatic, since static methods do not have a this variable.)

  • Finally, it checks to see if there is a static field with that name in the current class. If so, that static field is used.

If nothing is found after the compiler has looked in all three places, the compiler gives up and signals an error.

10.5.2 Other Assignments

The Java language has several other assignment expressions, for example,


which means the same thing as

 i = i + 1 

whether i is a local variable, an object field, or a static field.

Table 10.5 lists other assignment expressions. They work exactly as shown when x names a local variable or static field. When x is of the form expression.field, the meaning is slightly more complex. The expression to compute the object should be evaluated only once, not twice. This prevents any side effect from occurring more than once. Consider a method like this:

Table 10.5. Other assignment expressions
Expression Meaning
x++ x = x + 1
x-- x = x - 1
x += expression x = x + expression
x -= expression x = x - expression
x *= expression x = x * expression
x /= expression x = x / expression
x %= expression x = x % expression
x &= expression x = x & expression
x |= expression x = x ^ expression
x ^= expression x = x | expression

 Employee print_employee() {    System.out.println(this);    return this; } 

This method has the side effect of printing some information about the employee before returning the Employee itself. Suppose joe is an employee in this computation:

 joe.print_employee().salary *= 1.15 

The intention is that print_employee prints some debugging information about joe, then returns joe so that he can receive a raise. The compiler must not be naive and treat this expression as though it were equivalent to

 joe.print_employee().salary = joe.print_employee().salary * 1.15 

The new salary is correct, but the console will print the information about joe twice, not once. A better translation is

 Employee temporary = print_employee(joe); temporary.salary = temporary.salary * 1.15 

which has the intended effect, as long as temporary is some new variable name not used anywhere else in the method.

10.5.3 Assignments as Expressions

An assignment is actually an expression. The value of an expression is the value of the right-hand side. Consider this statement:

 x = y = z = 0; 

This statement is composed from three assignment expressions, grouped from the right:

 x = (y = (z = 0)); 

To compile this code, the compiler starts with the rightmost expression. The right-hand side is the expression 0, a constant expression. It is assigned to z, which alters the state of z and has the result 0. The result is used in the next expression, which assigns the result to y and then again to z. This statement can be translated into Oolong using dup instructions before the assignment:

 .var 1 is x I .var 2 is y I .var 3 is z I iconst_0                  ; Evaluate the right-most expression dup                       ; Dup it istore_3                  ; Store one copy into z ; The original expression result remains on the stack, and we ; can assign it to y dup                       ; Dup it again istore_2                  ; Store one copy in y ; We are in the same position as before, except now with x dup                       ; Dup yet again istore_1                  ; Store one copy in x ; To turn the expression into a statement, we must remove the ; value on the stack to ensure that the stack is empty pop 

Usually, the final dup and pop are eliminated for efficiency reasons. Examples in previous sections are shown with the dup and pop eliminated for clarity.

10.5.4 Type-Checking in Assignments

In an assignment, the type of the expression on the right-hand side must conform to the type of the target on the left-hand side, independent of whether the target is a variable, a static field, or an object field. For example,

 int x; x = 0; 

The expression 0 is of type int, which conforms to the type of x, which is also int.

When the type is an object type rather than a numeric type, the type checking must check that the compile-time type of the right-hand side is a subtype of the compile-time type of the left-hand side. This involves checking subtyping relationships if the two classes are not the same. This example shows some legal assignments:

 class Employee { } class Supervisor extends Employee { } Object object; Employee employee1, employee2; Supervisor supervisor; object = employee1;            // OK employee2 = supervisor;        // OK object = supervisor;                // OK employee1 = employee2;              // OK 

Since the type of the right-hand side is always a subtype of the type of the variable on the left-hand side, those assignments are legal. Reversing these assignments:

 employee1 = object;            // ERROR! supervisor = employee2;        // ERROR! supervisor = object;           // ERROR! employee2 = employee1;         // OK 

The last case is okay because both have the same type. The other cases are errors since the Object is not necessarily an Employee, and an Employee is not necessarily a Supervisor.

Sometimes, the assignment can't work either way:

 String greeting = "Hello, world"; employee1 = greeting;     // ERROR! Employee is not a String greeting = employee1;     // ERROR! String is not an Employer 

The compiler must check all assignments before generating compiled code. If any of the types do not conform, then the program must be rejected.

Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

Similar book on Amazon

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