Quiz Yourself: Overriding Operator Precedence (Intermediate)

Parentheses make a real difference in the results of an evaluation.

April 21, 2020 | Download a PDF of this article
More quiz questions available here

If you have worked on our quiz questions in the past, you know none of them is easy. They model the difficult questions from certification examinations. The “intermediate” and “advanced” designations refer to the exams rather than to the questions, although in almost all cases, “advanced” questions will be harder. We write questions for the certification exams, and we intend that the same rules apply: Take words at their face value and trust that the questions are not intended to deceive you but to straightforwardly test your knowledge of the ins and outs of the language.

The objective in this Java SE quiz is to understand how parentheses override operator precedence.

Given this code:


public class Tester {
    
    public static String test(byte b) {
        return "Byte ";
    }
    
    public static String test(char c) {
        return "Char ";
    }
    
    public static String test(int i) {
        return "Int ";
    }
    
    public static void main(String[] args) {
        byte b = 0;
        char c = 'A';
        System.out.print(test(true  ? b : c));
        System.out.print(test(false ? b : c));
        System.out.print(test(true  ? 0 : 'A'));
        System.out.print(test(false ? 'A' : (byte)0));
    }
}

What is the output? Choose one.

A. Byte Char Int Byte
B. Int Int Int Int
C. Int Int Char Int
D. Int Int Char Byte
 

Answer. This question would be classified under the objective “Use Java operators,” but it also touches on knowledge closely related to the issue of initializing variables, because the answer depends in part on how the effective type of a literal expression might vary according to the context in which it appears. Beyond that, we should make a couple of additional warnings:

  • This question is probably significantly more difficult than what you will see on the exam.
  • If you see real code written in this way, you should almost certainly change it to something less confusing. We’ll add a bit more on that second point later.

In case you are unfamiliar with the conditional operator (sometimes called the ternary operator), let’s explore it briefly. It takes three expressions: The first two are separated by a query (?) and the second and third are separated by a colon (:). It creates the effect of an if/else expression. That is, given an expression looking like the following, the expression a is required to be of boolean (or Boolean) type:


a ? b : c

If the value of a is true, the overall expression’s value is b; otherwise, it’s c. The conditional operator is particularly convenient in situations where you’d otherwise have an if/else that performs a single assignment, to the same variable, in both the if and the else, and only the value assigned differs.

So, this expression:


String message = isMorning(theTime) ? "Good morning" : " Good afternoon"

Has an effect equivalent to this code:


String message;
if (isMorning(theTime)) {
  message = "Good morning";
} else { 
  message = "Good afternoon";
}

This example usage suggests that the two alternate values must both be assignment-compatible with the intended variable to which the result will be assigned. Indeed, in a more general case, there must be some commonality in those types (even if it’s just Object), and from a style perspective, it’s probably wise that the second and third operands should be of the same type. This is likely to happen naturally (as this example suggests) if those arguments are object types, but with primitives, there’s perhaps a greater chance of mixed types appearing in those positions. When primitives are mixed in expressions, sometimes the promotions affect the type of the end result.

That’s enough background. Let’s look at the specifics of this question. There are several things that might need your consideration here:

  • Does the boolean argument to the conditional operator ever affect the type of the overall conditional expression?
  • How can you determine the type of a conditional expression from the types of its operands?
  • How is one method selected for invocation from among overloaded methods that vary by primitive argument type?
  • How do literal values acquire the type from their context?

Java has, of course, rules describing how to determine the exact result type (and, therefore, assignment compatibility) for any combination of operand types provided to any operator. Generally, these rules are reasonably simple, but the conditional operator has a special set of rules that differ somewhat from the more general case. The conditional operator and these rules are described in detail in the Java Language Specification, Java SE 11 Edition in section 15.25.

That dauntingly long description explains that the result type of the conditional operator is not affected by the value of the Boolean condition. This is the case even if the Boolean condition is a constant and is, therefore, known to the compiler. Therefore, you can safely ignore the Boolean literals in the code and simply pay attention to the combination of the second and third operands.

Next, let’s defer the question of the type of the conditional expression and think about the overloaded methods and which would be invoked under what circumstances. The output and, hence, the correct answer depend critically on which method is invoked from each of the four print calls. Fortunately, the rules for selecting among overloaded methods can also be described fairly simply in this context. The first point on this issue is that when overloaded methods exist, the target is selected at compile time. The second may be described briefly by saying that the target method is selected based on the best fit of the argument types. So, if you had a single method such as this:


void doStuff(int x) {}

It would be a valid target for an invocation of the following form:


short s = 99;
x.doStuff(s); 

In this case, the short argument would be promoted to int. However, if a second method were added to this mix, like this:


void doStuff(short x) {}

The second method would be selected over the first because although the short argument s can be promoted to int, it’s a better fit to select the method that takes the exact same argument type. In another situation, a method might be selected that requires the least amount of change of the argument type.

The full details on selecting one of several overloaded methods are described in the Java Language Specification, Java SE 11 Edition in section 15.12.2. The details are not trivial, but because the methods in this case have no generics, no variable argument lists, and no autoboxing or unboxing, you avoid those sources of complexity in this particular question.

From the discussion so far, you know that the essential information you need in order to determine which of the overloaded test methods is invoked and, hence, which messages are printed, is simply what the types of the four conditional expressions are.

The operands of the four conditional expressions are as follows:

  • In the first: a byte variable and a char variable
  • In the second: a byte variable and a char variable
  • In the third: a constant expression of int type and a char literal
  • In the fourth: a char literal and a byte expression

As mentioned, there are rules for how these things combine. In this case, there are some widely applicable rules and some special variations that apply only to the conditional operator. Let’s look at the general rules first.

The Java Language Specification, Java SE 11 Edition notes in section 5.6.2  (number 2) that the rules for a “widening primitive conversion” will be applied to binary operators. (Notice that the conditional operator is not a binary operator; it’s a ternary one. And it turns out these rules do not completely describe everything of importance to this question.) So, these rules, which are fairly simple, state that

  • If either operand is of type double, the other is converted to double.
  • Otherwise, if either operand is of type float, the other is converted to float.
  • Otherwise, if either operand is of type long, the other is converted to long.
  • Otherwise, both operands are converted to type int.

If these were the rules that applied to the conditional operator, it is clear that the fourth of these would prevail in all four of the options for this question, and as a result the output would be Int Int Int Int. However, they’re not the only rules that apply.

The correct rule set to apply to a conditional operator is given in the Java Language Specification, Java SE 11 Edition in section 15.25 and is spread across many pages. For the first, second, and fourth expressions in our example, investigation shows that the rule listed for these combinations actually does end up being the behavior already suggested. The specification’s description says that the type will be the result of bnp(byte,char).

Evaluating bnp (which stands for binary numeric promotion) returns an int, which results in the output of the text Int in the first, second, and fourth positions.

Now, let’s investigate the third combination. In this case, it turns out that because the rules for conditional expressions do not always produce “at least int,” and because of a rule relating to literal expressions, the resulting type of the expression is actually char. This may be determined from table 15.25-A in the Java Language Specification, Java SE 11 Edition and the supporting descriptions. The table cell that describes the effect of combining an int literal with a char describes the resulting type as char | bnp(int,char). This means that if the literal fits in the char data size, the result is a char; otherwise, it’s the result of normal promotion (which would be int). To be strict, the rule applies to a “constant expression of type int,” which isn’t exactly the same, and it allows for some additional expressions that can be fully evaluated by the compiler.

Because the int literal in this element is zero, which fits entirely in a char representation, the resulting type is a char, and the overall output is Int Int Char Int, which makes option C the correct answer.

This same logic might seem to apply to the fourth option. However, in this case, the cast expression (byte)0 is not treated as a literal, but simply as an expression of byte type. This results in normal binary promotion and a resulting type of int.

Another point to focus on is how a literal value has a somewhat special relationship with its type. This is most commonly observed when initializing a variable declaration. For example, if the rules were simple (as they were in Java 1.0), this would be illegal:


short x = 1; // Illegal in Java 1.0, legal today

It would be illegal, because 1 is an int literal, and an int is not assignment-compatible with a short. However, Java quickly changed to permit this assignment (and others like it) provided the literal value can be properly represented in the target variable. Perhaps equally interesting is that this doesn’t apply to the initialization of a float variable, so this is (still) illegal:


float pi = 3.14;

This initialization is illegal, because the floating-point format literal is a double not a float, and in this case, the well-it-fits argument doesn’t apply.

The correct answer is option C.

Simon Roberts

Simon Roberts joined Sun Microsystems in time to teach Sun’s first Java classes in the UK. He created the Sun Certified Java Programmer and Sun Certified Java Developer exams. He wrote several Java certification guides and is currently a freelance educator who publishes recorded and live video training through Pearson InformIT (available direct and through the O’Reilly Safari Books Online service). He remains involved with Oracle’s Java certification projects.

Mikalai Zaikin

Mikalai Zaikin is a lead Java developer at IBA IT Park in Minsk, Belarus. During his career, he has helped Oracle with development of Java certification exams, and he has been a technical reviewer of several Java certification books, including three editions of the famous Sun Certified Programmer for Java study guides by Kathy Sierra and Bert Bates.

Share this Page