Operators in Java

Operators in Java programming language are special symbols that perform specific operations on one, two, or three operands, and then return a result.

The Increment/Decrement Operators (++/–)

Variables can be incremented or decremented by 1 using the ++ and — operators, respectively.

When the ++ and — operators follow variables, they are called post-increment and post-decrement respectively.

int a = 10;
a++; // a now equals 11
a--; // a now equals 10 again

When the ++ and — operators precede the variables the operations are called pre-increment and pre-decrement respectively.

int x = 10;
--x;   // x now equals 9
++x;   // x now equals 10

If the operator precedes the variable, the value of the expression is the value of the variable after being incremented or decremented. If the operator follows the variable, the value of the expression is the value of the variable prior to being incremented or decremented.

int x=10;
System.out.println("x=" + x + " x=" + x++ + " x=" + x); // outputs x=10 x=10 x=11
System.out.println(" x=" + x + " x=" + ++x + " x=" + x); // outputs x=11 x=12 x=12
System.out.println(" x=" + x + " x=" + x-- + " x=" + x); // outputs x=12 x=12 x=11
System.out.println(" x=" + x + " x=" + --x + " x=" + x); // outputs x=11 x=10 x=10

Be careful not to overwrite post-increments or decrements. This happens if you use a post-in/decrement operator at the end of an expression which is reassigned to the in/decremented variable itself. The in/decrement will not have an effect. Even though the variable on the left-hand side is incremented correctly, its value will be immediately overwritten with the previously evaluated result from the right-hand side of the expression:

int x = 0;
x = x++ + 1 + x++; // x = 0 + 1 + 1
// do not do this - the last increment has no effect (bug!)
System.out.println(x); // prints 2 (not 3!)

Correct:

int x = 0;
x = x++ + 1 + x; // evaluates to x = 0 + 1 + 1
x++; // adds 1
System.out.println(x); // prints 3

The Conditional Operator (?)

Syntax

{condition-to-evaluate} ? {statement-executed-on-true} : {statement-executed-on-false}

As shown in the syntax, the Conditional Operator (also known as the Ternary Operator1) uses the ? (question mark) and : (colon) characters to enable a conditional expression of two possible outcomes. It can be used to replace longer if-else blocks to return one of two values based on condition.

result = testCondition ? value1 : value2

Is equivalent to

if (testCondition) {
     result = value1;
} else {
     result = value2;
}

It can be read as β€œIf testCondition is true, set result to value1; otherwise, set result to value2”.
For example:

// get absolute value using conditional operator
a = -10;
int absValue = a < 0 ? -a : a;
System.out.println("abs = " + absValue); // prints "abs = 10"

Is equivalent to

// get absolute value using if/else loop
a = -10;
int absValue;
if (a < 0) {
    absValue = -a;
} else {
    absValue = a;
}
System.out.println("abs = " + absValue); // prints "abs = 10"

Common Usage

You can use the conditional operator for conditional assignments (like null checking).

String x = y != null ? y.toString() : ""; //where y is an object

This example is equivalent to:

String x = "";
if (y != null) {
   x = y.toString();
}

Since the Conditional Operator has the second-lowest precedence, above the Assignment Operators, there is rarely a need for use parenthesis around the condition, but parenthesis is required around the entire Conditional
Operator constructs when combined with other operators:

// no parenthesis needed for expressions in the 3 parts
10 <= a && a < 19 ? b * 5 : b * 7 
// parenthesis required 
7 * (a > 0 ? 2 : 5)

Conditional operator’s nesting can also be done in the third part, where it works more like chaining or like a switch statement.

a ? "a is true" :
b ? "a is false, b is true" :
c ? "a and b are false, c is true" :
"a, b, and c are false"
//Operator precedence can be illustrated with parenthesis:
a ? x : (b ? y : (c ? z : w))

Footnote:

1 – Both the Java Language Specification and the Java Tutorial call the (? πŸ™‚ operator the Conditional Operator. The tutorial says that it is “also known as the Ternary Operator” as it is (currently) the only ternary operator defined by Java. The “Conditional Operator” terminology is consistent with C and C++ and other languages with an equivalent operator.

The Bitwise and Logical Operators (~, &, |, ^)

The Java language provides 4 operators that perform bitwise or logical operations on integer or boolean operands.

  • The complement (~) operator is a unary operator that performs a bitwise or logical inversion of the bits of one operand;
  • The AND (&) operator is a binary operator that performs a bitwise or logical “and” of two operands;
  • The OR (|) operator is a binary operator that performs a bitwise or logical “inclusive or” of two operands;
  • The XOR (^) operator is a binary operator that performs a bitwise or logical “exclusive or” of two operands;

The logical operations performed by these operators when the operands are booleans can be summarized as follows:

AB~AA & BA | BA ^ B
001000
011011
100011
110110

Note that for integer operands, the above table describes what happens for individual bits. The operators actually
operate on all 32 or 64 bits of the operand or operands in parallel.

Operand types and result types.

The usual arithmetic conversions apply when the operands are integers. Common use-cases for the bitwise
operators

The ~ operator is used to reverse a boolean value, or change all the bits in an integer operand.

The & operator is used for “masking out” some of the bits in an integer operand. For example:

int word = 0b00101010;
int mask = 0b00000011;         // Mask for masking out all but the bottom
                              // two bits of a word
int lowBits = word & mask;    // -> 0b00000010
int highBits = word & ~mask;  // -> 0b00101000

The | operator is used to combine the truth values of two operands. For example:

int word2 = 0b01011111;
// Combine the bottom 2 bits of word1 with the top 30 bits of word2
int combined = (word & mask) | (word2 & ~mask);   // -> 0b01011110

The ^ operator is used for toggling or “flipping” bits:

int word3 = 0b00101010;
int word4 = word3 ^ mask;     // -> 0b00101001
For more examples of the use of the bitwise operators, see Bit Manipulation

The String Concatenation Operator (+)

The + symbol can mean three distinct operators in Java:

  • If there is no operand before the +, then it is the unary Plus operator.
  • If there are two operands, and they are both numeric. then it is the binary Addition operator.
  • If there are two operands, and at least one of them is a String, then it it the binary Concatenation operator.

In the simple case, the Concatenation operator joins two strings to give a third string. For example:

String s1 = "a String";
String s2 = "This is " + s1; // s2 contains "This is a String"

When one of the two operands is not a string, it is converted to a String as follows:

  • An operand whose type is a primitive type is converted as if by calling toString() on the boxed value.
  • An operand whose type is a reference type is converted by calling the operand’s toString() method. If the operand is null, or if the toString() method returns null, then the string literal “null” is used instead.

For example:

int one = 1;
String s3 = "One is " + one;          // s3 contains "One is 1"
String s4 = null + " is null";        // s4 contains "null is null"
String s5 = "{1} is " + new int[]{1}; // s5 contains something like                                  // "{} is [I@xxxxxxxx"

The explanation for the s5 example is that the toString() method on array types is inherited from java.lang.object and the behavior are to produce a string that consists of the type name and the object’s identity hashcode.

The Concatenation operator is specified to create a new String object, except in the case where the expression is a Constant Expression. In the latter case, the expression is evaluated at compile type, and its runtime value is equivalent to a string literal. This means that there is no runtime overhead in splitting a long string literal like this:

String typing = "The quick brown fox " +
"jumped over the " +
"lazy dog"; // constant expression
Optimization and efficiency

As noted above, with the exception of constant expressions, each string concatenation expression creates a new
String object. Consider this code:

public String stars(int count) {
      String res = "";
      for (int i = 0; i < count; i++) {
            res = res + "*";
      }
      return res;
}

In the method above, each iteration of the loop will create a new String that is one character longer than the previous iteration. Each concatenation copies all of the characters in the operand strings to form the new String.
Thus, stars(N) will:

  • create N new String objects, and throw away all but the last one,
  • copy N * (N + 1) / 2 characters, and
  • generate O(N^2) bytes of garbage.

This is very expensive for large N. Indeed, any code that concatenates strings in a loop is liable to have this problem. A better way to write this would be as follows:

public String stars(int count) {
      // Create a string builder with capacity 'count'
      StringBuilder sb = new StringBuilder(count);
      for (int i = 0; i < count; i++) {
      sb.append("*");
      }
      return sb.toString();
}

Ideally, you should set the capacity of the StringBuilder, but if this is not practical, the class will automatically grow the backing array that the builder uses to hold characters. (Note: the implementation expands the backing array exponentially. This strategy keeps that amount of character copying to an O(N) rather than O(N^2).)

Some people apply this pattern to all string concatenations. However, this is unnecessary because the JLS allows a Java compiler to optimize string concatenations within a single expression. For example:

String s1 = …;
String s2 = …;
String test = "Hello " + s1 + ". Welcome to " + s2 + "\n";

will typically be optimized by the bytecode compiler to something like this;

StringBuilder tmp = new StringBuilder();
tmp.append("Hello ")
tmp.append(s1 == null ? "null" + s1);
tmp.append("Welcome to ");
tmp.append(s2 == null ? "null" + s2);
tmp.append("\n");
String test = tmp.toString();

(The JIT compiler may optimize that further if it can deduce that s1 or s2 cannot be null.) But note that this
optimization is only permitted within a single expression.

In short, if you are concerned about the efficiency of string concatenations:

  • Do hand-optimize if you are doing repeated concatenation in a loop (or similar).
  • Don’t hand-optimize a single concatenation expression.

The Arithmetic Operators (+, -, *, /, %)

The Java language provides 7 operators that perform arithmetic on integer and floating point values.

  • There are two + operators:
    • The binary addition operator adds one number to another one. (There is also a binary + operator that performs string concatenation. That is described in a separate example.)
    • The unary plus operator does nothing apart from triggering numeric promotion (see below)
  • There are two – operators:
    • The binary subtraction operator subtracts one number from another one.
    • The unary minus operator is equivalent to subtracting its operand from zero.
  • The binary multiply operator (*) multiplies one number by another.
  • The binary divide operator (/) divides one number by another.
  • The binary remainder1 operator (%) calculates the remainder when one number is divided by another.
  1. This is often incorrectly referred to as the “modulus” operator. “Remainder” is the term that is used by the JLS. “Modulus” and “remainder” are not the same thing.

Operand and result types, and numeric promotion

The operators require numeric operands and produce numeric results. The operand types can be any primitive numeric type (i.e. byte, short, char, int, long, float or double) or any numeric wrapper type define in java.lang;
i.e. (Byte, Character, Short, Integer, Long, Float or Double.

The result type is determined base on the types of the operand or operands, as follows:

  • If either of the operands is a double or Double, then the result type is double.
  • Otherwise, if either of the operands is a float or Float, then the result type is float.
  • Otherwise, if either of the operands is a long or Long, then the result type is long.
  • Otherwise, the result type is int. This covers byte, short and char operands as well as `int.

The result type of the operation determines how the arithmetic operation is performed, and how the operands are handled

  • If the result type is double, the operands are promoted to double, and the operation is performed using a 64-bit (double precision binary) IEE 754 floating-point arithmetic.
  • If the result type is float, the operands are promoted to float, and the operation is performed using 32-bit (single precision binary) IEE 754 floating-point arithmetic.
  • If the result type is long, the operands are promoted to long, and the operation is performed using 64-bit signed twos-complement binary integer arithmetic.
  • If the result type is int, the operands are promoted to int, and the operation is performed using 32-bit signed twos-complement binary integer arithmetic.

Promotion is performed in two stages:

  • If the operand type is a wrapper type, the operand value is unboxed to a value of the corresponding primitive type.
  • If necessary, the primitive type is promoted to the required type:
    • Promotion of integers to int or long is loss-less.
    • The promotion of float to double is loss-less.
    • The promotion of an integer to a floating-point value can lead to loss of precision. The conversion is performed using IEE 768 “round-to-nearest” semantics.
The meaning of division

The / operator divides the left-hand operand n (the dividend) and the right-hand operand d (the divisor) and
produces the result q (the quotient).

Java integer division rounds towards zero. The JLS Section 15.17.2 specifies the behavior of Java integer division as
follows: The quotient produced for operands n and d is an integer value q whose magnitude is as large as possible
while satisfying |d β‹… q| ≀ |n|. Moreover, q is positive when |n| β‰₯ |d| and n and d have the same sign,
but q is negative when |n| β‰₯ |d| and n and d have opposite signs.

There are a couple of special cases:

  • If the n is MIN_VALUE, and the divisor is -1, then integer overflow occurs and the result is MIN_VALUE. No exception is thrown in this case.
  • If d is 0, then `ArithmeticException is thrown.

Java floating-point division has more edge cases to consider. However, the basic idea is that the result q is the value that is closest to satisfying d. q = n.

The floating-point division will never result in an exception. Instead, operations that divide by zero results in an INF and NaN values; see below.

The meaning of remainder

Unlike C and C++, the remainder operator in Java works with both integer and floating-point operations.

For integer cases, the result of a % b is defined to be the number r such that (a / b) * b + r is equal to a, where /, * and + are the appropriate Java integer operators. This applies in all cases except when b is zero. That case,
remainder results in an ArithmeticException.

It follows from the above definition that a % b can be negative only if a is negative, and it is positive only if a is positive. Moreover, the magnitude of a % b is always less than the magnitude of b.

Floating-point remainder operation is a generalization of the integer case. The result of a % b is the remainder r is defined by the mathematical relation r = a – (b β‹… q) where:

  • q is an integer,
  • it is negative only if a / b is negative a positive only if a / b is positive, and
  • its magnitude is as large as possible without exceeding the magnitude of the true mathematical quotient of a and b.

The floating-point remainder can produce INF and NaN values in edge-cases such as when b is zero; see below. It will not throw an exception.

Important note:

The result of a floating-point remainder operation as computed by % is not the same as that produced by the remainder operation defined by IEEE 754. The IEEE 754 remainder may be computed using the Math.IEEEremainder library method.

Integer Overflow

Java 32 and 64-bit integer values are signed and use twos-complement binary representation. For example, the range of numbers representable as (32 bit) int -231 through +231 – 1.

When you add, subtract, or multiple two N bit integers (N == 32 or 64), the result of the operation may be too large to represent as an N bit integer. In this case, the operation leads to integer overflow, and the result can be computed as follows:

  • The mathematical operation is performed to give an intermediate twos-complement representation of the entire number. This representation will be larger than N bits.
  • The bottom 32 or 64 bits of the intermediate representation is used as the result.

It should be noted that integer overflow does not result in exceptions under any circumstances.

Floating point INF and NAN values

Java uses IEE 754 floating-point representations for float and double. These representations have some special values for representing values that fall outside of the domain of Real numbers:

  • The “infinite” or INF values denote numbers that are too large. The +INF value denotes numbers that are too large and positive. The -INF value denotes numbers that are too large and negative.
  • The “indefinite” / “not a number” or NaN denote values resulting from meaningless operations.

The INF values are produced by floating operations that cause overflow, or by division by zero.

The NaN values are produced by dividing zero by zero, or computing zero remainder zero.

Surprisingly, it is possible perform arithmetic using INF and NaN operands without triggering exceptions. For
example:

  • Adding +INF and a finite value gives +INF.
  • Adding +INF and +INF gives +INF.
  • Adding +INF and -INF gives NaN.
  • Dividing by INF gives either +0.0 or -0.0.
  • All operations with one or more NaN operands give NaN.

For full details, please refer to the relevant subsections of JLS 15. Note that this is largely “academic”. For typical calculations, an INF or NaN means that something has gone wrong; e.g. you have incomplete or incorrect input data, or the calculation has been programmed incorrectly.

Leave a Comment