| Contents | Prev | Next | Index | Java Language Specification Third Edition |
CHAPTER 15
Much of the work in a program is done by evaluating expressions, either for their side effects, such as assignments to variables, or for their values, which can be used as arguments or operands in larger expressions, or to affect the execution sequence in statements, or both.
This chapter specifies the meanings of expressions and the rules for their evaluation.
An expression denotes nothing if and only if it is a method invocation (§15.12) that invokes a method that does not return a value, that is, a method declared void (§8.4). Such an expression can be used only as an expression statement (§14.8), because every other context in which an expression can appear requires the expression to denote something. An expression statement that is a method invocation may also invoke a method that produces a result; in this case the value returned by the method is quietly discarded.
Value set conversion (§5.1.13) is applied to the result of every expression that produces a value.
Each expression occurs in either:
If the value of a variable of type float or double is used in this manner, then value set conversion (§5.1.13) is applied to the value of the variable.
The value of an expression is assignment compatible (§5.2) with the type of the expression, unless heap pollution (§4.12.2.1) occurs. Likewise the value stored in a variable is always compatible with the type of the variable, unless heap pollution occurs. In other words, the value of an expression whose type is T is always suitable for assignment to a variable of type T.
Note that an expression whose type is a class type F that is declared final is guaranteed to have a value that is either a null reference or an object whose class is F itself, because final types have no subclasses.
float or double, then there is a question as to what value set (§4.2.3) the value of the expression is drawn from. This is governed by the rules of value set conversion (§5.1.13); these rules in turn depend on whether or not the expression is FP-strict.
Every compile-time constant expression (§15.28) is FP-strict. If an expression is not a compile-time constant expression, then consider all the class declarations, interface declarations, and method declarations that contain the expression. If any such declaration bears the strictfp modifier, then the expression is FP-strict.
If a class, interface, or method, X, is declared strictfp, then X and any class, interface, method, constructor, instance initializer, static initializer or variable initializer within X is said to be FP-strict. Note that an annotation (§9.7) element value (§9.6) is always FP-strict, because it is always a compile-time constant (§15.28).
It follows that an expression is not FP-strict if and only if it is not a compile-time constant expression and it does not appear within any declaration that has the strictfp modifier.
Within an FP-strict expression, all intermediate values must be elements of the float value set or the double value set, implying that the results of all FP-strict expressions must be those predicted by IEEE 754 arithmetic on operands represented using single and double formats. Within an expression that is not FP-strict, some leeway is granted for an implementation to use an extended exponent range to represent intermediate results; the net effect, roughly speaking, is that a calculation might produce "the correct answer" in situations where exclusive use of the float value set or double value set might result in overflow or underflow.
null, is not necessarily known at compile time. There are a few places in the Java programming language where the actual class of a referenced object affects program execution in a manner that cannot be deduced from the type of the expression. They are as follows:
o.m(...) is chosen based on the methods that are part of the class or interface that is the type of o. For instance methods, the class of the object referenced by the run-time value of o participates because a subclass may override a specific method already declared in a parent class so that this overriding method is invoked. (The overriding method may or may not choose to further invoke the original overridden m method.)
instanceof operator (§15.20.2). An expression whose type is a reference type may be tested using instanceof to find out whether the class of the object referenced by the run-time value of the expression is assignment compatible (§5.2) with some other reference type.
[] to be treated as a subtype of T[] if S is a subtype of T, but this requires a run-time check for assignment to an array component, similar to the check performed for a cast.
catch clause only if the class of the thrown exception object is an instanceof the type of the formal parameter of the catch clause.
In addition, there are situations where the statically known type may not be accurate at run-time. Such situations can arise in a program that gives rise to unchecked warnings. Such warnings are given in response to operations that cannot be statically guaranteed to be safe, and cannot immediately be subjected to dynamic checking because they involve non-reifiable (§4.7) types. As a result, dynamic checks later in the course of program execution may detect inconsistencies and result in run-time type errors.
A run-time type error can occur only in these situations:
ClassCastException is thrown.
ArrayStoreException is thrown.
catch handler (§11.3); in this case the thread of control that encountered the exception first invokes the method uncaughtException for its thread group and then terminates.
If, however, evaluation of an expression throws an exception, then the expression is said to complete abruptly. An abrupt completion always has an associated reason, which is always a throw with a given value.
Run-time exceptions are thrown by the predefined operators as follows:
OutOfMemoryError if there is insufficient memory available.
NegativeArraySizeException if the value of any dimension expression is less than zero (§15.10).
NullPointerException if the value of the object reference expression is null.
NullPointerException if the target reference is null.
NullPointerException if the value of the array reference expression is null.
ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
ClassCastException if a cast is found to be impermissible at run time.
ArithmeticException if the value of the right-hand operand expression is zero.
OutOfMemoryError as a result of boxing conversion (§5.1.7).
ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
If an exception occurs, then evaluation of one or more expressions may be terminated before all steps of their normal mode of evaluation are complete; such expressions are said to complete abruptly. The terms "complete normally" and "complete abruptly" are also applied to the execution of statements (§14.1). A statement may complete abruptly for a variety of reasons, not just because an exception is thrown.
If evaluation of an expression requires evaluation of a subexpression, abrupt completion of the subexpression always causes the immediate abrupt completion of the expression itself, with the same reason, and all succeeding steps in the normal mode of evaluation are not performed.
It is recommended that code not rely crucially on this specification. Code is usually clearer when each expression contains at most one side effect, as its outermost operation, and when code does not depend on exactly which exception arises as a consequence of the left-to-right evaluation of expressions.
Thus:
class Test {
public static void main(String[] args) {
int i = 2;
int j = (i=3) * i;
System.out.println(j);
}
}
prints:
It is not permitted for it to print9
6 instead of 9.If the operator is a compound-assignment operator (§15.26.2), then evaluation of the left-hand operand includes both remembering the variable that the left-hand operand denotes and fetching and saving that variable's value for use in the implied combining operation. So, for example, the test program:
class Test {
public static void main(String[] args) {
int a = 9;
a += (a = 3); // first example
System.out.println(a);
int b = 9;
b = b + (b = 3); // second example
System.out.println(b);
}
}
prints:
because the two assignment statements both fetch and remember the value of the left-hand operand, which is12 12
9, before the right-hand operand of the addition is evaluated, thereby setting the variable to 3. It is not permitted for either example to produce the result 6. Note that both of these examples have unspecified behavior in C, according to the ANSI/ISO standard.If evaluation of the left-hand operand of a binary operator completes abruptly, no part of the right-hand operand appears to have been evaluated.
Thus, the test program:
class Test {
public static void main(String[] args) {
int j = 1;
try {
int i = forgetIt() / (j = 2);
} catch (Exception e) {
System.out.println(e);
System.out.println("Now j = " + j);
}
}
static int forgetIt() throws Exception {
throw new Exception("I'm outta here!");
}
}
prints:
That is, the left-hand operandjava.lang.Exception: I'm outta here! Now j = 1
forgetIt() of the operator / throws an exception before the right-hand operand is evaluated and its embedded assignment of 2 to j occurs.&&, ||, and ? :) appears to be fully evaluated before any part of the operation itself is performed.
If the binary operator is an integer division / (§15.17.2) or integer remainder % (§15.17.3), then its execution may raise an ArithmeticException, but this exception is thrown only after both operands of the binary operator have been evaluated and only if these evaluations completed normally.
So, for example, the program:
class Test {
public static void main(String[] args) {
int divisor = 0;
try {
int i = 1 / (divisor * loseBig());
} catch (Exception e) {
System.out.println(e);
}
}
static int loseBig() throws Exception {
throw new Exception("Shuffle off to Buffalo!");
}
}
always prints:
and not:java.lang.Exception: Shuffle off to Buffalo!
since no part of the division operation, including signaling of a divide-by-zero exception, may appear to occur before the invocation ofjava.lang.ArithmeticException: / by zero
loseBig completes, even though the implementation may be able to detect or infer that the division operation would certainly result in a divide-by-zero exception.
In the case of floating-point calculations, this rule applies also for infinity and not-a-number (NaN) values. For example, !(x<y) may not be rewritten as x>=y, because these expressions have different values if either x or y is NaN or both are NaN.
Specifically, floating-point calculations that appear to be mathematically associative are unlikely to be computationally associative. Such computations must not be naively reordered.
For example, it is not correct for a Java compiler to rewrite 4.0*x*0.5 as 2.0*x; while roundoff happens not to be an issue here, there are large values of x for which the first expression produces infinity (because of overflow) but the second expression produces a finite result.
So, for example, the test program:
strictfp class Test {
public static void main(String[] args) {
double d = 8e+307;
System.out.println(4.0 * d * 0.5);
System.out.println(2.0 * d);
}
}
prints:
because the first expression overflows and the second does not.Infinity 1.6e+308
In contrast, integer addition and multiplication are provably associative in the Java programming language.
For example a+b+c, where a, b, and c are local variables (this simplifying assumption avoids issues involving multiple threads and volatile variables), will always produce the same answer whether evaluated as (a+b)+c or a+(b+c); if the expression b+c occurs nearby in the code, a smart compiler may be able to use this common subexpression.
Thus:
class Test {
public static void main(String[] args) {
String s = "going, ";
print3(s, s, s = "gone");
}
static void print3(String a, String b, String c) {
System.out.println(a + b + c);
}
}
always prints:
because the assignment of the stringgoing, going, gone
"gone" to s occurs after the first two arguments to print3 have been evaluated.If evaluation of an argument expression completes abruptly, no part of any argument expression to its right appears to have been evaluated.
Thus, the example:
class Test {
static int id;
public static void main(String[] args) {
try {
test(id = 1, oops(), id = 3);
} catch (Exception e) {
System.out.println(e + ", id=" + id);
}
}
static int oops() throws Exception {
throw new Exception("oops");
}
static int test(int a, int b, int c) {
return a + b + c;
}
}
prints:
because the assignment ofjava.lang.Exception: oops, id=1
3 to id is not executed.
Primary:
PrimaryNoNewArray
ArrayCreationExpression
PrimaryNoNewArray:
Literal
Type . class
void . class
this
ClassName.this
( Expression )
ClassInstanceCreationExpression
FieldAccess
MethodInvocation
ArrayAccess
The following production from §3.10 is repeated here for convenience:
Literal:
IntegerLiteral
FloatingPointLiteral
BooleanLiteral
CharacterLiteral
StringLiteral
NullLiteral
The type of a literal is determined as follows:
L or l is long; the type of any other integer literal is int.
F or f is float and its value must be an element of the float value set (§4.2.3). The type of any other floating-point literal is double and its value must be an element of the double value set.
boolean.
char.
String.
null is the null type; its value is the null reference.
void, followed by a `.' and the token class. The type of a class literal, C.Class, where C is the name of a class, interface or array type, is Class<C>. If p is the name of a primitive type, let B be the type of an expression of type p after boxing conversion (§5.1.7). Then the type of p.class is Class<B>. The type of void.class is Class<Void>.
A class literal evaluates to the Class object for the named type (or for void) as defined by the defining class loader of the class of the current instance.
It is a compile time error if any of the following occur:
this may be used only in the body of an instance method, instance initializer or constructor, or in the initializer of an instance variable of a class. If it appears anywhere else, a compile-time error occurs.
When used as a primary expression, the keyword this denotes a value that is a reference to the object for which the instance method was invoked (§15.12), or to the object being constructed. The type of this is the class C within which the keyword this occurs. At run time, the class of the actual object referred to may be the class C or any subclass of C.
In the example:
class IntVector {
int[] v;
boolean equals(IntVector other) {
if (this == other)
return true;
if (v.length != other.v.length)
return false;
for (int i = 0; i < v.length; i++)
if (v[i] != other.v[i])
return false;
return true;
}
}
the class IntVector implements a method equals, which compares two vectors. If the other vector is the same vector object as the one for which the equals method was invoked, then the check can skip the length and value comparisons. The equals method implements this check by comparing the reference to the other object to this.
The keyword this is also used in a special explicit constructor invocation statement, which can appear at the beginning of a constructor body (§8.8.7).
Let C be the class denoted by ClassName. Let n be an integer such that C is the nth lexically enclosing class of the class in which the qualified this expression appears. The value of an expression of the form ClassName.this is the nth lexically enclosing instance of this (§8.1.3). The type of the expression is C. It is a compile-time error if the current class is not an inner class of class C or C itself.
The use of parentheses only effects the order of evaluation, with one fascinating exception.
Discussion
Consider the case if the smallest possible negative value of type long. This value, 9223372036854775808L, is allowed only as an operand of the unary minus operator (§3.10.1). Therefore, enclosing it in parentheses, as in -(9223372036854775808L) causes a compile time error.
In particular, the presence or absence of parentheses around an expression does not (except for the case noted above) affect in any way:
float or double.
ClassInstanceCreationExpression:
new TypeArgumentsopt ClassOrInterfaceType ( ArgumentListopt )
ClassBodyopt
Primary. new TypeArgumentsopt Identifier TypeArgumentsopt (
ArgumentListopt ) ClassBodyopt
ArgumentList:
Expression
ArgumentList , Expression
A class instance creation expression specifies a class to be instantiated, possibly followed by type arguments (if the class being instantiated is generic (§8.1.2)), followed by (a possibly empty) list of actual value arguments to the constructor. It is also possible to pass explicit type arguments to the constructor itself (if it is a generic constructor (§8.8.4)). The type arguments to the constructor immediately follow the keyword new. It is a compile-time error if any of the type arguments used in a class instance creation expression are wildcard type arguments (§4.5.1). Class instance creation expressions have two forms:
new. An unqualified class instance creation expression may be used to create an instance of a class, regardless of whether the class is a top-level (§7.6), member (§8.5, §9.5), local (§14.3) or anonymous class (§15.9.5).
We say that a class is instantiated when an instance of the class is created by a class instance creation expression. Class instantiation involves determining what class is to be instantiated, what the enclosing instances (if any) of the newly created instance are, what constructor should be invoked to create the new instance and what arguments should be passed to that constructor.
final class. If T denotes an interface then an anonymous direct subclass of Object that implements the interface named by T is declared. In either case, the body of the subclass is the ClassBody given in the class instance creation expression. The class being instantiated is the anonymous subclass.
final inner class (§8.1.3) that is a member of the compile-time type of the Primary. It is also a compile-time error if T is ambiguous (§8.5) or if T denotes an enum type. An anonymous direct subclass of the class named by T is declared. The body of the subclass is the ClassBody given in the class instance creation expression. The class being instantiated is the anonymous subclass.
abstract, or a compile-time error occurs. In this case, the class being instantiated is the class denoted by ClassOrInterfaceType.
abstract inner class (§8.1.3) T that is a member of the compile-time type of the Primary. It is also a compile-time error if Identifier is ambiguous (§8.5), or if Identifier denotes an enum type (§8.9). The class being instantiated is the class denoted by Identifier.
this (§8.1.3).
this.
this.
this.
First, if the class instance creation expression is a qualified class instance creation expression, the qualifying primary expression is evaluated. If the qualifying expression evaluates to null, a NullPointerException is raised, and the class instance creation expression completes abruptly. If the qualifying expression completes abruptly, the class instance creation expression completes abruptly for the same reason.
Next, space is allocated for the new class instance. If there is insufficient space to allocate the object, evaluation of the class instance creation expression completes abruptly by throwing an OutOfMemoryError (§15.9.6).
The new object contains new instances of all the fields declared in the specified class type and all its superclasses. As each new field instance is created, it is initialized to its default value (§4.12.5).
Next, the actual arguments to the constructor are evaluated, left-to-right. If any of the argument evaluations completes abruptly, any argument expressions to its right are not evaluated, and the class instance creation expression completes abruptly for the same reason.
Next, the selected constructor of the specified class type is invoked. This results in invoking at least one constructor for each superclass of the class type. This process can be directed by explicit constructor invocation statements (§8.8) and is described in detail in §12.5.
The value of a class instance creation expression is a reference to the newly created object of the specified class. Every time the expression is evaluated, a fresh object is created.
An anonymous class is never abstract (§8.1.1.1). An anonymous class is always an inner class (§8.1.3); it is never static (§8.1.1, §8.5.2). An anonymous class is always implicitly final (§8.1.1.2).
The body of the constructor consists of an explicit constructor invocation (§8.8.7.1) of the form super(...), where the actual arguments are the formal parameters of the constructor, in the order they were declared.
super(...), where o is the first formal parameter of the constructor, and the actual arguments are the subsequent formal parameters of the constructor, in the order they were declared.
throws clause of an anonymous constructor must list all the checked exceptions thrown by the explicit superclass constructor invocation statement contained within the anonymous constructor, and all checked exceptions thrown by any instance initializers or instance variable initializers of the anonymous class.Note that it is possible for the signature of the anonymous constructor to refer to an inaccessible type (for example, if such a type occurred in the signature of the superclass constructor cs). This does not, in itself, cause any errors at either compile time or run time.
OutOfMemoryError is thrown. This check occurs before any argument expressions are evaluated.So, for example, the test program:
class List {
int value;
List next;
static List head = new List(0);
List(int n) { value = n; next = head; head = this; }
}
class Test {
public static void main(String[] args) {
int id = 0, oldid = 0;
try {
for (;;) {
++id;
new List(oldid = id);
}
} catch (Error e) {
System.out.println(e + ", " + (oldid==id));
}
}
}
prints:
because the out-of-memory condition is detected before the argument expressionjava.lang.OutOfMemoryError: List, false
oldid = id is evaluated.Compare this to the treatment of array creation expressions (§15.10), for which the out-of-memory condition is detected after evaluation of the dimension expressions (§15.10.3).
ArrayCreationExpression:
new PrimitiveType DimExprs Dimsopt
new ClassOrInterfaceType DimExprs Dimsopt
new PrimitiveType Dims ArrayInitializer
new ClassOrInterfaceType Dims ArrayInitializer
DimExprs:
DimExpr
DimExprs DimExpr
DimExpr:
[ Expression ]
Dims:
[ ]
Dims [ ]
An array creation expression creates an object that is a new array whose elements are of the type specified by the PrimitiveType or ClassOrInterfaceType. It is a compile-time error if the ClassOrInterfaceType does not denote a reifiable type (§4.7). Otherwise, the ClassOrInterfaceType may name any named reference type, even an abstract class type (§8.1.1.1) or an interface type (§9). Discussion
The rules above imply that the element type in an array creation expression cannot be a parameterized type, other than an unbounded wildcard.
The type of the creation expression is an array type that can denoted by a copy of the creation expression from which the new keyword and every DimExpr expression and array initializer have been deleted.
For example, the type of the creation expression:
is:new double[3][3][]
The type of each dimension expression within a DimExpr must be a type that is convertible (§5.1.8) to an integral type, or a compile-time error occurs. Each expression undergoes unary numeric promotion (§). The promoted type must bedouble[][][]
int, or a compile-time error occurs; this means, specifically, that the type of a dimension expression must not be long.If an array initializer is provided, the newly allocated array will be initialized with the values provided by the array initializer as described in §10.6.
First, the dimension expressions are evaluated, left-to-right. If any of the expression evaluations completes abruptly, the expressions to the right of it are not evaluated.
Next, the values of the dimension expressions are checked. If the value of any DimExpr expression is less than zero, then an NegativeArraySizeException is thrown.
Next, space is allocated for the new array. If there is insufficient space to allocate the array, evaluation of the array creation expression completes abruptly by throwing an OutOfMemoryError.
Then, if a single DimExpr appears, a single-dimensional array is created of the specified length, and each component of the array is initialized to its default value (§4.12.5).
If an array creation expression contains N DimExpr expressions, then it effectively executes a set of nested loops of depth N-1 to create the implied arrays of arrays.
For example, the declaration:
is equivalent in behavior to:float[][] matrix = new float[3][3];
float[][] matrix = new float[3][];
for (int d = 0; d < matrix.length; d++)
matrix[d] = new float[3];
and:
is equivalent to:Age[][][][][] Aquarius = new Age[6][10][8][12][];
Age[][][][][] Aquarius = new Age[6][][][][];
for (int d1 = 0; d1 < Aquarius.length; d1++) {
Aquarius[d1] = new Age[10][][][];
for (int d2 = 0; d2 < Aquarius[d1].length; d2++) {
Aquarius[d1][d2] = new Age[8][][];
for (int d3 = 0; d3 < Aquarius[d1][d2].length; d3++) {
Aquarius[d1][d2][d3] = new Age[12][];
}
}
}
with d, d1, d2 and d3 replaced by names that are not already locally declared. Thus, a single new expression actually creates one array of length 6, 6 arrays of length 10, 6 x 10 = 60 arrays of length 8, and 6 x 10 x 8 = 480 arrays of length 12. This example leaves the fifth dimension, which would be arrays containing the actual array elements (references to Age objects), initialized only to null references. These arrays can be filled in later by other code, such as:
Age[] Hair = { new Age("quartz"), new Age("topaz") };
Aquarius[1][9][6][9] = Hair;
A multidimensional array need not have arrays of the same length at each level.
Thus, a triangular matrix may be created by:
float triang[][] = new float[100][];
for (int i = 0; i < triang.length; i++)
triang[i] = new float[i+1];
Thus:
class Test {
public static void main(String[] args) {
int i = 4;
int ia[][] = new int[i][i=3];
System.out.println(
"[" + ia.length + "," + ia[0].length + "]");
}
}
prints:
because the first dimension is calculated as[4,3]
4 before the second dimension expression sets i to 3.If evaluation of a dimension expression completes abruptly, no part of any dimension expression to its right will appear to have been evaluated. Thus, the example:
class Test {
public static void main(String[] args) {
int[][] a = { { 00, 01 }, { 10, 11 } };
int i = 99;
try {
a[val()][i = 1]++;
} catch (Exception e) {
System.out.println(e + ", i=" + i);
}
}
static int val() throws Exception {
throw new Exception("unimplemented");
}
}
prints:
because the embedded assignment that setsjava.lang.Exception: unimplemented, i=99
i to 1 is never executed.OutOfMemoryError is thrown. This check occurs only after evaluation of all dimension expressions has completed normally. So, for example, the test program:
class Test {
public static void main(String[] args) {
int len = 0, oldlen = 0;
Object[] a = new Object[0];
try {
for (;;) {
++len;
Object[] temp = new Object[oldlen = len];
temp[0] = a;
a = temp;
}
} catch (Error e) {
System.out.println(e + ", " + (oldlen==len));
}
}
}
prints:
because the out-of-memory condition is detected after the dimension expressionjava.lang.OutOfMemoryError, true
oldlen = len is evaluated.Compare this to class instance creation expressions (§15.9), which detect the out-of-memory condition before evaluating argument expressions (§15.9.6).
super. (It is also possible to refer to a field of the current instance or current class by using a simple name; see §6.5.6.)
FieldAccess:
Primary . Identifier
super . Identifier
ClassName .super . Identifier
The meaning of a field access expression is determined using the same rules as for qualified names (§6.6), but limited by the fact that an expression cannot denote a package, class type, or interface type.
static:
final, then the result is the value of the specified class variable in the class or interface that is the type of the Primary expression.
final, then the result is a variable, namely, the specified class variable in the class that is the type of the Primary expression.
static:
null, then a NullPointerException is thrown.
final, then the result is the value of the specified instance variable in the object referenced by the value of the Primary.
final, then the result is a variable, namely, the specified instance variable in the object referenced by the value of the Primary.
Thus, the example:
class S { int x = 0; }
class T extends S { int x = 1; }
class Test {
public static void main(String[] args) {
T t = new T();
System.out.println("t.x=" + t.x + when("t", t));
S s = new S();
System.out.println("s.x=" + s.x + when("s", s));
s = t;
System.out.println("s.x=" + s.x + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
produces the output:
The last line shows that, indeed, the field that is accessed does not depend on the run-time class of the referenced object; even ift.x=1 when t holds a class T at run time. s.x=0 when s holds a class S at run time. s.x=0 when s holds a class T at run time.
s holds a reference to an object of class T, the expression s.x refers to the x field of class S, because the type of the expression s is S. Objects of class T contain two fields named x, one for class T and one for its superclass S.This lack of dynamic lookup for field accesses allows programs to be run efficiently with straightforward implementations. The power of late binding and overriding is available, but only when instance methods are used. Consider the same example using instance methods to access the fields:
class S { int x = 0; int z() { return x; } }
class T extends S { int x = 1; int z() { return x; } }
class Test {
public static void main(String[] args) {
T t = new T();
System.out.println("t.z()=" + t.z() + when("t", t));
S s = new S();
System.out.println("s.z()=" + s.z() + when("s", s));
s = t;
System.out.println("s.z()=" + s.z() + when("s", s));
}
static String when(String name, Object t) {
return " when " + name + " holds a "
+ t.getClass() + " at run time.";
}
}
Now the output is:
The last line shows that, indeed, the method that is accessed does depend on the run-time class of referenced object; whent.z()=1 when t holds a class T at run time. s.z()=0 when s holds a class S at run time. s.z()=1 when s holds a class T at run time.
s holds a reference to an object of class T, the expression s.z() refers to the z method of class T, despite the fact that the type of the expression s is S. Method z of class T overrides method z of class S.The following example demonstrates that a null reference may be used to access a class (static) variable without causing an exception:
class Test {
static String mountain = "Chocorua";
static Test favorite(){
System.out.print("Mount ");
return null;
}
public static void main(String[] args) {
System.out.println(favorite().mountain);
}
}
It compiles, executes, and prints:
Mount Chocorua
Even though the result of favorite() is null, a NullPointerException is not thrown. That "Mount " is printed demonstrates that the Primary expression is indeed fully evaluated at run time, despite the fact that only its type, not its value, is used to determine which field to access (because the field mountain is static).
super are valid only in an instance method, instance initializer or constructor, or in the initializer of an instance variable of a class; these are exactly the same situations in which the keyword this may be used (§15.8.3). The forms involving super may not be used anywhere in the class Object, since Object has no superclass; if super appears in class Object, then a compile-time error results.
Suppose that a field access expression super.name appears within class C, and the immediate superclass of C is class S. Then super.name is treated exactly as if it had been the expression ((S)this).name; thus, it refers to the field named name of the current object, but with the current object viewed as an instance of the superclass. Thus it can access the field named name that is visible in class S, even if that field is hidden by a declaration of a field named name in class C.
The use of super is demonstrated by the following example:
interface I { int x = 0; }
class T1 implements I { int x = 1; }
class T2 extends T1 { int x = 2; }
class T3 extends T2 {
int x = 3;
void test() {
System.out.println("x=\t\t"+x);
System.out.println("super.x=\t\t"+super.x);
System.out.println("((T2)this).x=\t"+((T2)this).x);
System.out.println("((T1)this).x=\t"+((T1)this).x);
System.out.println("((I)this).x=\t"+((I)this).x);
}
}
class Test {
public static void main(String[] args) {
new T3().test();
}
}
x= 3 super.x= 2 ((T2)this).x= 2 ((T1)this).x= 1 ((I)this).x= 0
Within class T3, the expression super.x is treated exactly as if it were:
Suppose that a field access expression T.((T2)this).x
super.name appears within class C, and the immediate superclass of the class denoted by T is a class whose fully qualified name is S. Then T.super.name is treated exactly as if it had been the expression ((S)T.this).name.
Thus the expression T.super.name can access the field named name that is visible in the class named by S, even if that field is hidden by a declaration of a field named name in the class named by T.
It is a compile-time error if the current class is not an inner class of class T or T itself.
MethodInvocation:
MethodName ( ArgumentListopt )
Primary . NonWildTypeArgumentsopt Identifier ( ArgumentListopt )
super . NonWildTypeArgumentsopt Identifier ( ArgumentListopt )
ClassName . super . NonWildTypeArgumentsopt Identifier ( ArgumentListopt
)
TypeName . NonWildTypeArguments Identifier ( ArgumentListopt )
The definition of ArgumentList from §15.9 is repeated here for convenience:
ArgumentList:
Expression
ArgumentList , Expression
Resolving a method name at compile time is more complicated than resolving a field name because of the possibility of method overloading. Invoking a method at run time is also more complicated than accessing a field because of the possibility of instance method overriding.
Determining the method that will be invoked by a method invocation expression involves several steps. The following three sections describe the compile-time processing of a method invocation; the determination of the type of the method invocation expression is described in §15.12.3.
. Identifier, then the name of the method is the Identifier and the class to search is the one named by the TypeName. If TypeName is the name of an interface rather than a class, then a compile-time error occurs, because this form can invoke only static methods and interfaces have no static methods.
. Identifier; then the name of the method is the Identifier and the class or interface to search is the declared type T of the field named by the FieldName, if T is a class or interface type, or the upper bound of T if T is a type variable.
super.NonWildTypeArgumentsopt Identifier, then the name of the method is the Identifier and the class to be searched is the superclass of the class whose declaration contains the method invocation. Let T be the type declaration immediately enclosing the method invocation. It is a compile-time error if any of the following situations occur:
super.NonWildTypeArgumentsopt Identifier, then the name of the method is the Identifier and the class to be searched is the superclass of the class C denoted by ClassName. It is a compile-time error if C is not a lexically enclosing class of the current class. It is a compile-time error if C is the class Object. Let T be the type declaration immediately enclosing the method invocation. It is a compile-time error if any of the following situations occur:
static methods and interfaces have no static methods.
A method is applicable if it is either applicable by subtyping (§15.12.2.2), applicable by method invocation conversion (§15.12.2.3), or it is an applicable variable arity method (§15.12.2.4).
The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1). The remainder of the process is split into three phases.
Discussion
The purpose of the division into phases is to ensure compatibility with older versions of the Java programming language.
The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.
Discussion
This guarantees that any calls that were valid in older versions of the language are not considered ambiguous as a result of the introduction of variable arity methods, implicit boxing and/or unboxing.
The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.
Discussion
This ensures that a variable arity method is never invoked if an applicable fixed arity method exists.
The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing and unboxing.
Deciding whether a method is applicable will, in the case of generic methods (§8.4.4), require that actual type arguments be determined. Actual type arguments may be passed explicitly or implicitly. If they are passed implicitly, they must be inferred (§15.12.2.7) from the types of the argument expressions.
If several applicable methods have been identified during one of the three phases of applicability testing, then the most specific one is chosen, as specified in section §15.12.2.5. See the following subsections for details.
Discussion
The clause above implies that a non-generic method may be potentially applicable to an invocation that supplies explicit type parameters. Indeed, it may turn out to be applicable. In such a case, the type parameters will simply be ignored.
This rule stems from issues of compatibility and principles of substitutability. Since interfaces or superclasses may be generified independently of their subtypes, we may override a generic method with a non-generic one. However, the overriding (non-generic) method must be applicable to calls to the generic method, including calls that explicitly pass type parameters. Otherwise the subtype would not be substitutable for its generified supertype.
Whether a member method is accessible at a method invocation depends on the access modifier (public, none, protected, or private) in the member's declaration and on where the method invocation appears.
The class or interface determined by compile-time step 1 (§15.12.1) is searched for all member methods that are potentially applicable to this method invocation; members inherited from superclasses and superinterfaces are included in this search.
In addition, if the method invocation has, before the left parenthesis, a MethodName of the form Identifier, then the search process also examines all methods that are (a) imported by single-static-import declarations (§7.5.3) and static-import-on-demand declarations (§7.5.4) within the compilation unit (§7.3) within which the method invocation occurs, and (b) not shadowed (§6.3.1) at the place where the method invocation appears.
If the search does not yield at least one method that is potentially applicable, then a compile-time error occurs.
i
n. Then:
1, be the formal type parameters of m, and let Bl be the declared bound of Rl, 1
l
p. Then:
i
n.
Then let Si = Fi[R1 = U1, ..., Rp = Up] 1
i
n, be the types inferred for of the formal parameters of m.
i
n, either:
l
p.
i
n. Then:
1, be the formal type parameters of m, and let Bl be the declared bound of Rl, 1
l
p. Then:
i
n.
Then let Si = Fi[R1 = U1, ..., Rp = Up] 1
i
n, be the types inferred for the formal parameters of m.
i
n, the type of ei, Ai, can be converted by method invocation conversion (§5.3) to Si.
l
p.
i
k. Then:
n
k+1, be the types of the formal parameters of m, where Fn = T[] for some type T, and let R1 ... Rp p
1, be the formal type parameters of m, and let Bl be the declared bound of Rl, 1
l
p. Then:
i
n and the constraints Aj << T, n
j
k.
Then let Si = Fi[R1 = U1, ..., Rp = Up] 1
i
n, be the types inferred for the formal parameters of m.
k+1, be the types of the formal parameters of m.
i
n, the type of ei, Ai, can be converted by method invocation conversion to Si.
n, then for n
i
k, the type of ei, Ai, can be converted by method invocation conversion to the component type of Sn.
l
p.
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.
One fixed-arity member method named m is more specific than another member method of the same name and arity if all of the following conditions hold:
1, be its formal type parameters, let Bl be the declared bound of Rl, 1
l
p, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ti << Ui, 1
i
n and let Si = Ui[R1 = A1, ..., Rp = Ap] 1
i
n; otherwise let Si = Ui 1
i
n.
l
p.
k. The types of the parameters of the first member method are T1, . . . , Tn-1 , Tn[], the types of the parameters of the other method are U1, . . . , Uk-1, Uk[]. If the second method is generic then let R1 ... Rp p
1, be its formal type parameters, let Bl be the declared bound of Rl, 1
l
p, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ti << Ui,1
i
k-1, Ti << Uk, k
i
n and let Si = Ui[R1 = A1, ..., Rp = Ap] 1
i
k; otherwise let Si = Ui, 1
i
k. Then:
l
p.
k. The types of the parameters of the first method are U1, . . . , Uk-1, Uk[], the types of the parameters of the other method are T1, . . ., Tn-1, Tn[]. If the second method is generic then let R1 ... Rp p
1, be its formal type parameters, let Bl be the declared bound of Rl, 1
l
p, let A1 ... Ap be the actual type arguments inferred (§15.12.2.7) for this invocation under the initial constraints Ui << Ti, 1
i
k-1, Uk << Ti, k
i
n and let Si = Ti[R1 = A1, ..., Rp = Ap] 1
i
n; otherwise let Si = Ti, 1
i
n. Then:
A method m1 is strictly more specific than another method m2 if and only if m1 is more specific than m2 and m2 is not more specific than m1.
A method is said to be maximally specific for a method invocation if it is accessible and applicable and there is no other method that is applicable and accessible that is strictly more specific.
If there is exactly one maximally specific method, then that method is in fact the most specific method; it is necessarily more specific than any other accessible method that is applicable. It is then subjected to some further compile-time checks as described in §15.12.3.
It is possible that no method is the most specific, because there are two or more methods that are maximally specific. In this case:
abstract, it is the most specific method.
abstract, and the signatures of all of the maximally specific methods have the same erasure (§4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type. However, the most specific method is considered to throw a checked exception if and only if that exception or its erasure is declared in the throws clauses of each of the maximally specific methods.
void, then the result is void.
i
n, let Fi be the formal type parameters of the method, let Ai be the actual type arguments inferred for the method invocation, and let R be the declared return type of the method being invoked. The result type is obtained by applying capture conversion (§5.1.10) to R[F1 := A1, ..., Fn := An].
i
n, let Fi be the formal type parameters of the method, let Ai be the actual type arguments inferred for the method invocation, and let Ej, 1
j
m be the exception types declared in the throws clause of the method being invoked. The throws clause consists of the types to Ej[F1 := A1, ..., Fn := An].
Discussion
The process of type inference is inherently complex. Therefore, it is useful to give an informal overview of the process before delving into the detailed specification.
Inference begins with an initial set of constraints. Generally, the constraints require that the statically known types of the actual arguments are acceptable given the declared formal argument types. We discuss the meaning of "acceptable" below.
Given these initial constraints, one may derive a set of supertype and/or equality constraints on the formal type parameters of the method or constructor.
Next, one must try and find a solution that satisfies the constraints on the type parameters. As a first approximation, if a type parameter is constrained by an equality constraint, then that constraint gives its solution. Bear in mind that the constraint may equate one type parameter with another, and only when the entire set of constraints on all type variables is resolved will we have a solution.
A supertype constraint T :> X implies that the solution is one of supertypes of X. Given several such constraints on T, we can intersect the sets of supertypes implied by each of the constraints, since the type parameter must be a member of all of them. We can then choose the most specific type that is in the intersection.
Computing the intersection is more complicated than one might first realize. Given that a type parameter is constrained to be a supertype of two distinct invocations of a generic type, say List<Object> and List<String>, the naive intersection operation might yield Object. However, a more sophisticated analysis yields a set containing List<?>. Similarly, if a type parameter, T, is constrained to be a supertype of two unrelated interfaces I and J, we might infer T must be Object, or we might obtain a tighter bound of I & J. These issues are discussed in more detail later in this section.
We will use the following notational conventions in this section:
Discussion
In a simpler world, the constraints could be of the form A <: F - simply requiring that the actual argument types be subtypes of the formal ones. However, reality is more involved. As discussed earlier, method applicability testing consists of up to three phases; this is required for compatibility reasons. Each phase imposes slightly different constraints. If a method is applicable by subtyping (§15.12.2.2), the constraints are indeed subtyping constraints. If a method is applicable by method invocation conversion (§15.12.2.3), the constraints imply that the actual type is convertible to the formal type by method invocation conversion. The situation is similar for the third phase (§15.12.2.4), but the exact form of the constraints differ due to the variable arity.
These constraints are then reduced to a set of simpler constraints of the forms T :> X, T = X or T <: X, where T is a type parameter of the method. This reduction is achieved by the procedure given below:
Discussion
It may be that the initial constraints are unsatisfiable; we say that inference is overconstrained. In that case, we do not necessarily derive unsatisfiable constraints on the type parameters. Instead, we may infer actual type arguments for the invocation, but once we substitute the actual type arguments for the formal type parameters, the applicability test may fail because the actual argument types are not acceptable given the substituted formals.
An alternative strategy would be to have type inference itself fail in such cases. Compilers may choose to do so, provided the effect is equivalent to that specified here.
Given a constraint of the form A << F, A = F, or A >> F:
null, no constraint is implied on Tj.
Discussion
This follows from the covariant subtype relation among array types. The constraint A << F, in this case means that A << U[]. A is therefore necessarily an array type V[], or a type variable whose upper bound is an array type V[] - otherwise the relation A << U[] could never hold true. It follows that V[] << U[]. Since array subtyping is covariant, it must be the case that V << U.
k
n where U is a type expression that involves Tj, then if A has a supertype of the form G<..., Xk-1, V, Xk+1, ...> where V is a type expression, this algorithm is applied recursively to the constraint V = U.
Discussion
For simplicity, assume that G takes a single type argument. If the method invocation being examined is to be applicable, it must be the case that A is a subtype of some invocation of G. Otherwise, A << F would never be true.
In other words, A << F, where F = G<U>, implies that A << G<V> for some V. Now, since U is a type expression (and therefore, U is not a wildcard type argument), it must be the case that U = V, by the non-variance of ordinary parameterized type invocations.
The formulation above merely generalizes this reasoning to generics with an arbitrary number of type arguments.
Discussion
Again, let's keep things as simple as possible, and consider only the case where G has a single type argument.
A <<F in this case means A << G<? extends U>. As above, it must be the case that A is a subtype of some invocation of G. However, A may now be a subtype of either G<V>, or G<? extends V>, or G<? super V>. We examine these cases in turn. The first variation is described (generalized to multiple arguments) by the sub-bullet directly above. We therefore have A = G<V> << G<? extends U>. The rules of subtyping for wildcards imply that V << U.
Discussion
Extending the analysis above, we have A = G<? extends V> << G<? extends U>. The rules of subtyping for wildcards again imply that V << U.
Discussion
Here, we have A = G<? super V> << G<? extends U>. In general, we cannot conclude anything in this case. However, it is not necessarily an error. It may be that U will eventually be inferred to beObject, in which case the call may indeed be valid. Therefore, we simply refrain from placing any constraint on U.
Discussion
As usual, we consider only the case where G has a single type argument.
A <<F in this case means A << G<? super U>. As above, it must be the case that A is a subtype of some invocation of G. A may now be a subtype of either G<V>, or G<? extends V>, or G<? super V>. We examine these cases in turn. The first variation is described (generalized to multiple arguments) by the sub-bullet directly above. We therefore have A = G<V> << G<? super U>. The rules of subtyping for wildcards imply that V >> U.
Discussion
We have A = G<? super V> << G<? super U>. The rules of subtyping for lower-bounded wildcards again imply that V >> U.
Discussion
Here, we have A = G<? extends V> << G<? super U>. In general, we cannot conclude anything in this case. However, it is not necessarily an error. It may be that U will eventually be inferred to the null type, in which case the call may indeed be valid. Therefore, we simply refrain from placing any constraint on U.
Discussion
Such a constraint is never part of the initial constraints. However, it can arise as the algorithm recurses. We have seen this occur above, when the constraint A << F relates two parameterized types, as in G<V> << G<U>.
Discussion
Clearly, if the array types U[] and V[] are the same, their component types must be the same.
k
n where U is type expression that involves Tj, then if A is of the form G<..., Xk-1, V, Xk+1,...> where V is a type expression, this algorithm is applied recursively to the constraint V = U.