Evaluate Java expressions with operators

jeff_friesen

Here's everything you need to know about Java operators and operator types, and how to use them to write expressions for your Java programs.

Three building blocks with question marks.

Credit: Ka Iki/Shutterstock

In this tutorial, you will learn how to write expressions for your Java programs. In many cases, you’ll use operators to write your Java expressions, and there are many operator types to know how to use. I’ll briefly introduce Java’s operator types, including the additive, bitwise, logical, conditional, shift, and equality types and their operands. You’ll also learn about operator overloading and operator precedence, and you’ll see a demonstration of primitive-type conversion. I’ll conclude with a small Java program that you can use to practice primitive-type conversions on your own.

What you’ll learn in this Java tutorial

Download the source code for example applications in this tutorial. Created by Jeff Friesen.

What is a Java expression?

Expressions are combinations of literals, method calls, variable names, and operators. Java applications evaluate expressions. Evaluating an expression produces a new value that can be stored in a variable, used to make a decision, and more.

How to write simple expressions

A simple expression is a literal, variable name, or method call. No operators are involved. Here are some examples of simple expressions:

 52 // integer literal age // variable name System.out.println("ABC"); // method call "Java" // string literal 98.6D // double precision floating-point literal 89L // long integer literal

A simple expression has a type, which is either a primitive type or a reference type. In these examples, 52 is a 32-bit integer ( int ); System.out.println("ABC"); is void ( void ) because it returns no value; "Java" is a string ( String ); 98.6D is a 64-bit double-precision floating-point value ( double ); and 89L is a 64-bit long integer ( long ). We don’t know age ‘s type.

Use jshell to experiment

You can easily try out these and other simple expressions using jshell . For example, enter 52 at the jshell> prompt and you’ll receive something like the following output:

$1 ==> 52

$1 is the name of a scratch variable that jshell creates to store 52 . (Scratch variables are created whenever literals are entered.) Execute System.out.println($1) and you’ll see 52 as the output.

You can run jshell with the -v command-line argument ( jshell -v ) to generate verbose feedback. In this case, entering 52 would result in the following message, revealing that scratch variable $1 has int (32-bit integer) type:

| created scratch variable $1 : int

Next, try entering age . In this case, you’ll probably receive an error message that the symbol was not found. The Java Shell assumes that age is a variable, but it doesn’t know its type. You would have to include a type; for example, see what happens if you enter int age .

How to write compound expressions

A compound expression consists of one or more simple expressions integrated into a larger expression via an operator, which is a sequence of instructions symbolically represented in source code. The operator transforms its expression operand(s) into another value. For example, in 6 * 5 , the multiplication operator ( * ) transforms operands 6 and 5 into 30.

Compound expressions can be combined into larger expressions. For example, 6 * 5 + 10 presents compound expression 6 * 5 and a compound expression consisting of their product, addition operator + , and the number 10 . The order of evaluation (multiply first and then add) is dictated by Java’s rule of precedence, which we’ll get to shortly.

Compound expressions can be simple, too

6 * 5 is a compound expression comprised of two simple expressions, 6 and 5 . But 6 * 5 is also a simple expression from + ‘s perspective. The + operator sees only their product, 30, which is a simple expression.

About Java operators and operands

Java’s operators are classified by their number of operands:

Java’s operators are also classified by position:

Another jshell example

I’ll introduce more operators in the following sections, where I present examples in the form of applications. You could also try out these operators with jshell , like so:

jshell> 6 + 2 $1 ==> 8 jshell> 7 * $1 $2 ==> 56

In this case, we first enter the expression 6 + 2 , which jshell evaluates, assigning the resulting 8 to scratch variable $1 . Next, we multiply $1 by 7 , which stores 56 in scratch variable $2 . This example demonstrates that you can use scratch variables in Java expressions.

Overloaded operators

The plus (+) operator is an example of an overloaded operator, which is an operator that performs one of several operations based on the types of its operands. The plus operator performs integer addition when both operands are integers, floating-point addition when both operands are floating-point values, and string concatenation when both operands are strings. The minus (-) operator is also overloaded, performing integer or floating-point subtraction.

Operator types in Java

Next, we’ll tour all the operator types in Java. After introducing each operator type, I’ll present an example that shows you how it’s used in Java expressions.

Additive operators

The additive operators increase or decrease a numeric value through addition and subtraction. Additive operators include addition ( + ), subtraction ( - ), postdecrement ( -- ), postincrement ( ++ ), predecrement ( -- ), and preincrement ( ++ ). String concatenation ( + ) is also considered to be additive. Here’s a formal definition for each of these operators:

The addition, subtraction, postdecrement, postincrement, predecrement, and preincrement operators can generate values that overflow the limits of the result type. For example, adding two large positive 64-bit integer values can produce a value that cannot be represented in 64 bits. The resulting overflow is not detected or reported by Java’s additive operators.

Overflow detection in the Java standard class library

The standard class library’s Math class includes methods for detecting overflows. For example, int addExact(int x, int y) adds the values in x and y , returning the sum or throwing an exception on overflow.

Example application: Additive operators

Listing 1 presents a small application for playing with Java’s additive operators.

Listing 1. Additive operators in Java (AddOp.java)

class AddOp < public static void main(String[] args) < System.out.println(125 + 463); System.out.println(2.0 - 6.3); int age = 65; System.out.println(age); System.out.println(age--); System.out.println(age++); System.out.println(--age); System.out.println(++age); System.out.println("A" + "B"); >>

See Elementary Java language features for an introduction to using the JDK’s javac tool to compile Java source code and the java tool to run the resulting application. Execute the following command to compile Listing 1:

javac AddOp.java

Assuming successful compilation, you should observe an AddOp.class file in the current directory. Execute the following command to run it:

java AddOp

AddOp responds by producing the following output:

588 -4.3 65 65 64 64 65 AB

Studying this output offers insight into the postincrement, postdecrement, preincrement, and predecrement operators. For postincrement/postdecrement, age ‘s current value is output before the increment/decrement operation. For preincrement/predecrement, the operation is performed and its result is stored in age , and then age ‘s new value is output.

Iterating with Java operators

The additive operators are especially useful in the context of an iteration statement, where they are used to advance to the next iteration. See Deciding and iterating with Java statements for more on this topic.

Array index operator

The array index operator ( [] ) accesses an array element by providing the element’s index (position). This operator is placed after the array variable’s name, as in grades[0] (access the first element in the array assigned to grades ; the first element is stored at index 0). Here’s a formal definition:

Given variable[index] , where index must be of integer ( int ) type, read a value from or store a value into variable ‘s storage element at location index . Example: temperatures[1]

The value passed to index is a 32-bit integer that is either 0 or a positive value ranging to one less than the array’s length, which is indicated by appending .length to the name of the array. For example, grades.length returns the number of elements in the array assigned to grades .

Array variables vs arrays

grades is not an array, but is a variable containing a reference to a region of memory that forms the array. This is true for all Java arrays. However, it’s conventional to refer to grades or any array variable as an array.

Example application: Array index operator

Listing 2 presents the source code to an example application that lets you play with the array index operator.

Listing 2. Array index operator in Java (ArrayIndexOp.java)

class ArrayIndexOp < public static void main(String[] args) < int[] grades = < 89, 90, 68, 73, 79 >; System.out.println(grades[1]); grades[1] = 91; System.out.println(grades[1]); int index = 4; System.out.println(grades[index]); System.out.println(grades['C' - 'A']); // System.out.println(grades[1D]); > >

Listing 2 is somewhat more interesting than Listing 1. After creating a five-element, one-dimensional array of integers (via an array initializer) and assigning the array’s reference to grades , main() proceeds to access various elements. Two items are of special interest:

The final example, which passes 1D as an index to the array index operator, is commented out because it will not compile. If you uncomment the line and attempt to compile Listing 2, you will receive an error message about incompatible types: “possible lossy conversion from double to int. .”

Compile Listing 2 ( javac ArrayIndexOp.java ) and run the application ( java ArrayIndexOp ). You should observe the following output:

90 91 79 68

The array index operator and multidimensional arrays

You can use this operator with multidimensional arrays. For example, assuming a two-dimensional costs array, costs[0][1] accesses the element assigned to the first row (via [0] ) and the second column (via [1] ).

Assignment operators

The assignment operator ( = ) assigns an expression’s value to a variable (e.g., i = 6; ), including an array element (e.g., x[0] = 15; ). The expression and variable must be assignment compatible, meaning their types must agree. For example, you cannot assign a string literal to an integer variable. I’ll explain more about this when we discuss type conversions.

Keep it short!

Instead of specifying x = x + 1; or x = x - 1; , you could specify the shorter x += 1; or x -= 1; . You could save even more keystrokes by specifying the shorter x++; or x--; .

Bitwise operators

The bitwise operators modify the binary values of their operands, which must be of an integer ( byte , short , int , or long ) or character type. These operators include bitwise AND ( & ), bitwise complement ( ~ ), bitwise exclusive OR ( ^ ), and bitwise inclusive OR ( | ); and are formally defined below:

Example application: Bitwise operators

Listing 3 presents the source code to a BitwiseOp application that lets you play with the bitwise operators.

Listing 3. Bitwise operators in Java (BitwiseOp.java)

class BitwiseOp < public static void main(String[] args) < short x = 0B0011010101110010; short y = 0B0110101011101011; System.out.println(x & y); System.out.println(~x); System.out.println(x ^ y); System.out.println(x | y); >>

Listing 3’s main() method initializes a pair of short integer variables and subsequently uses the bitwise operators to produce new values by operating on their bits; these values are then output.

Compile Listing 3 ( javac BitwiseOp.java ) and run the application ( java BitwiseOp ). You should observe the following output:

8290 -13683 24473 32763

Because it’s difficult to see each operator’s effect on its operands, consider the binary equivalent of the previous operators below. (You’ll understand why each binary number is 32 bits long instead of 16 when I introduce type conversions.)

00000000000000000010000001100010 11111111111111111100101010001101 00000000000000000101111110011001 00000000000000000111111111111011

Cast operator

The cast operator— (type) –attempts to convert the type of its operand to type . You can convert from one primitive type to another primitive type or from one reference type to another reference type, but not from primitive type to reference type or vice versa.

For example, to convert double precision floating-point value 1.0 to its 32-bit integer equivalent, specify (int) 1.0 . Also, to convert circle (of type Circle ) to its Shape supertype, specify (Shape) circle . There will be more to learn about cast when we get into type conversions later in this tutorial. The cast operator is also important for inheritance in Java.

Conditional operators

The conditional operators conditionally evaluate Boolean expressions, which are expressions that have Boolean type and evaluate to true or false. These operators include conditional ( ?: ), conditional AND ( && ), and conditional OR ( || ); and are formally defined below:

Example application: Conditional operators

Listing 4 presents the source code to a CondOp application that lets you play with the conditional operators.

Listing 4. Conditional operators in Java (CondOp.java)

class CondOp < public static void main(String[] args) < boolean sold_more_than_100_units = true; int bonus_dollars = (sold_more_than_100_units) ? 50 : 0; System.out.println(bonus_dollars); System.out.println(true && true); System.out.println(true && false); System.out.println(false && true); System.out.println(false && false); System.out.println(true || true); System.out.println(true || false); System.out.println(false || true); System.out.println(false || false); int x = 0; boolean status = true && ++x == 0; System.out.println(x); status = false && ++x == 0; System.out.println(x); status = true || ++x == 0; System.out.println(x); status = false || ++x == 0; System.out.println(x); >>

Because of short-circuiting, && and || won’t always evaluate their right operands. Although short-circuiting can improve performance somewhat because only one expression is evaluated, it can also be a source of bugs when a side-effect (code that is executed as a byproduct of expression evaluation) is involved.

Listing 4 presents side-effects in which variable x is preincremented. Preincrement occurs for expressions true && ++x == 0 and false || ++x == 0 . However, this variable isn’t incremented for expressions false && ++x == 0 and true || ++x == 0 because there’s no need to evaluate the right operands in these contexts.

Compile Listing 4 ( javac CondOp.java ) and run the application ( java CondOp ). You should observe the following output:

50 true false false false true true true false 1 1 1 2

Equality operators

The equality operators compare their operands to determine if they are equal or unequal. These operators include equality ( == ) and inequality ( != ). The former operator returns true when both operands are equal; the latter operator returns true when both operands are unequal. These operators are formally defined below:

You will need to be careful when comparing floating-point values because not all floating-point values can be represented accurately in memory. For example, 0.1 cannot be represented accurately. For this reason, some expressions involving these operands will return false when you think they should return true.

These operators can be used to compare primitive values or object references. However, you cannot compare a primitive value with an object reference. For example, you might have two Employee objects whose references are stored in e1 and e2 . Expression e1 == e2 returns true only when variables e1 and e2 refer to the same Employee object.

String comparison is a little unusual. You can attempt to compare two string literals, as in "A" == "B" . However, because string literals are really String objects, you are really comparing references to these objects and not comparing their characters. As a result, true returns only when both operands reference the same String object.

Example application: Equality operators

Listing 5 presents the source code to an EqualityOp application that lets you play with the equality operators.

Listing 5. Equality operators in Java (EqualityOp.java)

class EqualityOp < public static void main(String[] args) < int x = 0; System.out.println(x == 0); System.out.println(x != 0); double d = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1; System.out.println(d); System.out.println(d == 0.9); System.out.println("A" == "A"); System.out.println("A" == "B"); System.out.println("AB" == "A" + "B"); String s = "B"; System.out.println("AB" == "A" + s); >>

Compile Listing 5 ( javac EqualityOp.java ) and run the application ( java EqualityOp ). You should observe the following output:

true false 0.8999999999999999 false true false true false

The first two output lines are unsurprising given that x contains 0. The next two output lines reveal that 0.1 cannot be stored exactly in memory: summing nine instances of 0.1 doesn’t equal nine. For this reason, it’s unwise to control an iteration’s duration via an equality expression involving floating-point values.

The remaining output lines prove that Java creates exactly one String object for each unique string literal. For example, there is one String object for "A" , another String object for "B" , and a third String object for "AB" . In "A" == "A" , both "A" literals refer to the same "A" object, so true is the result. However, in "A" == "B" , "A" and "B" refer to the "A" object and to a "B" object. Because of different references, false is the result. Next, in "AB" == "A" + "B" , the string concatenation produces a reference to the same "AB" object as is referenced by literal "AB" , so true is the result. Finally, String s = "B"; creates a new String object that contains B and whose reference is assigned to s . This object (and reference) is separate from the object (and reference) associated with "B" . As a result, in "AB" == "A" + s , we end up with the "AB" String object’s reference being compared with a reference to a different String object containing AB , and this comparison results in false as the result. You’ll learn more about string comparison in a future tutorial.

Logical operators

The logical operators are the Boolean equivalent of the bitwise operators. Instead of working on the bit values of integral operands, they work on their Boolean operands. These operators include logical AND ( & ), logical complement ( ! ), logical exclusive OR ( ^ ), and logical inclusive OR ( | ); and are formally defined below:

Example application: Logical operators

Listing 6 presents the source code to a LogicalOp application that lets you play with the logical operators.

Listing 6. Logical operators in Java (LogicalOp.java)

class LogicalOp < public static void main(String[] args) < int x = 0; System.out.println(false & ++x == 0); System.out.println(x); System.out.println(!false); System.out.println(true ^ true); System.out.println(true ^ false); System.out.println(true | ++x == 0); System.out.println(x); >>

Compile Listing 6 ( javac LogicalOp.java ) and run the application ( java LogicalOp ). You should observe the following output:

false 1 true false true true 2

Member access operator

The member access operator ( . ) accesses class or object members (e.g., methods). For example, assuming that name is of type String and is initialized to a string, name.length() returns the length of that string. Essentially, this operator is accessing the length() member of the name object.

Java regards arrays as special objects with a single length member whose value (an int ) denotes the number of elements in the array. For example, grades.length returns the length of (the number of elements in) the array that grades references. In other words, this operator is accessing the length member of the grades array.

Method call operator

The method call operator — () — signifies that a method is being called and identifies the number, order, and types of expressions being passed to the method. For example, in System.out.println("Java"); , () signifies that method println , which is a member of the System class’s out member, is being called with one argument: "Java" .

Multiplicative operators

The multiplicative operators greatly increase or decrease a numeric value through the equivalent of multiple additions or subtractions (e.g., 4 times 3 is equivalent to adding three 4s, and 12 divided by 3 is equivalent to repeatedly subtracting 3 from 12 until the remainder is less than 3 (0, in this example). These operators include multiplication ( * ), division ( / ), and remainder ( % ); and are formally defined below:

The multiplication operator can generate a product that overflows the limits of the result type, and doesn’t detect and report an overflow. If you need to detect an overflow, you’ll want to work with the Math class’s multiplyExact() methods.

Example application: Multiplicative operators

Listing 7 presents the source code to a MulOp application that lets you play with the multiplicative operators.

Listing 7. Multiplicative operators in Java (MulOp.java)

class MulOp < public static void main(String[] args) < System.out.println(64.0 * 3.0); System.out.println(64 / 3); System.out.println(64 % 3); System.out.println(10.0 / 0.0); System.out.println(-10.0 / 0.0); System.out.println(0.0 / 0.0); System.out.println(10 / 0); >>

Listing 7 is fairly straightforward until you encounter the division-by-zero expressions. Dividing a numeric value by 0 (via the division or remainder operator) results in interesting behavior:

Compile Listing 7 ( javac MulOp.java ) and run the application ( java MulOp ). You should observe the following output:

192.0 21 1 Infinity -Infinity NaN Exception in thread "main" java.lang.ArithmeticException: / by zero at MulOp.main(MulOp.java:11)

Object creation operator

The object creation operator ( new ) is used to create an object from a class or to create an array. This operator is formally defined below:

To create a two-dimensional array, the syntax changes to identifier[integer size][integer size] (e.g., new double[5][5] ). For additional dimensions, append an [integer size] per dimension.

See Classes and objects in Java for an introduction to creating objects and arrays.

Relational operators

The relational operators impose an ordering on their operands by determining which operand is greater, lesser, and so on. These operators include greater than ( > ), greater than or equal to ( >= ), less than ( < ), and less than or equal to (

Example application: Relational operators

Listing 8 presents the source code to a RelOp application that lets you play with the relational operators.

Listing 8. Relational operators in Java (RelOp.java)

class RelOp < public static void main(String[] args) < int x = 10; System.out.println(x >10); System.out.println(x >= 10); System.out.println(x < 10); System.out.println(x >

Compile Listing 8 ( javac RelOp.java ) and run the application ( java RelOp ). You should observe the following output:

false true false true true

The final output line is interesting because it proves that a string literal (e.g., "A" ) is in fact a String object.

Shift operators

The shift operators let you shift an integral value left or right by a specific number of bit positions. These operators include left shift ( > ), and unsigned right shift ( >>> ); and are formally defined below:

Example application: Shift operators

Listing 9 presents the source code to a ShiftOp application that lets you play with the shift operators.

Listing 9. Shift operators in Java (ShiftOp.java)

class ShiftOp < public static void main(String[] args) < System.out.println(1 > 2); System.out.println(-1 >> 1); System.out.println(-1 >>> 1); > >

Compile Listing 9 ( javac ShiftOp.java ) and run the application ( java ShiftOp ). You should observe the following output:

256 2 -1 2147483647

The output reveals that bit shifting is equivalent to multiplying or dividing by multiples of 2 (but is faster). The first output line is equivalent to the value derived from 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 and the second output line is equivalent to the value derived from 8 / 4 . The final two output lines show the difference between preserving and not preserving the sign bit where negative values are concerned.

Unary minus/plus operators

The final operators that Java supports are unary minus ( - ) and unary plus ( + ). Unary minus returns the negative of its operand (e.g., -8 returns -8 and --8 returns 8), whereas unary plus returns its operand unchanged (e.g., +8 returns 8 and +-8 returns -8). Unary plus is not commonly used, but is included in Java’s set of operators for completeness.

Operator precedence and associativity

Earlier in this tutorial, I mentioned that Java’s rules of precedence (priority in order) dictate the order in which compound expressions are evaluated. For the common arithmetic operators (such as addition and multiplication), Java follows the established precedence convention of multiplication first, and then addition. The order of evaluation isn’t as clear for other operators. however. For example, how does Java evaluate 6 > 3 * 2 ? Should comparison precede multiplication, or vice-versa?

The following list shows you the precedence of Java’s operators. Operators closer to the top have higher precedence than operators lower down. In other words, operators higher up in the list are performed first. Operators that have the same precedence are listed on the same line. Also note that when the Java compiler encounters multiple operators with the same precedence in the same compound expression, it generates code to perform the operations according to their associativity, which I’ll explain next.

You won’t always want to follow this order. For example, you might want to perform addition before multiplication. Java lets you violate precedence by placing subexpressions between round brackets (parentheses). A parenthesized subexpression is evaluated first. Parentheses can be nested, in which a parenthesized subexpression can be located within a parenthesized subexpression. In this case, the innermost parenthesized subexpression is evaluated first.

During evaluation, operators with the same precedence level (such as addition and subtraction) are processed according to their associativity, meaning how operators having the same precedence are grouped when parentheses are absent. For example, 10 * 4 / 2 is evaluated as if it was (10 * 4) / 2 because * and / are left-to-right associative operators. In contrast, a = b = c = 50; is evaluated as if it was a = (b = (c = 50)); ( 50 is assigned to c , c ‘s value is assigned to b , and b ‘s value is assigned to a –all three variables contain 50) because = is a right-to-left associative operator.

Most of Java’s operators are left-to-right associative. Right-to-left associative operators include assignment, bitwise complement, cast, compound assignment, conditional, logical complement, object creation, predecrement, preincrement, unary minus, and unary plus.

Example application: Precedence and associativity

I’ve created a small application for playing with precedence and associativity. Listing 10 presents its source code.

Listing 10. Precedence and associativity in Java (PA.java)

class PA < public static void main(String[] args) < System.out.println(10 * 4 + 2); System.out.println(10 * (4 + 2)); int a, b, c; a = b = c = 50; System.out.println(a); System.out.println(b); System.out.println(c); >>

Compile Listing 10 ( javac PA.java ) and run the application ( java PA ). You should observe the following output:

42 60 50 50 50

In Listing 10, suppose I specified (a = b) = c = 50; instead of a = b = c = 50; because I want a = b to be evaluated first. How would the compiler respond — and why?

Primitive-type conversions

My previous binary and ternary operator examples presented operands having the same type (as an example, each of 6 * 5 ‘s operands is an int ). In many cases, operands will not have the same type, and the Java compiler will need to generate bytecode that converts an operand from one type to another before generating bytecode that performs the operation. For example, when confronted by 5.1 + 8 , the compiler generates bytecode to convert 32-bit integer 8 to its double precision floating-point equivalent followed by bytecode to add these double precision values. (In the example, the compiler would generate an i2d instruction to convert from int to double and then a dadd instruction to add the two double s.)

How does the compiler know which operand to convert? For primitive-type operands, its choice is based on the following widening rules, which essentially convert from a type with a narrower set of values to a type with a wider set of values:

Regarding expression 5.1 + 8 , we can see that the compiler chooses to convert 8 to a double based on the rule for converting an integer to double precision floating-point. If it converted 5.1 to an int , which is a narrower type, information would be lost because the fractional part would be effectively truncated. Therefore, the compiler always chooses to widen a type so information isn’t lost.

These rules also help to explain why, in BitwiseOp.java , the binary values resulting from expressions such as System.out.println(~x); were 32 bits long instead of 16 bits long. The compiler converts the short integer in x to a 32-bit integer value before performing bitwise complement, via iconst_m1 and ixor instructions — exclusive OR the 32-bit integer value with 32-bit integer -1 and produce a 32-bit integer result. The JVM (Java Virtual Machine) provides no sconst_m1 and sxor instructions for performing bitwise complement on short integers. Byte integers and short integers are always widened to 32-bit integers.

Widening rules in practice

Earlier, I mentioned that you would discover why 'C' - 'A' in ( grades['C' - 'A'] ) produces an integer index. Character literals 'C' and 'A' are represented in memory by their Unicode values, which are unsigned 16-bit integers. When it encounters this expression, the Java compiler generates an iconst_2 instruction, which is int value 2. In this case, no subtraction is performed because of optimization. However, if I replaced 'C' - 'A' with 'C' - base , where base is a char variable initialized to 'A' , the compiler would generate the following bytecode:

bipush 65 ; Push 8-bit Unicode value for A, which is sign-extended to 32-bit int, onto stack. istore_1 ; Pop this 32-bit value into a special int variable. . bipush 67 ; Push 8-bit Unicode value for C, which is sign-extended to 32-bit int, onto stack. iload_1 ; Push 32-bit Unicode value for A onto stack. isub ; Subtract 65 (A) from 67 (C). Push 32-bit result onto stack.

The bi in bipush stands for the byte integer type; the i in istore_1 , iload_1 , and isub stands for the 32-bit integer type. The compiler has converted the expression into an int value. It makes sense to do so because of the close relationship between character literals (really, unsigned Unicode integers) and Java’s signed integers.

In addition to the previous widening rules, Java provides a special widening rule for use with String objects (e.g., string literals). When either operand of the string concatenation operator is not a string, that operand is converted to a string before the concatenation operation is performed. For example, when confronted with "X" + 3 , the compiler generates code to convert 3 to "3" before performing the concatenation.

Using cast operators for type narrowing

Sometimes, you’ll need to deliberately narrow a type where information may be lost. For example, you’re drawing a mathematical curve with floating-point coordinates (for accuracy). Because the screen’s pixels use integer coordinates, you must convert from floating-point to integer before you can plot a pixel. In Java, we can use cast operators to narrow a type. Cast operators are available to perform the following primitive-type conversions:

For example, the (float) cast in float circumference = (float) 3.14159 * 10 * 10; is necessary to convert from double precision floating-point to floating-point.

A cast operator isn’t always necessary for the above primitive-type conversions. For example, consider conversion from 32-bit integer to 8-bit byte integer. You don’t need to supply a cast operation when assigning a 32-bit integer literal that ranges from -128 to 127 to a variable of byte integer type. For example, you could specify byte b = 100; and the compiler wouldn’t complain because no information is lost. (This is why I was previously able to specify short x = 0B0011010101110010; , where the binary literal is of 32-bit integer type, without requiring a (short) cast operator, as in short x = (short) 0B0011010101110010; .) However, if you specified int i = 2; byte b = i; , the compiler would complain because i could contain a value outside the valid range of integers that can be assigned to a byte integer variable.

Example application: Primitive-type conversions

A short application should help to clarify all of this theory. Check out Listing 11’s Convert source code.

Listing 11. Primitive-type conversions in Java (Convert.java)

class Convert < public static void main(String[] args) < float f = 1000; System.out.println("f = " + f); long l = 5000; System.out.println("l = " + l); System.out.println("'C' - 'A' = " + ('C' - 'A')); char base = 'A'; System.out.println("'C' - base f prettyprint">f = 1000.0 l = 5000 'C' - 'A' = 2 'C' - base = 2 2 25 -126 2

Conclusion

Java’s support for expressions is extensive, and there is a lot of theory to grasp. I encourage you to experiment with the example applications in this tutorial, and modify them to reinforce what you’ve learned about using operators to write compound Java expressions. Your practice will come in handy for the next tutorial, which wraps up this series on Java’s fundamental language features.