Java 101: Foundations

Java 101: Evaluate Java expressions with operators

Learn about simple expressions and operator-based compound expressions

1 2 3 4 Page 2
Page 2 of 4

Listing 3. 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, I present the binary equivalent of the previous operators below -- you'll understand why each binary number is 32 bits long instead of 16 when I discuss 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 article. The cast operator is also important for inheritance, which is a subject for another day.

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:

  • Conditional: Given operand1 ? operand2 : operand3, where operand1 must be of Boolean type, return operand2 when operand1 is true or operand3 when operand1 is false. The types of operand2 and operand3 must agree. Example: boolean status = true; int statusInt = (status) ? 1 : 0;
  • Conditional AND: Given operand1 && operand2, where each operand must be of Boolean type, return true when both operands are true. Otherwise, return false. When operand1 is false, operand2 isn't evaluated because the entire expression will be false anyway. This is known as short-circuiting. Example: true && false
  • Conditional OR: Given operand1 || operand2, where each operand must be of Boolean type, return true when at least one operand is true. Otherwise, return false. When operand1 is true, operand2 isn't evaluated because the entire expression will be true anyway. This is known as short-circuiting. Example: true || false

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

Listing 4. 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:

  • Equality: Given operand1 == operand2, where both operands must be comparable (you cannot compare an integer with a Boolean value, for example), compare both operands for equality. Return true when these operands are equal. Otherwise, return false. Example: 'A' == 'a'
  • Inequality: Given operand1 != operand2, where both operands must be comparable (you cannot compare a floating-point value with a string literal, for example), compare both operands for inequality. Return true when these operands are not equal. Otherwise, return false. Example: 'A' != 'a'

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.

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

Listing 5. 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. I'll say more about this in the next article.

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. I will have more to say about string comparison when I explore the String class in a future article.

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:

  • Logical AND: Given operand1 & operand2, where each operand must be of Boolean type, return true when both operands are true. Otherwise, return false. In contrast to conditional AND, logical AND doesn't perform short-circuiting. Example: true & false
  • Logical complement: Given !operand, where operand must be of Boolean type, flip operand's value (true to false or false to true) and return the result. Example: !false
  • Logical exclusive OR: Given operand1 ^ operand2, where each operand must be of Boolean type, return true when one operand is true and the other operand is false. Otherwise, return false. Example: true ^ false
  • Logical inclusive OR: Given operand1 | operand2, where each operand must be of Boolean type, return true when at least one operand is true. Otherwise, return false. In contrast to conditional OR, logical inclusive OR doesn't perform short-circuiting. Example: true | false

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

Listing 6. 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.

I'll have more to say about classes, objects, class/object members, and arrays in future articles.

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:

  • 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.

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

Listing 7. 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:

1 2 3 4 Page 2
Page 2 of 4