Полезная информация

Java Language Reference

Previous Chapter 4 Next
 

4. Expressions

Contents:

Select a section...

..then directly to it.

Expressions in Java are used to fetch, compute, and store values. To fetch a value, you use a type of expression called a primary expression. To compute and store values, you use the various operators described in this chapter. In Java, expressions are most often used in methods and constructors; they can also appear in field variable initializers and static initializers.

Most expressions, when they are evaluated, produce values that can be used by other expressions. The one exception is an expression that calls a method declared with the void return type. An expression that invokes a void method cannot be embedded in another expression. The evaluation of an expression can also produce side effects, such as variable assignments and increment and decrement operations.

The value produced by an expression can be either a pure value or a variable or array element. The distinction is that a pure value cannot be used to store a value, while a variable or array element can.[1] An expression that produces a variable or an array element can be used anywhere that an expression that produces a pure value can be used.

[1] Note that Java's distinction between pure values and variable and array elements is similar to the distinction in C and C++ between rvalues and lvalues.

This chapter refers to values as being either pure values or variables. Saying that a value is a pure value means that it is a value such as 24 that contains information, but cannot be used as the target of an assignment expression. Saying that a value is a variable means that it is something like var or ar[i] that can be used as the target of an assignment. The generic term "value" is used to mean either a variable or a pure value.

The formal definition of an expression is:

[Graphic: Figure from the text]

The above diagram may seem deceptively simple; why is such a definition even necessary? Expressions in Java are defined with a number of mutually recursive railroad diagrams. You can think of the Expression definition as being both the lowest-level definition and the highest-level definition of these mutually recursive diagrams. In other words, a=b[i]+c[i] is an expression, as are b[i], c[i], a, b, c, and i. This first diagram defines an expression to be an AssignmentExpression, which is the final definition used to describe Java expressions.

References Assignment Operators

4.1 Primary Expressions

A primary expression is the most elementary type of expression. Primary expressions are constructs that fetch or create values, but do not directly perform computations on them:

[Graphic: Figure from the text]

Terms are those primary expressions that produce values, by doing such things as accessing fields in classes, invoking methods, and accessing array elements:

[Graphic: Figure from the text]

References Allocation Expressions; Expression 4; FieldExpression 4.1.6; Identifiers; Index Expressions; Literals; Method Call Expression; Class Literals

this

The keyword this can be used only in the body of a constructor or an instance method (i.e., a method that is not declared static), or in the initializer for an instance variable. A static method is not associated with an object, so this makes no sense in such a method and is treated as an undefined variable. If this is used in an inappropriate place, a compile-time error occurs.

The value produced by this is a reference to the object associated with the expression that is being evaluated. The type of the primary expression this is a reference to the class in which it appears.

One common for this is to allow access to a field variable when there is a local variable with the same name. For example:

int foo;
void setFoo(int foo) {
    this.foo = foo;
}

Another common usage is to implement a callback mechanism. Passing this to another object allows that object to call methods in the object associated with the calling code. For example, to allow an object inside of an applet to be able to access parameters passed to the applet in the HTML <applet> tag, you can use code like the following:

public class MyApplet extends Applet {
    ...
    Foo foo;
    public void init() {
        foo = new Foo(this);
    ...
    }
}
class Foo {
    Applet app;
    ...
    Foo(Applet app) {
        this.app = app;
    }
    ...
    void doIt() {
        String dir = app.getParameter("direction");
        ...
    }
    ...
}

Another use for the keyword this is in a special kind of FieldExpression that refers to an enclosing instance of this object. A reference to an enclosing instance is written as the class name of the enclosing instance followed by a dot and the keyword this. Consider the following code:

public class ImageButton extends Canvas {
    ...
    private class MyImage extends Image {
        Image fileImage;
        MyImage(String fileName) throws IOException {
            URL url = new URL(fileName);
            ImageProducer src = (ImageProducer)url.getContent();
            Image fileImage = createImage(src);
            prepareImage(this, ImageButton.this);
        } 
    ...

The call to prepareImage() takes two arguments. The first argument is a reference to this instance of the MyImage class. The second argument is a reference to this object's enclosing instance, which is an instance of the ImageButton class.

References Constructors; Constructor implementation; FieldExpression 4.1.6; Inner Classes; Methods

super

The keyword super can be used only in the body of a constructor or an instance method (i.e., a method that is not declared static), or in the initializer for an instance variable. In addition, super cannot appear in the class Object because Object has no superclass. If super is used in an inappropriate place, a compile-time error occurs.

In most cases, the primary expression super has a value that is equivalent to casting the value of the primary expression this to the superclass of the class in which it appears. In other words, super causes the object to be treated as an instance of its superclass. The type of the primary expression super is a reference to the superclass of the class in which it appears.

There are two situations in which super produces a result that is different than what would be produced by casting this to its superclass:

The main purpose of super is to allow the behavior of methods and constructors to be extended, rather than having to be totally replaced. Consider the following example:

class A {
    public int foo(int x) {
        return x*x;
    }
    public int bar(int x) {
        return x*8;
    }
}
class B extends A{
    public int foo(int x) {
        return super.foo(x)+x;
    }
    public int bar(int x){
        return x*5;
    }
}

The foo() method in class B extends the behavior of the foo() method in class A by calling that method and performing further computations on its result. On the other hand, the bar() method in class B totally replaces the behavior of the bar() method in class A.

References Constructors; Constructor implementation; Methods

null

The primary expression null produces a special object reference value that does not refer to any object, but is assignment-compatible with all object reference types.

An operation on an object reference that does not attempt to access the referenced object works the same way for null as it does for other object reference values. For example:

foo == null

However, any operation that attempts to access an object through a null reference throws a NullPointerException. The one exception to this rule is the string concatenation operator (+), which converts a null operand to the string literal "null".

References Runtime exceptions; String Concatenation Operator +

Literal Expressions

A primary expression that consists of a literal produces a pure value. The data type of this pure value is the data type of the literal.

References Literals

Parenthetical Expressions

A primary expression that consists of a parenthesized expression produces the same pure value as the expression in parentheses. The data type of this pure value is also the same as the data type of the expression in parentheses. A parenthetical expression can only produce a pure value. Thus, the following code produces an error:

(x) = 5;          // Illegal

References Expression 4

Field Expressions

A field expression is a primary expression that fetches such things as local variables, formal parameters, and field variables. A field expression can evaluate to a pure value or a variable. The data type of a field expression is the data type of the pure value, variable, or array element produced by the expression:

[Graphic: Figure from the text]

[Graphic: Figure from the text]

Essentially, a field expression can be a simple identifier, a primary expression followed by an identifier, or a class or interface name followed by an identifier. Here's an example of a field expression that consists of a simple Identifier :

myVar

Before the Java compiler can decide what to do with a lone identifier such as this, it must first match it with a declaration. The compiler first looks in the method where the identifier appears for a local variable or formal parameter with the same name as the identifier. If the compiler finds a matching local variable or formal parameter, the field expression produces the matching variable or parameter.

If the identifier does not match a local variable or a formal parameter, it is expected to match the name of a field variable in the class in which it occurs. If the matching variable is declared final, the field expression produces the pure value specified by the variable's initializer. Otherwise, the field expression produces the matching field variable. If the method that the identifier appears in is declared static, the matching variable must also be declared static or the compiler declares an error.

A lone identifier that matches the name of a field variable is equivalent to:

this.Identifier

This form of a field expression can be used to access a field variable that is shadowed by a local variable or a formal parameter. For example:

class Shadow {
    int value;
    Shadow(int value) {
        this.value=value;
    }
}

In the above example, the identifier value is used as both the name of a field variable and the name of a formal parameter in the constructor. Within the constructor, the unqualified identifier value refers to the formal parameter, not to the field variable. In order to access the field variable, you have to qualify value with this.

In addition to allowing an object to refer to itself, the keyword this has another use in field expressions. The construct ClassOrInterfaceName.this identifies the enclosing instance of an object that is an instance of an inner class.[2] Consider the following example:

[2] Since this construct fetches an object reference, you might expect it to be a primary expression. However, due to the way in which inner classes are implemented, this construct is actually a field expression.

public class ImageButton extends Canvas {
    ...
    private class MyImage extends Image {
        Image fileImage;
        MyImage(String fileName) throws IOException {
            URL url = new URL(fileName);
            ImageProducer src = (ImageProducer)url.getContent();
            Image fileImage = createImage(src);
            prepareImage(this, ImageButton.this);
        } 
    ...

The call to prepareImage() takes two arguments. The first argument is a reference to this instance of the MyImage class. The second argument is a reference to this object's enclosing instance, which is an instance of the ImageButton class.

Here are some examples of field expressions that consist of a PrimaryExpression and an Identifier:

this.myVar
size().height
(new Foo()).bar

A primary expression that appears at the beginning of a field expression must produce a reference to an object. The identifier to the right of the dot must be the name of a field variable in the object referred to by the primary expression. If, at runtime, the primary expression in a field expression produces the value null, a NullPointerException is thrown.

Here's an example of a field expression that consists of a ClassOrInterfaceName and an Identifier:

Double.POSITIVE_INFINITY

A field expression that begins with ClassOrInterfaceName produces a field variable of the specified class. If the field variable is not declared static, the specified class must either be the same class in which the field expression appears or a superclass of that class. Such a field expression is approximately equivalent to:

((ClassOrInterfaceName)this).Identifier

If ClassOrInterfaceName specifies a class or interface defined in a different package than the package in which the field expression appears, the class name must be qualified by the package in which the class is defined. For example:

java.awt.Event.MOUSE_UP

However, if an import statement imports the specified class, the package name is not necessary.

ClassOrInterfaceName can refer to an inner class or a nested top-level class or interface by qualifying the name of the class or interface with the name of the enclosing class. For example, consider the following declaration:

public class A {
    public class B {
    } 
}

Based on this declaration, you can create a new instance of B as follows:

new A.B()

Most field expressions produce variables when they are evaluated. This means that the field expression can be used as the left operand of an assignment operator. A field expression produces a pure value, rather than a variable, if the identifier at the end of the field expression is a field variable that is declared final. Such a field expression returns a pure value because the value of a final variable cannot be modified. A field expression that produces a pure value cannot be the left operand of an assignment operator, or the operand of the ++ or - - operators. Here's an erroneous example:

final int four=4
four++

This is equivalent to:

4++

As such, it causes the Java compiler to issue an error message.

When the Java compiler detects an expression that uses the value of a local variable that may not have been initialized, it issues an error message. For example:

{
    int x;
    if (testForSomething())
        x = 4;
    System.out.println(x);    // Compiler complains
}

The compiler complains about the use of x in the println() method because x may not have been given an explicit value when the program reaches that statement. Even though there is an assignment to x in the preceding statement, the compiler recognizes that the assignment may not have been performed, since it is enclosed within a conditional statement. The Java language specification requires that a compiler issue an error message when it detects an uninitialized local variable.

References Identifiers; Inheritance; Inner Classes; Interface Variables; Local Variables; Packages; Primary Expressions; Runtime exceptions; Variables

Index Expressions

An index expression is a primary expression that produces an array element when it is evaluated. The value produced by an index expression is a variable; it can be used as the left operand of an assignment expression. The data type of an index expression is the data type of the array element produced by the expression:

[Graphic: Figure from the text]

When the compiler evaluates an index expression, the term to the left of the square brackets is evaluated before the expression inside of the square brackets. If the term produces the value null, a NullPointerException is thrown.

Array indexing uses an int-valued expression between the square brackets. If the type of the expression is byte, char, or short, the value is automatically converted to int. An array index cannot be of type long. The value of the array index must be in the range zero to one less than the length of the array. An array object detects an out-of-range index value and throws an ArrayIndexOutOfBoundsException.

Because of the precedence of Java expressions, an array allocation expression can only be indexed when the expression is enclosed in parentheses. For example:

(new int[6])[3]

This expression refers to the fourth element of the newly created array. Leaving out the parentheses results in the following:

new int[6][3]

This is not an index expression, but an array allocation expression that allocates an array of 3 arrays of 6 integers.

References Array Allocation Expressions; Array Types; Expression 4; Term 4.1; Exceptions

Method Call Expression

A method call expression is a primary expression that invokes a method:

[Graphic: Figure from the text]

[Graphic: Figure from the text]

A method call expression produces the pure value returned by the method; the type of this value is specified by the return type in the method declaration. If the method has the return type void, however, the expression does not produce a value.

The PrimaryExpression, if present, is evaluated first. The expressions provided as method arguments are then evaluated from left to right. Finally, the method is invoked.

When a method call is made to a method that is not static, the call is made through an object reference:

When a method call is made to a static method, the call is made through a class or interface type:

The rules for supplying actual values for the formal parameters of a method are similar to the rules for assignment. A particular value can be specified as the actual value of a formal parameter if and only if it is assignment-compatible with the type of the formal parameter. You can use a type cast to make a value assignment compatible with a formal parameter.

The process that the Java compiler uses to select the actual method that will be invoked at runtime is rather involved. The compiler begins by finding any methods that have the specified name. If the method call has been made through an object reference, the compiler searches in the class of that object reference. If the call has been made through a specific class or interface name, the compiler searches in that class or interface. The compiler searches all of the methods defined in the particular class or interface, as well as any methods that are inherited from superclasses or super-interfaces. At this point, the compiler is searching for both static and non-static methods, since it does not know for certain which type of method is being called.

If the compiler finds more than one method, that means the method is overloaded. Consider this example:

public class MyMath {
    public int square(int x) { return x*x; }
    public long square(long x) { return x*x; }
    public float square(float x) { return x*x; }
    public double square(double x) { return x*x; }
    public double hypotenuse(double x, double y) {
        return Math.sqrt(x*x + y*y);
    }
}

In the above example, the square() method is overloaded, while hypotenuse() is not.

If the method is overloaded, the compiler then determines which of the methods has formal parameters that are compatible with the given arguments. If more than one method is compatible with the given arguments, the method that most closely matches the given parameters is selected. If the compiler cannot select one of the methods as a better match than the others, the method selection process fails and the compiler issues an error message. Note that the return types of overloaded methods play no part in selecting which method is to be invoked.

After the compiler successfully selects the method that most closely matches the specified arguments, it knows the name and signature of the method that will be invoked at runtime. It does not, however, know for certain what class that method will come from. Although the compiler may have selected a method from MyMath, it is possible that a subclass of MyMath could define a method that has the same name and the same number and types of parameters as the selected method. In this case, the method in the subclass overrides the method in MyMath. The compiler cannot know about overriding methods, so it generates runtime code that dynamically selects the appropriate method.

Here are the details of the three-step method selection process:

Step One

The method definitions are searched for methods that, taken in isolation, could be called by the method call expression. If the method call expression uses an object reference, the search takes place in the class of that object reference. If the expression uses a specific class or interface name, the search takes place in that class or interface. The search includes all of the methods defined in the particular class or interface, as well as any methods inherited from superclasses or super-interfaces. The search also includes both static and non-static methods.

A method is a candidate if it meets the following criteria:

  • The name of the method is the same as the name specified in the method call expression.

  • The method is accessible to the method call expression, based on any access modifiers specified in the method's declaration.

  • The number of formal parameters declared for the method is the same as the number of actual arguments provided in the method call expression.

  • The data type of each actual parameter is assignment-compatible with the corresponding formal parameter.

Consider the following expression that calls a method defined in the preceding example:

MyMath m;
m.square(3.4F)

Here is how the Java compiler uses the above criteria to decide which method the expression actually calls:

  • The name square matches four methods defined in the MyMath class, so the compiler must decide which one of those methods to invoke.

  • All four methods are declared public, so they are all accessible to the above expression and are thus all still viable candidates.

  • The method call expression provides one argument. Since the four methods under consideration each take one argument, there are still four possible choices.

  • The method call expression is passing a float argument. Because a float value cannot be assigned to an int or a long variable, the compiler can eliminate the versions of square() that take these types of arguments. That still leaves two possible methods for the above expression: the version of square() that takes a float argument and the one that takes a double argument.

Step Two

If more than one method meets the criteria in Step One, the compiler tries to determine if one method is a more specific match than the others. If there is no method that matches more specifically, the selection process fails and the compiler issues an error message.

Given two methods, A() and B(), that are both candidates to be invoked by the same method call expression, A() is more specific than B() if:

  • The class in which the method A() is declared is the same class or a subclass of the class in which the method B() is declared.

  • Each parameter of A() is assignment-compatible with the corresponding parameter of B().

Let's go back to our previous example. We concluded by narrowing the possible methods that the expression m.square(3.4F) might match to the methods in MyMath named square() that take either a float or a double argument. Using the criteria of this step, we can further narrow the possibilities. These methods are declared in the same class, but the version of square() that takes a float value is more specific than the one that takes a double value. It is more specific because a float value can be assigned to a double variable, but a double value cannot be assigned to a float variable without a type cast.

There are some cases in which it is not possible to choose one method that is more specific than others. When this happens, the Java compiler treats the situation as an error and issues an appropriate error message.

For example, consider a situation where the compiler needs to choose between two methods declared as follows:

double foo(float x, double y)
double foo(double x, float y)

Neither method is more specific than the other. The first method is not more specific because the type of its second parameter is double and double values cannot be assigned to float variables. The second method is not more specific because of a similar problem with its first parameter.

Step Three

After successfully completing the previous two steps, the Java compiler knows that the expression in our example will call a method named square() and that the method will take one float argument. However, the compiler does not know if the method called at runtime will be the one defined in the MyMath class. It is possible that a subclass of MyMath could define a method that is also called square() and takes a single float argument. This method in a subclass would override the method in MyMath. If the variable m in the expression m.square(3.4F) refers to such a subclass, the method defined in the subclass is called instead of the one defined in MyMath.

The Java compiler generates code to determine at runtime which method named square() that takes a single float argument it should call. The Java compiler must always generate such runtime code for method call expressions, unless it is able to determine at compile time the exact method to be invoked at runtime.

There are four cases in which the compiler can know exactly which method is to be called at runtime:

  • The method is called through an object reference, and the type of the reference is a final class. Since the type of the reference is a final class, Java does not allow any subclasses of that class to be defined. Therefore, the object reference will always refer to an object of the class declared final. The Java compiler knows the actual class that the reference refers to, so it can know the actual method to be called at runtime.

  • The method is invoked through an object reference, and the type of the reference is a class that defines or inherits a final method that has the method name, number of parameters, and types of parameters determined by the preceding steps. In this case, the compiler knows the actual method to be called at runtime because final methods cannot be overridden.

  • The method is a static method. When a method is declared static, it is also implicitly declared final. Thus, the compiler can be sure that the method to be called at runtime is the one defined in or inherited by the specified class that has the method name, number of parameters, and types of parameters determined by the preceding steps.

  • The compiler is able to deduce that a method is invoked through an object reference that will always refer to the same class of object at runtime. One way the compiler might deduce this is through data flow analysis.

If none of the above cases applies to a method call expression, the Java compiler must generate runtime code to determine the actual method to be invoked. The runtime selection process begins by getting the class of the object through which the method is being invoked. This class is searched for a method that has the same name and the same number and types of parameters as the method selected in Step Two. If this class does not contain such a definition, its immediate superclass is searched. If the immediate superclass does not contain an appropriate definition, its superclass are searched, and so on up the inheritance hierarchy. This search process is called dynamic method lookup.

Dynamic method lookup always begins with the class of the actual object being referenced. The type of the reference being used to access the object does not influence where the search for a method begins. The one exception to this rule occurs when the keyword super is used as part of the method call expression. The form of this type of method call expression is:

super.Identifier(...)

In this case, dynamic method lookup begins by searching the superclass of the class that the calling code appears in.

Now that we've gone through the entire method selection process, let's consider an example that illustrates the process:

class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class W {
    void foo(D d) {System.out.println("C");}
}
class X extends W {
    void foo(A a) {System.out.println("A");}
    void foo(B b) {System.out.println("X.B");}
}
class Y extends X {
    void foo(B b) {System.out.println("Y.B");}
}
class Z extends Y {
    void foo(C c) {System.out.println("D");}
}
public class CallSelection {
    public static void main(String [] argv) {
        Z z = new Z();
        ((X) z).foo(new C());
    }
}

In the class CallSelection, the method main() contains a call to a method named foo(). This method is called through an object reference. Although the object refers to an instance of the class Z, it is treated as an instance of the class X because the reference is type cast to the class X. The process of selecting which method to call proceeds as follows:

  1. The compiler finds all of the methods named foo() that are accessible through an object of class X: foo(A), foo(B), and foo(D). However, because a reference to an object of class C cannot be assigned to a variable of class D, foo(D) is not a candidate to be invoked by the method call expression.

  2. Now the compiler must choose one of the two remaining foo() methods as more specific than the other. Both methods are defined in the same class, but foo(B) is more specific than foo(A) because a reference to an object of class B can be assigned to a variable declared with a type of class A.

  3. At runtime, the dynamic method lookup process finds that it has a reference to an object of class Z. The fact that the reference is cast to class X is not significant, since dynamic lookup is concerned with the class of an object, not the type of the reference used to access the object. The definition of class Z is searched for a method named foo() that takes one parameter that is a reference to an object of class B. No such method is found in the definition of class Z, so its immediate superclass, class Y, is searched. Such a method is found in class Y, so that method is invoked.

Here is another example that shows some ambiguous and erroneous method call expressions:

class A {}
class B extends A {}
class AmbiguousCall {
    void foo(B b, double x){}
    void foo(A a, int i){}
    void doit() {
        foo(new A(), 8);   // Matches foo(A, int)
        foo(new A(), 8.0); // Error: doesn't match anything
        foo(new B(), 8);   // Error: ambiguous, matches both
        foo(new B(), 8.0); // Matches foo(B, double)
    }
}

References Assignment Compatibility; ClassOrInterfaceName 4.1.6; Casts; Expression 4; Identifiers; Inheritance; Interface Methods; Methods; Primary Expressions

Class Literals

A class literal is an expression that produces a reference to a Class object that identifies a specified data type. Class literals are not supported prior to Java 1.1. The syntax for a class literal is:

[Graphic: Figure from the text]

If the type in a class literal is a reference type, the class literal produces a reference to the Class object that defines the specified reference type. Here are some examples of this type of class literal:

String.class
java.util.Stack.class
myNewClass.class

Such a class literal can throw a NoClassDefFoundError if the specified class is not available.

You can also call Class.forName() with the name of a specified reference type to retrieve the Class object for that type. For example:

Class.forName("java.util.Stack")

A class literal and a call to Class.forName() for the same reference type return the same Class object. There are certain situations where it makes sense to use a class literal, while in other cases a call to Class.forName() is more appropriate. Here are the differences between the two techniques for retrieving a Class object:

If the type in a class literal is void or a primitive type, the class literal produces a reference to a unique Class object that represents void or the specified type. The special Class object that represents void or a primitive type can be distinguished from a Class object that represents a reference type by calling its isPrimitive() method. This method only returns true if the Class object represents void or a primitive type. The getName() method of a special Class object returns a string that contains the name of the primitive type represented by the object. The easiest way to determine the primitive type of a special Class object is to compare it to the TYPE variables of the primitive wrapper classes. The following comparisons always produce true:

boolean.class == Boolean.TYPE
byte.class == Byte.TYPE
short.class == Short.TYPE
int.class == Integer.TYPE
long.class == Long.TYPE
char.class == Character.TYPE
float.class == Float.TYPE
double.class == Double.TYPE
void.class == Void.TYPE

References Boolean; Byte; Character; Class; Double; Errors; Float; Inner Classes; Integer; Long; Short; Void; Type 3


Previous Library Home Next
Reference Types Book Index Allocation Expressions