Java 11 Developer Certification - Java Operators

October 14, 2020

What we are covering in this lesson

  1. What are Java Operators
  2. Unary Operators
  3. Binary Operators
  4. Ternary Operators
  5. Special Cases

What are Java Operators

Java operators are symbols that are used to perform mathematical or logical manipulations. They can be classified based on the number of operands the operator has, unary, binary, ternary OR based on the type of operation it performs.

Important things to note about operators

  • Always check for the precendence of the operators. Unary operators are evaluated before binary operators.
  • Among the unary operators, the postfix increment and decrement operators have the highest precedence.
  • Unary operators, with the exception of the prefix and postfix operators, promote the variable to and int if it’s smaller than an int.
  • If all operators have the same precedence, the expression will be evaluated from left to right, with the exception of the simple and compound assignment operators, which are evaluated from right to left.
  • Parentheses always take the highest precedence.

Unary Operators

Expressions with unary operators group right to left, so -~x is equal to -(~x). Lets have a look at some of the unary operators

  • Prefix Decrement - --x will decrement the value of x before expression in evaluated
  • Postfix Decrement - x-- will evaluate the expression and then decrement the value of x
  • Prefix Increment - ++x will increment the value of x before expression in evaluated
  • Postfix Increment - x++ will evaluate the expression and then increment the value of x Note: it is possible in case of postfix increment and decrement that the variable might go out of scope
  • Unary Minus - -x returns negated value of x without changing its value
  • Unary Plus - +x this is allowed but it does nothing to the value of x Note: If x is a datatype smaller than int, then it will get promoted to int
  • Logical Complement Operator - !x returns the complement of a boolean value
  • Bitwise Complement Operator - ~x turns 0 to 1 or 1 to 0 Note: `~x equals (-x)-1
  • Cast Operator - (int) x used to cast the value of x to any datatype

It should always be remembered that the postfix increment and decrement operators do not change the value of its operand until the expression it is operating on is complete OR the same operand is used again in the same statement. If the operation in interrupted, the postfix increment or decrement may not be actually applied. Postfix and prefix operators can be stand alone statements, meaning they are directly incrementing or decrementing the value in the variable. If we use unary minus for example, it doesnt change the value of the varible it is operated upon.

package maxCode.online.operators;

public class UnaryOperators {
	public static void main(String[] args) {
        int a = 1;
        System.out.println("value of a is " + a);
        /* ++a and a++ behave the same if not used in any complex expression */
        ++a;  // a = a+1
        System.out.println("a after ++a = " + a);
        a = 1;
        System.out.println("value of a is " + a);
        a++;  // a = a+1
        System.out.println("a after a++ = " + a);

        /* difference in pre and postfix operators */
        a = 1;
        System.out.println("value of a is " + a);
        System.out.println("a after ++a = " + ++a);
        a = 1;
        System.out.println("value of a is " + a);
        System.out.println("a after a++ = " + a++);
        System.out.println("And now the value of a is: " + a);

        /* postfix increment in variable declaration */
        a = 1;
        System.out.println("value of a is " + a);
        int a2 = a++;
        System.out.println("The value of a is " + a);
        System.out.println("The value of a2 is " + a2);

        /* postfix in an expression */
        a = 1;
        System.out.println("value of a is " + a);
        if (++a == 1) {
            System.out.println("Inside if statement with value of a = " + a);
        }

        /* Prefix operator in loop */
        int b = 5;
        System.out.println("value of b is " + b);
        int i = 0;
        while (--b > 0) {  // Use a prefix decrement
            i++;
        }
        System.out.println("Prefix decrement operator used, loopiterations = " + i + ", b = " + b);

        /* Postfix operator in loop */
        b = 5;
        System.out.println("value of b is " + b);
        i = 0;
        while (b-- > 0) {  // Use a postfix decrement
            i++;
        }
        System.out.println("Postfix decrement operator used, loopiterations = " + i + ", b = " + b);
	}
}

Output

value of a is 1
a after ++a = 2
value of a is 1
a after a++ = 2
value of a is 1
a after ++a = 2
value of a is 1
a after a++ = 1
And now the value of a is: 2
value of a is 1
The value of a is 2
The value of a2 is 1
value of a is 1
value of b is 5
Prefix decrement operator used, loopiterations = 4, b = 0
value of b is 5
Postfix decrement operator used, loopiterations = 5, b = -1

Now lets have a look at unary minus and plus. The unary minus returns a negative value if the value in the operand is positive, and positive value if the operand value is negative. The unary plus returns the same sign as the operand, and has no effect on the value. Both operators will promote the value to an int. The value of the operand reference itself stays unchanged. The expression’s value must be returned to a variable or used in an expression in case of unary plus or minus.

The bitwise complement operator flips the bit for the entire value of the variable. So the binary literal value of the integer zero, gets every bit flipped to one, making it’s integer value minus one. The logical complement operator only works on a Boolean, and changes false to true.

package maxCode.online.operators;

public class UnaryOperators2 {
	public static void main(String[] args) {
        // Unary Minus
        int a = 1, b = -a;

        // the value of actual operand a does not change
        System.out.println("a = " + a + "; b = " + b);
        
        a = -1;
        b = -a;
        System.out.println("a = " + a + "; b = " + b);

        //  Unary Plus
        a = 1;
        b = +a;  // Not to be mistaken for b+=a;
        System.out.println("a = " + a + "; b = " + b);
        
        a = -1;
        b = +a;
        System.out.println("a = " + a + "; b = " + b);

        // Bitwise Complement Operator ~x
        // when value is x then ~x = (-x)-1;
        int bin1 = 0b00000000_00000000_00000000_00000000;
        int bin2 = ~bin1;

        System.out.println("bin1 = " + bin1 +
                " (" + Integer.toBinaryString(bin1) + "), " +
                "bin2 = " + bin2 +
                " (" + Integer.toBinaryString(bin2) + ")");

        // Logical Complement Operator !x
        boolean myBoolean = false;
        boolean newBoolean = !myBoolean;
        System.out.println("myBoolean = " + myBoolean +
                ", the opposite is = " + newBoolean);
    }
}
a = 1; b = -1
a = -1; b = 1
a = 1; b = 1
a = -1; b = -1
bin1 = 0 (0), bin2 = -1 (11111111111111111111111111111111)
myBoolean = false, the opposite is = true

Binary Operators

Binary operators, as the name suggests, operate on 2 operands. Some examples of binary operators are mentioned below

  • Multiplicative operators (*, /, %)
  • Additive operators (+, -)
  • Shift operators (<<, >>, >>>)
  • Relational operators (<, >, <=, >=, instanceof)
  • Equality operators (==, !=)
  • Bitwise and Logical operators (&, ^, |)
  • Conditional AND (&&) - evaluates right hand operand only if left hand operand is true
  • Conditional OR (||) - evaluates right hand operand only if left hand operand is false
  • Conditional operator (?:) - This is a ternary operator
  • Assignment operators (=, +=, *=, etc) - (a=b=c means a=(b=c) so c assigned to b and then b assigned to a)
  • Lambda operator (->)

For each group, precendence is equal among them, and group left to right. But for bitwise and logical operators, & (AND) has highest precendence and | (OR) has lowest Unary operators always have higher precedence than binary ones. Just like unary operator, if any operand type is smaller than int, both operands will be automatically promoted to int. If any operand is larger than int, then it will be promoted to the larger type. So any operation on numeric values will never result in a value which is smaller than int.

Multiplicative operators have precedence over Additive operators, so they will be evaluated first. So in effect, a + e * b - f / c % b can be evaluated as a + (e * b) - (f / c) % b.

Modulus operator returns the remainder of the division operation. So 10 % 5 will return 0 and 10 % 3 will return 1. Java also accepts floating point operands for modulus operator (unline C/C++ which only accepts int). There is one difference in int and floating operands, if we do a 10 % 0 (effectively divide by 0 and get the remainder), int operands line will throw ArithmeticException for / by zero but floating operands line will return a NaN or Not A Number value, without throwing any exception.

Lets look at some code to understand the other binary operators.

package maxCode.online.operators;

public class BinaryOperators {
	public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = 3;
        int d = 4;

        System.out.println("--- Shift Operators ---");
        // left shift (<<)
        // bit pattern shifted left by 2 places (right operand = 2)
        // 0b0000_0001 becomes 0b0000_0100
        System.out.println("result of left shift (00000001 << 2 ) = "
                + String.format("%8s",Integer.toBinaryString(0b00000001 << 2)).replace(' ', '0'));

        // right shift (>>)
        // bit pattern shifted left by 3 places (right operand = 3)
        // 0b10001000 becomes 0b00010001
        System.out.println("result of right shift (10001000 >> 3 ) = "
                + String.format("%8s",Integer.toBinaryString(0b10001000 >> 3)).replace(' ', '0'));

        // >>>  right shift unsigned
        System.out.println("result of unsigned right shift (" +
                "10000010_00000010_00000010_00000010 >>> 1 ) = "
                + String.format("%32s",Integer.toBinaryString(
                0b10000010_00000010_00000010_00000010 >>> 1)).replace(' ', '0'));

        //  Compare right shift unsigned to right shift results
        System.out.println("result of signed right shift   (" +
                "10000010_00000010_00000010_00000010 >>  1 ) = "
                + String.format("%32s",Integer.toBinaryString(
                0b10000010_00000010_00000010_00000010 >> 1)).replace(' ', '0'));

        // Relationship operators <, <=
        // | - logical or
        // || - conditional logical or
        System.out.println("\nResults using relationship operators" +
                " and logical or operators (| ||) ");
        c = 0;
        d = 0;
        if ((c++ <= d) | (++c < d)) {
            System.out.println("Evaluation [(e++ <= f) | (++e < f)] met");
        }
        System.out.println("Logical | (OR) will evaluate both expressions: e = "
                + c + ", and f = " + d);

        c = 0;
        d = 0;
        if ((c++ <= d) || (++c < d)) {
            System.out.println("Evaluation [(e++ <= f) || (++e < f)] met");
        }
        System.out.println("Conditional Logical || evaluates only first " +
                "expression if it evaluates to true: e = "
                + c + ", and f = " + d);

        c = 0;
        d = 0;
        if ((c++ < d) || (++c <= d)) {
            System.out.println("Evaluation [(e++ <= f) || (++e < f)] met");
        }
        System.out.println("Conditional Logical || (OR) will evaluate" +
                " both expressions ONLY if first expression is false : e = "
                + c + ", and f = " + d);

        System.out.println("\nResults using relationship operators" +
                " and logical or operators (& &&) ");

        a = 0;
        b = 10;
        if ((++a > b) & (++a >= b)) {
            System.out.println("Evaluation [(++a > b) & (++a >= b)] met");
        }

        System.out.println("Logical & (AND) will evaluate both expressions: a = "
                + a + ", and b = " + b);

        a = 0;
        b = 10;
        if ((++a > b) && (++a >= b)) {
            System.out.println("Evaluation [(++a > b) && (++a >= b)] met");
        }

        System.out.println("Conditional && (AND) will evaluate only first " +
                "expression if it evaluates to false: a = "
                + a + ", and b = " + b);

        a = 0;
        b = 0;
        if ((++a > b) && (++a >= b)) {
            System.out.println("Evaluation [(++a > b) && (++a >= b)] met");
        }

        System.out.println("Conditional && (AND) will evaluate both " +
                "expressions if first evaluates to true: a = "
                + a + ", and b = " + b);
    }
}

Output

--- Shift Operators ---
result of left shift (00000001 << 2 ) = 00000100
result of right shift (10001000 >> 3 ) = 00010001
result of unsigned right shift (10000010_00000010_00000010_00000010 >>> 1 ) = 01000001000000010000000100000001
result of signed right shift   (10000010_00000010_00000010_00000010 >>  1 ) = 11000001000000010000000100000001

Results using relationship operators and logical or operators (| ||) 
Evaluation [(e++ <= f) | (++e < f)] met
Logical | (OR) will evaluate both expressions: e = 2, and f = 0
Evaluation [(e++ <= f) || (++e < f)] met
Conditional Logical || evaluates only first expression if it evaluates to true: e = 1, and f = 0
Conditional Logical || (OR) will evaluate both expressions ONLY if first expression is false : e = 2, and f = 0

Results using relationship operators and logical or operators (& &&) 
Logical & (AND) will evaluate both expressions: a = 2, and b = 10
Conditional && (AND) will evaluate only first expression if it evaluates to false: a = 1, and b = 10
Evaluation [(++a > b) && (++a >= b)] met
Conditional && (AND) will evaluate both expressions if first evaluates to true: a = 2, and b = 0

Important things to note

  • Bitwise OR bit is 1 if one or the other operand bit is 1.
  • Bitwise AND bit is 1 if both operand bits are one.
  • Bitwise XOR bit is 0 if both operand bits are of the same value, otherwise it’s one.
  • The assignment operators, are not promoting the results of the compound assignments, so you can use these operators on smaller than int variables, as we can see in the below java code.
  • All binary operators promote results at a minimum to int.
package maxCode.online.operators;

public class BinaryOperators2 {
	public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = 1;

        // == checks equality of values for primitive data types
        if (a == c) {
            System.out.println("a and c primitive values are equal");
        }
        
        if (a != b) {
            System.out.println("a and b are not equal because primitive data values are not equal");
        }

        // == checks equality of String literals or String objects
        String s1 = "hello";
        String s2 = new String(s1);
        String s3 = s2.intern();
        if (s1 == s3) {
            System.out.println("Strings are equal if they are interned or are String literals");
        }
        if (s1 != s2) {
            System.out.println("Otherwise String objects are not equal");
        }

        Object o1 = s1;
        Object o2 = s1;
        Object o3 = new String(s1);
        if (o1 == o2) {
            System.out.println("Objects are equal if they reference same object");
        }
        if (o1 != o3) {
            System.out.println("Otherwise  objects are not equal");
        }
        System.out.println("\n--- Bitwise Operators, AND, OR, XOR ---");

        System.out.println("result of bitwise AND (0b0000_0000 & 0b1111_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b0000_0000 & 0b1111_1111)).replace(' ', '0'));

        System.out.println("result of bitwise AND (0b1111_0000 & 0b1111_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b1111_0000 & 0b1111_1111)).replace(' ', '0'));

        System.out.println("result of bitwise OR (0b0000_0000 | 0b1111_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b0000_0000 | 0b1111_1111)).replace(' ', '0'));

        System.out.println("result of bitwise OR (0b0000_0000 | 0b0000_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b0000_0000 | 0b0000_1111)).replace(' ', '0'));

        System.out.println("result of bitwise XOR (0b0000_0000 ^ 0b1111_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b0000_0000 ^ 0b1111_1111)).replace(' ', '0'));

        System.out.println("result of bitwise XOR (0b0000_1111 ^ 0b0000_1111) = "
                + String.format("%8s", Integer.toBinaryString(
                0b0000_1111 ^ 0b0000_1111)).replace(' ', '0'));

        System.out.println("\nResults from assignment operators");
        // Assignment Operator
        byte b1 = 2;
        byte b2 = 2;
        byte b3 = 5;

        // Multiplicative Assignments, note that variables NOT promoted
        b1 *= 2;
        System.out.println("b1 (after b1 *= 2 ) = " + b1);
        b2 /= 2;
        System.out.println("b2 (after b2 /= 2) = " + b2);
        b3 %= 2;
        System.out.println("b3 (after b3 %= 2) = " + b3);

        // Additive Assignments, note that variables NOT promoted
        b1 = 2;
        b2 = 2;
        b1 += 2;
        System.out.println("b1 (after b1 += 2) = " + b1);
        b2 -= 2;
        System.out.println("b2 (after b2 -= 2) = " + b2);

        // Assignment operators with bit shift
        b1 = 16;
        b2 = 16;
        b3 = 16;
        b1 <<= 1;
        System.out.println("b1 (after b1 <<= 1) = " + b1);
        b2 >>= 1;
        System.out.println("b2 (after b2 >>= 1) = " + b2);
        b3 >>>= 1;
        System.out.println("b3 (after b3 >>>= 1) = " + b3);

        // Assignment operators with bit and, xor, or
        b1 = 0b0000;
        b1 &= 0b1111;
        System.out.println("b1 (after b1 &= 0b1111 ) = " + b1);
        b1 |= 0b1111;
        System.out.println("b1 (after b1 |= 0b1111 ) = " + b1);
        b1 ^= 15;
        System.out.println("b1 (after b1 ^= 15 )= " + b1);

        // This code results in an overflow
        byte testByte = 127;
        testByte += 1;
        System.out.println("testByte = " + testByte);

        // This code results in a compiler error, shows incompatible types
        // byte testByte2 = 127;
        // testByte2 = testByte2 + 1;
        // System.out.println("testByte2 = " + testByte2);
    }
}

Output

a and c primitive values are equal
a and b are not equal because primitive data values are not equal
Strings are equal if they are interned or are String literals
Otherwise String objects are not equal
Objects are equal if they reference same object
Otherwise  objects are not equal

--- Bitwise Operators, AND, OR, XOR ---
result of bitwise AND (0b0000_0000 & 0b1111_1111) = 00000000
result of bitwise AND (0b1111_0000 & 0b1111_1111) = 11110000
result of bitwise OR (0b0000_0000 | 0b1111_1111) = 11111111
result of bitwise OR (0b0000_0000 | 0b0000_1111) = 00001111
result of bitwise XOR (0b0000_0000 ^ 0b1111_1111) = 11111111
result of bitwise XOR (0b0000_1111 ^ 0b0000_1111) = 00000000

Results from assignment operators
b1 (after b1 *= 2 ) = 4
b2 (after b2 /= 2) = 1
b3 (after b3 %= 2) = 1
b1 (after b1 += 2) = 4
b2 (after b2 -= 2) = 0
b1 (after b1 <<= 1) = 32
b2 (after b2 >>= 1) = 8
b3 (after b3 >>>= 1) = 8
b1 (after b1 &= 0b1111 ) = 0
b1 (after b1 |= 0b1111 ) = 15
b1 (after b1 ^= 15 )= 0
testByte = -128

Ternary operators

Format for a ternary operator is operand1 ? operand2 : operand3. It is usually compared to if statement, but it is an operator and not a statement and so used in some expression or to return value to a variable. The first operand must be a boolean value or be an expression whose result is a boolean value. The other operands datatypes must be a common type. If operand2 and operand3 are of different datatypes, we must assign the returned value to an Object type variable.

  • If operand1 evaluates to true, then the resulting value will be operand2.
  • If operand1 evaluates to false, then the resulting value will be operand3.

If operand two and operand three are both expressions, only one of the expressions is ever evaluated, based on the value of operand one. It will never be the case that both expressions are evaluated. It can be pretty much self explanatory by the below java code.

package maxCode.online.operators;

public class TernaryOperator {
    public static void main(String[] args) {
        // The value returned from this ternary operation is a boolean.
        boolean hasArguments = (args.length == 0) ? false : true;
        System.out.println("Result of Example 1 = " + hasArguments);

        // Value returned from ternary operation is primitive data
        boolean b = true;
        int result = (b && (hasArguments && args[0].equals("10"))) ? 10 : 0;
        System.out.println("Result of Example 2 = " + result);

        // Value returned from ternary operation is either an Integer or String
        Object objectResult = (b && (hasArguments && args[0].equals("10"))) ? 10 : "Not ten";
        System.out.println("Result of Example 3 = " + objectResult);

        // Expressions only evaluated in the one of the cases
        int x = 0;
        int y = 0;
        int newResult = (b && (hasArguments && args[0].equals("10"))) ? x++ : y++;
        System.out.println("Result of Example 4 = " + newResult + ", x = " + x + ", y = " + y);
    }
}

Output

Result of Example 1 = false
Result of Example 2 = 0
Result of Example 3 = Not ten
Result of Example 4 = 0, x = 0, y = 1

Keep in mind the outputs mentioned above will be displayed only if we do not pass any arguments. We can pass different set of arguments to get a different output. The last output shows that since we didnt pass any arguments, operand1 returned false and only y (operand3) got incremented, with no change in x value.

Special Cases

Lets have a look at the below code to explore some special scenarios in case of operators.

package maxCode.online.operators;

public class OperatorsSpecial {
	public static void main(String[] args) {
        int number = 10;
        int result = 0;
        
        // result = --number - number--
        // result always resolves to zero
        for (int i = 10; i <= 50; i += 10) {
            number = i;
            result = --number - number--;   //9 - 9 = 0
            System.out.println("i = " + i + ", number = " + number + ", result = " + result);
        }

        System.out.println();
        // int result = number-- - --number;
        // result is always the number 2
        for (int i = 10; i <= 50; i += 10) {
            number = i;
            result = number-- - --number;   //10 - 8 = 2
            System.out.println("i = " + i + ", number = " + number + ", result = " + result);
        }

        System.out.println();
        for (int i = 10; i <= 20; i += 10) {
            number = i;
            result = number-- - number++ * --number;	//10 - 9 * 9 = -71
            System.out.println("i = " + i + ", number = " + number + ", result = " + result);
        }

        System.out.println();
        // (number--) == (number += 1)
        // evaluates to true!
        number = 10;
        boolean isEqual = (number--) == (number += 1);
        System.out.println("isEqual = " + isEqual + ", for number = " + number);

        System.out.println();
        // number = number--; here value in number stays unchanged
        number = 10;
        number = number--;
        System.out.println("number = " + number);
    }
}

Output

i = 10, number = 8, result = 0
i = 20, number = 18, result = 0
i = 30, number = 28, result = 0
i = 40, number = 38, result = 0
i = 50, number = 48, result = 0

i = 10, number = 8, result = 2
i = 20, number = 18, result = 2
i = 30, number = 28, result = 2
i = 40, number = 38, result = 2
i = 50, number = 48, result = 2

i = 10, number = 9, result = -71
i = 20, number = 19, result = -341

isEqual = true, for number = 10

number = 10

Looking at the above outputs, we can safely say that

  • --number - number-- will always be 0 since prefix operator reduces the number value and the same number is getting subtracted later due to postfix usage.
  • number-- - --number will always be 2 since postfix operator will update the value after expression and prefix will reduce the new reduces number by one more time, so a difference of 2.
  • (number--) == (number+=1) will always be true, again due to postfix usage.
  • number = number-- does not change the value of number as we are assigning the result of postfix operator to the same number.

The operators can be a bit confusing when used with loops or post/prefix operators. Best way to understand it is to try and practice as much as possible!