Java Language Basics

Java 101: Evaluate Java expressions with operators

A complete guide to the Java operator types and writing Java expressions in Java 12

1 2 3 4 Page 3
Page 3 of 4

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:

  • Multiplication: Given operand1 * operand2, where each operand must be of character or numeric type, multiply operand1 by operand2 and return the product. Example: 4 * 3.
  • Division: Given operand1 / operand2, where each operand must be of character or numeric type, divide operand1 by operand2 and return the quotient. Example: 12 / 3.
  • Remainder: Given operand1 % operand2, where each operand must be of character or numeric type, divide operand1 by operand2 and return the remainder. Also known as the modulus operator. Example: 12 % 3.

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:

  • Dividing a floating-point/double precision floating-point value by 0 causes the operator to return one of the following special values: +infinity (the dividend is positive), -infinity (the dividend is negative), or NaN -- Not a Number -- (the dividend and divisor are both 0).
  • Dividing an integer value by integer 0 causes the operator to throw an ArithmeticException object. We'll explore exceptions in a future Java 101 tutorial.

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:

  • Given new identifier(argument list), allocate memory for object and call constructor specified as identifier(argument list). Example: new String("ABC").
  • Given new identifier[integer size], allocate a one-dimensional array of values. Example: new int[5].

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.

We'll dive into creating objects and arrays in a future tutorial.

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 (<=). Type checking (instanceof) is also considered to be relational. These operators are formally defined below:

  • Greater than: Given operand1 > operand2, where each operand must be of character or numeric type, return true when operand1 is greater than operand2. Otherwise, return false. Example: 65.3 > 22.5.
  • Greater than or equal to: Given operand1 >= operand2, where each operand must be of character or numeric type, return true when operand1 is greater than or equal to operand2. Otherwise, return false. Example: 0 >= 0.
  • Less than: Given operand1 < operand2, where each operand must be of character or numeric type, return true when operand1 is less than operand2. Otherwise, return false. Example: x < 15.
  • Less than or equal to: Given operand1 <= operand2, where each operand must be of character or numeric type, return true when operand1 is less than or equal to operand2. Otherwise, return false. Example: 0 <= 0.
  • Type checking: Given operand1 instanceof operand2, where operand1 is an object and operand2 is a class (or other user-defined type), return true when operand1 is an instance of operand2. Otherwise, return false.

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 <= 10);
      System.out.println("A" instanceof String);
   }
}

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 (<<), signed right shift (>>), and unsigned right shift (>>>); and are formally defined below:

  • Left shift: Given operand1 << operand2, where each operand must be of character or integer type, shift operand1's binary representation left by the number of bits that operand2 specifies. For each shift, a 0 is shifted into the rightmost bit and the leftmost bit is discarded. Only the five low-order bits of operand2 are used when shifting a 32-bit integer (to prevent shifting more than the number of bits in a 32-bit integer). Only the six low-order bits of operand2 are used when shifting a 64-bit integer (to prevent shifting more than the number of bits in a 64-bit integer). The shift preserves negative values. Furthermore, it's equivalent to (but faster than) multiplying by a multiple of 2. Example: 3 << 2.
  • Signed right shift: Given operand1 >> operand2, where each operand must be of character or integer type, shift operand1's binary representation right by the number of bits that operand2 specifies. For each shift, a copy of the sign bit (the leftmost bit) is shifted to the right and the rightmost bit is discarded. Only the five low-order bits of operand2 are used when shifting a 32-bit integer (to prevent shifting more than the number of bits in a 32-bit integer). Only the six low-order bits of operand2 are used when shifting a 64-bit integer (to prevent shifting more than the number of bits in a 64-bit integer). The shift preserves negative values. Furthermore, it's equivalent to (but faster than) dividing by a multiple of 2. Example: -5 >> 2.
  • Unsigned right shift: Given operand1 >>> operand2, where each operand must be of character or integer type, shift operand1's binary representation right by the number of bits that operand2 specifies. For each shift, a zero is shifted into the leftmost bit and the rightmost bit is discarded. Only the five low-order bits of operand2 are used when shifting a 32-bit integer (to prevent shifting more than the number of bits in a 32-bit integer). Only the six low-order bits of operand2 are used when shifting a 64-bit integer (to prevent shifting more than the number of bits in a 64-bit integer). The shift doesn't preserve negative values. Furthermore, it's equivalent to (but faster than) dividing by a multiple of 2. Example: 42 >>> 2.

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 << 8);
      System.out.println(8 >> 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.

  • Array index, member access, method call, postdecrement, postincrement
  • Bitwise complement, cast, logical complement, object creation, predecrement, preincrement, unary minus, unary plus
  • Division, multiplication, remainder
  • Addition, string concatenation, subtraction
  • Left shift, signed right shift, unsigned right shift
  • Greater than, greater than or equal to, less than, less than or equal to, type checking
  • Equality, inequality
  • Bitwise AND, logical AND
  • Bitwise exclusive OR, logical exclusive OR
  • Bitwise inclusive OR, logical inclusive OR
  • Conditional AND
  • Conditional OR
  • Conditional
  • Assignment, compound assignment

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 doubles.)

1 2 3 4 Page 3
Page 3 of 4