Using Java operators

Bruce Eckel’s Thinking in Java Contents | Prev | Next

An
operator takes one or more arguments and produces a new value. The arguments
are in a different form than ordinary method calls, but the effect is the same.
You should be reasonably comfortable with the general concept of operators from
your previous programming experience. Addition (
+),
subtraction and unary minus (
),
multiplication (
*),
division (
/)
and assignment (
=)
all work much the same in any programming language.

Precedence

Operator
precedence defines how an expression evaluates when several operators are
present. Java has specific rules that determine the order of evaluation. The
easiest one to remember is that multiplication and division happen before
addition and subtraction. Programmers often forget the other precedence rules,
so you should use parentheses to make the order of evaluation explicit. For
example:

A
= X + Y - 2/2 + Z;

has
a very different meaning from the same statement with a particular grouping of
parentheses:

A
= X + (Y - 2)/(2 + Z);

Assignment

Assignment
of primitives is quite straightforward. Since the primitive holds the actual
value and not a handle to an object, when you assign primitives you copy the
contents from one place to another. For example, if you say
A
= B

for primitives, then the contents of
B
is copied into
A.
If you then go on to modify
A,
B
is naturally unaffected by this modification. This is what you’ve come to
expect as a programmer for most situations.

As
an aside, the first thing you see is a
package
statement for
package
c03
,
indicating this book’s Chapter 3. The first code listing of each chapter
will contain a package statement like this to establish the chapter number for
the remaining code listings in that chapter. In Chapter 17, you’ll see
that as a result, all the listings in chapter 3 (except those that have
different package names) will be automatically placed in a subdirectory called
c03,
Chapter 4’s listings will be in
c04
and so on. All this will happen via the
CodePackager.java
program shown in Chapter 17, and in Chapter 5 the concept of packages will be
fully explained. What you need to recognize at this point is that, for this
book, lines of code of the form
package
c03

are used just to establish the chapter subdirectory for the listings in the
chapter.

For
later versions of Java (1.1.4 and on), when your
main( )
is
inside a file with a
package
statement,
you must give the full package name before the program name in order to run the
program. In this case, the command line is:

java
c03.Assignment

Keep
this in mind any time you’re running a program that’s in a
package.

Here’s
the example:

//: Assignment.java
// Assignment with objects is a bit tricky
package c03;
 
class Number {
  int i;
}
 
public class Assignment {
  public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    n1.i = 9;
    n2.i = 47;
    System.out.println("1: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1 = n2;
    System.out.println("2: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
    n1.i = 27;
    System.out.println("3: n1.i: " + n1.i +
      ", n2.i: " + n2.i);
  }
} ///:~ 

The
Number
class is simple, and two instances of it (
n1
and
n2)
are created within
main( ).
The
i
value within each
Number
is given a different value, and then
n2
is assigned to
n1,
and
n1
is changed. In many programming languages you would expect
n1
and
n2
to be independent at all times, but because you’ve assigned a handle
here’s the output you’ll see:

1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27

Changing
the
n1
object appears to change the
n2
object as well! This is because both
n1
and
n2
contain the same handle, which is pointing to the same object. (The original
handle that was in
n1
that pointed to the object holding a value of 9 was overwritten during the
assignment and effectively lost; its object will be cleaned up by the garbage
collector.)

n1.i
= n2.i;

This
retains the two separate objects instead of tossing one and tying
n1
and
n2
to the same object, but you’ll soon realize that manipulating the fields
within objects is messy and goes against good object-oriented design
principles. This is a non-trivial topic, so it is left for Chapter 12, which is
devoted to aliasing. In the meantime, you should keep in mind that assignment
for objects can add surprises.


Aliasing
during method calls

Aliasing
will also occur when you pass an object into a method:

//: PassObject.java
// Passing objects to methods can be a bit tricky
 
class Letter {
  char c;
}
 
public class PassObject {
  static void f(Letter y) {
    y.c = 'z';
  }
  public static void main(String[] args) {
    Letter x = new Letter();
    x.c = 'a';
    System.out.println("1: x.c: " + x.c);
    f(x);
    System.out.println("2: x.c: " + x.c);
  }
} ///:~ 

In
many programming languages, the method
f( )
would appear to be making a copy of its argument
Letter
y

inside the scope of the method. But once again a handle is being passed so the
line

y.c
= 'z';

is
actually changing the object outside of
f( ).
The output shows this:

1: x.c: a
2: x.c: z

Aliasing
and its solution is a complex issue and, although you must wait until Chapter
12 for all the answers, you should be aware of it at this point so you can
watch for pitfalls.

Mathematical
operators

Java
also uses a shorthand notation to perform an operation and an assignment at the
same time. This is denoted by an operator followed by an equal sign, and is
consistent with all the operators in the language (whenever it makes sense).
For example, to add 4 to the variable
x
and assign the result

to
x,
use:
x
+= 4;
.

This
example shows the use of the mathematical operators:

//: MathOps.java
// Demonstrates the mathematical operators
import java.util.*;
 
public class MathOps {
  // Create a shorthand to save typing:
  static void prt(String s) {
    System.out.println(s);
  }
  // shorthand to print a string and an int:
  static void pInt(String s, int i) {
    prt(s + " = " + i);
  }
  // shorthand to print a string and a float:
  static void pFlt(String s, float f) {
    prt(s + " = " + f);
  }
  public static void main(String[] args) {
    // Create a random number generator,
    // seeds with current time by default:
    Random rand = new Random();
    int i, j, k;
    // '%' limits maximum value to 99:
    j = rand.nextInt() % 100;
    k = rand.nextInt() % 100;
    pInt("j",j);  pInt("k",k);
    i = j + k; pInt("j + k", i);
    i = j - k; pInt("j - k", i);
    i = k / j; pInt("k / j", i);
    i = k * j; pInt("k * j", i);
    i = k % j; pInt("k % j", i);
    j %= k; pInt("j %= k", j);
    // Floating-point number tests:
    float u,v,w;  // applies to doubles, too
    v = rand.nextFloat();
    w = rand.nextFloat();
    pFlt("v", v); pFlt("w", w);
    u = v + w; pFlt("v + w", u);
    u = v - w; pFlt("v - w", u);
    u = v * w; pFlt("v * w", u);
    u = v / w; pFlt("v / w", u);
    // the following also works for
    // char, byte, short, int, long,
    // and double:
    u += v; pFlt("u += v", u);
    u -= v; pFlt("u -= v", u);
    u *= v; pFlt("u *= v", u);
    u /= v; pFlt("u /= v", u);
  }
} ///:~ 

The
first thing you will see are some shorthand methods for printing: the
prt( )
method prints a
String,
the
pInt( )
prints a
String
followed by an
int
and the
pFlt( )
prints a
String
followed by a
float.
Of course, they all ultimately end up using
System.out.println( ).

To
generate numbers, the program first creates a
Random
object. Because no arguments are passed during creation, Java uses the current
time as a seed for the random number generator. The program generates a number
of different types of random numbers with the
Random
object simply by calling different methods:
nextInt( ),
nextLong( ),
nextFloat( )

or

nextDouble( )
.

The
modulus operator, when used with the result of the random number generator,
limits the result to an upper bound of the operand minus one (99 in this case).


Unary
minus and plus operators

x
= -a;

has
an obvious meaning. The compiler is able to figure out:

x
= a * -b;

but
the reader might get confused, so it is more clear to say:

x
= a * (-b);

Auto
increment and decrement

Java,
like C, is full of shortcuts. Shortcuts can make code much easier to type, and
either easier or harder to read.

There
are two versions of each type of operator, often called the prefix and postfix
versions. Pre-increment means the
++
operator
appears before the variable or expression, and post-increment means the
++
operator appears after the variable or expression. Similarly, pre-decrement
means the

operator
appears before the variable or expression, and post-decrement means the

operator appears after the variable or expression. For pre-increment and
pre-decrement, (i.e.,
++A
or
–A),
the operation is performed and the value is produced. For post-increment and
post-decrement (i.e.
A++
or
A–),
the value is produced, then the operation is performed. As an example:

//: AutoInc.java
// Demonstrates the ++ and -- operators
 
public class AutoInc {
  public static void main(String[] args) {
    int i = 1;
    prt("i : " + i);
    prt("++i : " + ++i); // Pre-increment
    prt("i++ : " + i++); // Post-increment
    prt("i : " + i);
    prt("--i : " + --i); // Pre-decrement
    prt("i-- : " + i--); // Post-decrement
    prt("i : " + i);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~ 

The
output for this program is:

i : 1
++i : 2
i++ : 2
i : 3
--i : 2
i-- : 2
i : 1

You
can see that for the prefix form you get the value after the operation has been
performed, but with the postfix form you get the value before the operation is
performed. These are the only operators (other than those involving assignment)
that have side effects. (That is, they change the operand rather than using
just its value.)

Relational
operators


Testing
object equivalence

The
relational operators
==
and
!=
also work with all objects, but their meaning often confuses the first-time
Java programmer. Here’s an example:

//: Equivalence.java
 
public class Equivalence {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1 == n2);
    System.out.println(n1 != n2);
  }
} ///:~ 

The
expression
System.out.println(n1
== n2)

will print out the result of the
boolean
comparison within it. Surely the output should be
true
and then
false,
since
both
Integer
objects are the same. But while the
contents
of the objects are the same, the handles
are not the same and the operators
==
and
!=
compare
object handles. So the output is actually
false
and then
true.
Naturally, this surprises people at first.

//: EqualsMethod.java
 
public class EqualsMethod {
  public static void main(String[] args) {
    Integer n1 = new Integer(47);
    Integer n2 = new Integer(47);
    System.out.println(n1.equals(n2));
  }
} ///:~ 

The
result will be
true,
as you would expect. Ah, but it’s not as simple as that. If you create
your own class, like this:

//: EqualsMethod2.java
 
class Value {
  int i;
}
 
public class EqualsMethod2 {
  public static void main(String[] args) {
    Value v1 = new Value();
    Value v2 = new Value();
    v1.i = v2.i = 100;
    System.out.println(v1.equals(v2));
  }
} ///:~ 

you’re
back to square one: the result is
false.
This is because the default behavior of
equals( )
is to compare handles. So unless you
override
equals( )
in your new class you won’t get the desired behavior. Unfortunately, you
won’t learn about overriding until Chapter 7, but being aware of the way
equals( )
behaves might save you some grief in the meantime.

Most
of the Java library classes implement
equals( )
so that it compares the contents of objects instead of their handles.

Logical
operators

//: Bool.java
// Relational and logical operators
import java.util.*;
 
public class Bool {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt() % 100;
    int j = rand.nextInt() % 100;
    prt("i = " + i);
    prt("j = " + j);
    prt("i > j is " + (i > j));
    prt("i < j is " + (i < j));
    prt("i >= j is " + (i >= j));
    prt("i <= j is " + (i <= j));
    prt("i == j is " + (i == j));
    prt("i != j is " + (i != j));
 
    // Treating an int as a boolean is 
    // not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
//! prt("!i is " + !i);
 
    prt("(i < 10) && (j < 10) is "
       + ((i < 10) && (j < 10)) );
    prt("(i < 10) || (j < 10) is "
       + ((i < 10) || (j < 10)) );
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~ 

One
output listing looked like this:

i = 85
j = 4
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is true

Note
that a
boolean
value is automatically converted to an appropriate text form if it’s used
where a
String
is expected.


Short-circuiting

//: ShortCircuit.java
// Demonstrates short-circuiting behavior
// with logical operators.
 
public class ShortCircuit {
  static boolean test1(int val) {
    System.out.println("test1(" + val + ")");
    System.out.println("result: " + (val < 1));
    return val < 1;
  }
  static boolean test2(int val) {
    System.out.println("test2(" + val + ")");
    System.out.println("result: " + (val < 2));
    return val < 2;
  }
  static boolean test3(int val) {
    System.out.println("test3(" + val + ")");
    System.out.println("result: " + (val < 3));
    return val < 3;
  }
  public static void main(String[] args) {
    if(test1(0) && test2(2) && test3(2))
      System.out.println("expression is true");
    else
      System.out.println("expression is false");
  }
} ///:~ 

Each
test performs a comparison against the argument and returns true or false. It
also prints information to show you that it’s being called. The tests are
used in the expression:

if(test1(0)
&& test2(2) && test3(2))

You
might naturally think that all three tests would be executed, but the output
shows otherwise:

test1(0)
result: true
test2(2)
result: false
expression is false

The
first test produced a
true
result, so the expression evaluation continues. However, the second test
produced a
false
result. Since this means that the whole expression must be
false,
why continue evaluating the rest of the expression? It could be expensive. The
reason for short-circuiting, in fact, is precisely that; you can get a
potential performance increase if all the parts of a logical expression do not
need to be evaluated.

Bitwise
operators

The
bitwise operators and logical operators use the same characters, so it is
helpful to have a mnemonic device to help you remember the meanings: since bits
are “small,” there is only one character in the bitwise operators.

Shift
operators

The
shift operators also manipulate bits. They can be used solely with primitive,
integral types. The left-shift operator (
<<)

produces the operand to the left of the operator shifted to the left by the
number of bits specified after the operator (inserting zeroes at the
lower-order bits). The signed right-shift operator (
>>)

produces the operand to the left of the operator shifted to the right by the
number of bits specified after the operator. The signed right shift
>>
uses
sign
extension
:
if the value is positive, zeroes are inserted at the higher-order bits; if the
value is negative, ones are inserted at the higher-order bits. Java has also
added the unsigned right shift
>>>,
which
uses
zero
extension
:
regardless of the sign, zeroes are inserted at the higher-order bits.

This
operator does not exist in C or C++.

//: URShift.java
// Test of unsigned right shift
 
public class URShift {
  public static void main(String[] args) {
    int i = -1;
    i &gt;&gt;&gt;= 10;
    System.out.println(i);
    long l = -1;
    l &gt;&gt;&gt;= 10;
    System.out.println(l);
    short s = -1;
    s &gt;&gt;&gt;= 10;
    System.out.println(s);
    byte b = -1;
    b &gt;&gt;&gt;= 10;
    System.out.println(b);
  }
} ///:~ 

Shifts
can be combined with the equal sign (
<<=
or
>>=
or
>>>=).
The lvalue is replaced by the lvalue shifted by the rvalue.

Here’s
an example that demonstrates the use of all the operators involving bits:

//: BitManipulation.java
// Using the bitwise operators
import java.util.*;
 
public class BitManipulation {
  public static void main(String[] args) {
    Random rand = new Random();
    int i = rand.nextInt();
    int j = rand.nextInt();
    pBinInt("-1", -1);
    pBinInt("+1", +1);
    int maxpos = 2147483647;
    pBinInt("maxpos", maxpos);
    int maxneg = -2147483648;
    pBinInt("maxneg", maxneg);
    pBinInt("i", i);
    pBinInt("~i", ~i);
    pBinInt("-i", -i);
    pBinInt("j", j);
    pBinInt("i &amp; j", i &amp; j);
    pBinInt("i | j", i | j);
    pBinInt("i ^ j", i ^ j);
    pBinInt("i &lt;&lt; 5", i &lt;&lt; 5);
    pBinInt("i &gt;&gt; 5", i &gt;&gt; 5);
    pBinInt("(~i) &gt;&gt; 5", (~i) &gt;&gt; 5);
    pBinInt("i &gt;&gt;&gt; 5", i &gt;&gt;&gt; 5);
    pBinInt("(~i) &gt;&gt;&gt; 5", (~i) &gt;&gt;&gt; 5);
 
    long l = rand.nextLong();
    long m = rand.nextLong();
    pBinLong("-1L", -1L);
    pBinLong("+1L", +1L);
    long ll = 9223372036854775807L;
    pBinLong("maxpos", ll);
    long lln = -9223372036854775808L;
    pBinLong("maxneg", lln);
    pBinLong("l", l);
    pBinLong("~l", ~l);
    pBinLong("-l", -l);
    pBinLong("m", m);
    pBinLong("l &amp; m", l &amp; m);
    pBinLong("l | m", l | m);
    pBinLong("l ^ m", l ^ m);
    pBinLong("l &lt;&lt; 5", l &lt;&lt; 5);
    pBinLong("l &gt;&gt; 5", l &gt;&gt; 5);
    pBinLong("(~l) &gt;&gt; 5", (~l) &gt;&gt; 5);
    pBinLong("l &gt;&gt;&gt; 5", l &gt;&gt;&gt; 5);
    pBinLong("(~l) &gt;&gt;&gt; 5", (~l) &gt;&gt;&gt; 5);
  }
  static void pBinInt(String s, int i) {
    System.out.println(
      s + ", int: " + i + ", binary: ");
    System.out.print("   ");
    for(int j = 31; j &gt;=0; j--)
      if(((1 &lt;&lt; j) &amp;  i) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
  static void pBinLong(String s, long l) {
    System.out.println(
      s + ", long: " + l + ", binary: ");
    System.out.print("   ");
    for(int i = 63; i &gt;=0; i--)
      if(((1L &lt;&lt; i) &amp; l) != 0)
        System.out.print("1");
      else
        System.out.print("0");
    System.out.println();
  }
} ///:~ 

The
two methods at the end,
pBinInt( )
and
pBinLong( )
take an
int
or a
long,
respectively, and print it out in binary format along with a descriptive
string. You can ignore the implementation of these for now.

You’ll
note the use of
System.out.print( )
instead of
System.out.println( ).
The
print( )
method does not put out a new line, so it allows you to output a line in pieces.

As
well as demonstrating the effect of all the bitwise operators for
int
and
long,
this example also shows the minimum, maximum, +1 and -1 values for
int
and
long
so you can see what they look like. Note that the high bit represents the sign:
0 means positive and 1 means negative. The output for the
int
portion looks like this:

-1, int: -1, binary:
   11111111111111111111111111111111
+1, int: 1, binary:
   00000000000000000000000000000001
maxpos, int: 2147483647, binary:
   01111111111111111111111111111111
maxneg, int: -2147483648, binary:
   10000000000000000000000000000000
i, int: 59081716, binary:
   00000011100001011000001111110100
~i, int: -59081717, binary:
   11111100011110100111110000001011
-i, int: -59081716, binary:
   11111100011110100111110000001100
j, int: 198850956, binary:
   00001011110110100011100110001100
i &amp; j, int: 58720644, binary:
   00000011100000000000000110000100
i | j, int: 199212028, binary:
   00001011110111111011101111111100
i ^ j, int: 140491384, binary:
   00001000010111111011101001111000
i &lt;&lt; 5, int: 1890614912, binary:
   01110000101100000111111010000000
i &gt;&gt; 5, int: 1846303, binary:
   00000000000111000010110000011111
(~i) &gt;&gt; 5, int: -1846304, binary:
   11111111111000111101001111100000
i &gt;&gt;&gt; 5, int: 1846303, binary:
   00000000000111000010110000011111
(~i) &gt;&gt;&gt; 5, int: 132371424, binary:
   00000111111000111101001111100000

The
binary representation of the numbers is referred to as signed
two’s complement
.

Ternary
if-else operator

Of
course, you could use an ordinary
if-else
statement
(described later), but the ternary operator is much terser. Although C prides
itself on being a terse language, and the ternary operator might have been
introduced partly for efficiency, you should be somewhat wary of using it on an
everyday basis – it’s easy to produce unreadable code.

The
conditional operator can be used for its side effects or for the value it
produces, but in general you want the value since that’s what makes the
operator distinct from the
if-else.
Here’s an example:

static int ternary(int i) {
  return i &lt; 10 ? i * 100 : i * 10;
}

You
can see that this code is more compact than what you’d need to write
without the ternary operator:

static int alternative(int i) {
  if (i &lt; 10)
    return i * 100;
  return i * 10;
}

The
comma operator

String
operator +

There’s
one special usage of an operator in Java: the
+
operator can be used to
concatenate
strings, as you’ve already seen. It seems a natural use of the
+
even though it doesn’t fit with the traditional way that
+
is used. This capability seemed like a good idea in C++, so
operator
overloading

was added to C++ to allow the C++ programmer to add meanings to almost any
operator. Unfortunately, operator overloading combined with some of the other
restrictions in C++ turns out to be a fairly complicated feature for
programmers to design into their classes. Although operator overloading would
have been much simpler to implement in Java than it was in C++, this feature
was still considered too complex, so Java programmers cannot implement their
own overloaded operators as C++ programmers can.

The
use of the
String
+

has some interesting behavior. If an expression begins with a
String,
then all operands that follow must be
Strings:

int
x = 0, y = 1, z = 2;

String
sString = "x, y, z ";

System.out.println(sString
+ x + y + z);

Here,
the Java compiler will convert
x,
y,
and
z
into their
String
representations instead of adding them together first. However, if you say:

System.out.println(x
+ sString);

Common
pitfalls when using operators

One
of the pitfalls when using operators is trying to get away without parentheses
when you are even the least bit uncertain about how an expression will
evaluate. This is still true in Java.

An
extremely common error in C and C++ looks like this:

while(x = y) {
    // ....
}

Casting
operators

The
word
cast
is used in the sense of “casting into a mold.” Java will
automatically change one type of data into another when appropriate. For
instance, if you assign an integral value to a floating-point variable, the
compiler will automatically convert the
int
to a
float.
Casting allows you to make this type conversion explicit, or to force it when
it wouldn’t normally happen.

To
perform a cast, put the desired data type (including all modifiers) inside
parentheses to the left of any value. Here’s an example:

void casts() {
  int i = 200;
  long l = (long)i;
  long l2 = (long)200;
}

As
you can see, it’s possible to perform a cast on a numeric value as well
as on a variable. In both casts shown here, however, the cast is superfluous,
since the compiler will automatically promote an
int
value to a
long
when necessary. You can still put a cast in to make a point or to make your
code more clear. In other situations, a cast is essential just to get the code
to compile.


Literals

Ordinarily
when you insert a literal value into a program the compiler knows exactly what
type to make it. Sometimes, however, the type is ambiguous. When this happens
you must guide the compiler by adding some extra information in the form of
characters associated with the literal value. The following code shows these
characters:

//: Literals.java
 
class Literals {
  char c = 0xffff; // max char hex value
  byte b = 0x7f; // max byte hex value
  short s = 0x7fff; // max short hex value
  int i1 = 0x2f; // Hexadecimal (lowercase)
  int i2 = 0X2F; // Hexadecimal (uppercase)
  int i3 = 0177; // Octal (leading zero)
  // Hex and Oct also work with long.
  long n1 = 200L; // long suffix
  long n2 = 200l; // long suffix
  long n3 = 200;
  //! long l6(200); // not allowed
  float f1 = 1;
  float f2 = 1F; // float suffix
  float f3 = 1f; // float suffix
  float f4 = 1e-45f; // 10 to the power
  float f5 = 1e+9f; // float suffix
  double d1 = 1d; // double suffix
  double d2 = 1D; // double suffix
  double d3 = 47e47d; // 10 to the power
} ///:~ 

Exponents
use a notation that I’ve always found rather dismaying:
1.39
e-47f
.
In science and engineering, ‘e’ refers to the base of
natural
logarithms, approximately 2.718. (A more precise
double
value
is available in Java as
Math.E.)
This is used in exponentiation expressions such as 1.39 x e
-47,
which means 1.39 x 2.718
-47.
However, when
FORTRAN
was invented they decided that
e
would naturally mean “ten to the power,” which is an odd decision
because FORTRAN was designed for science and engineering and one would think
its designers would be sensitive about introducing such an ambiguity.
[16]
At any rate, this custom was followed in C, C++ and now Java. So if
you’re used to thinking in terms of
e
as the base of natural logarithms, you must do a mental translation when you
see an expression such as
1.39
e-47f

in Java; it means 1.39 x 10
-47.

Note
that you don’t need to use the trailing character when the compiler can
figure out the appropriate type. With

long
n3 = 200;

there’s
no ambiguity, so an
L
after the 200 would be superfluous. However, with

float
f4 = 1e-47f; // 10 to the power

the
compiler normally takes exponential numbers as doubles, so without the trailing
f
it will give you an error telling you that you must use a cast to convert
double
to
float.


Promotion

Java
has no “sizeof”

Precedence
revisited

Mnemonic

Operator
type

Operators

Ulcer

Unary

+
– ++ – [[ rest…]]

Addicts

Arithmetic
(and shift)

*
/ % + – << >>

Really

Relational

>
< >= <= == !=

Like

Logical
(and bitwise)

&&
|| & | ^

C

Conditional
(ternary)

A
> B ? X : Y

A
Lot

Assignment

=
(and compound assignment like *=)

A
compendium of operators

//: AllOps.java
// Tests all the operators on all the
// primitive data types to show which
// ones are accepted by the Java compiler.
 
class AllOps {
  // To accept the results of a boolean test:
  void f(boolean b) {}
  void boolTest(boolean x, boolean y) {
    // Arithmetic operators:
    //! x = x * y;
    //! x = x / y;
    //! x = x % y;
    //! x = x + y;
    //! x = x - y;
    //! x++;
    //! x--;
    //! x = +y;
    //! x = -y;
    // Relational and logical:
    //! f(x &gt; y);
    //! f(x &gt;= y);
    //! f(x &lt; y);
    //! f(x &lt;= y);
    f(x == y);
    f(x != y);
    f(!y);
    x = x &amp;&amp; y;
    x = x || y;
    // Bitwise operators:
    //! x = ~y;
    x = x &amp; y;
    x = x | y;
    x = x ^ y;
    //! x = x &lt;&lt; 1;
    //! x = x &gt;&gt; 1;
    //! x = x &gt;&gt;&gt; 1;
    // Compound assignment:
    //! x += y;
    //! x -= y;
    //! x *= y;
    //! x /= y;
    //! x %= y;
    //! x &lt;&lt;= 1;
    //! x &gt;&gt;= 1;
    //! x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! char c = (char)x;
    //! byte B = (byte)x;
    //! short s = (short)x;
    //! int i = (int)x;
    //! long l = (long)x;
    //! float f = (float)x;
    //! double d = (double)x;
  }
  void charTest(char x, char y) {
    // Arithmetic operators:
    x = (char)(x * y);
    x = (char)(x / y);
    x = (char)(x % y);
    x = (char)(x + y);
    x = (char)(x - y);
    x++;
    x--;
    x = (char)+y;
    x = (char)-y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    x= (char)~y;
    x = (char)(x &amp; y);
    x  = (char)(x | y);
    x = (char)(x ^ y);
    x = (char)(x &lt;&lt; 1);
    x = (char)(x &gt;&gt; 1);
    x = (char)(x &gt;&gt;&gt; 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x &lt;&lt;= 1;
    x &gt;&gt;= 1;
    x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void byteTest(byte x, byte y) {
    // Arithmetic operators:
    x = (byte)(x* y);
    x = (byte)(x / y);
    x = (byte)(x % y);
    x = (byte)(x + y);
    x = (byte)(x - y);
    x++;
    x--;
    x = (byte)+ y;
    x = (byte)- y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    x = (byte)~y;
    x = (byte)(x &amp; y);
    x = (byte)(x | y);
    x = (byte)(x ^ y);
    x = (byte)(x &lt;&lt; 1);
    x = (byte)(x &gt;&gt; 1);
    x = (byte)(x &gt;&gt;&gt; 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x &lt;&lt;= 1;
    x &gt;&gt;= 1;
    x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void shortTest(short x, short y) {
    // Arithmetic operators:
    x = (short)(x * y);
    x = (short)(x / y);
    x = (short)(x % y);
    x = (short)(x + y);
    x = (short)(x - y);
    x++;
    x--;
    x = (short)+y;
    x = (short)-y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    x = (short)~y;
    x = (short)(x &amp; y);
    x = (short)(x | y);
    x = (short)(x ^ y);
    x = (short)(x &lt;&lt; 1);
    x = (short)(x &gt;&gt; 1);
    x = (short)(x &gt;&gt;&gt; 1);
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x &lt;&lt;= 1;
    x &gt;&gt;= 1;
    x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void intTest(int x, int y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x &amp; y;
    x = x | y;
    x = x ^ y;
    x = x &lt;&lt; 1;
    x = x &gt;&gt; 1;
    x = x &gt;&gt;&gt; 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x &lt;&lt;= 1;
    x &gt;&gt;= 1;
    x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    long l = (long)x;
    float f = (float)x;
    double d = (double)x;
  }
  void longTest(long x, long y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    x = ~y;
    x = x &amp; y;
    x = x | y;
    x = x ^ y;
    x = x &lt;&lt; 1;
    x = x &gt;&gt; 1;
    x = x &gt;&gt;&gt; 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    x &lt;&lt;= 1;
    x &gt;&gt;= 1;
    x &gt;&gt;&gt;= 1;
    x &amp;= y;
    x ^= y;
    x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    float f = (float)x;
    double d = (double)x;
  }
  void floatTest(float x, float y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x &amp; y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x &lt;&lt; 1;
    //! x = x &gt;&gt; 1;
    //! x = x &gt;&gt;&gt; 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x &lt;&lt;= 1;
    //! x &gt;&gt;= 1;
    //! x &gt;&gt;&gt;= 1;
    //! x &amp;= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    double d = (double)x;
  }
  void doubleTest(double x, double y) {
    // Arithmetic operators:
    x = x * y;
    x = x / y;
    x = x % y;
    x = x + y;
    x = x - y;
    x++;
    x--;
    x = +y;
    x = -y;
    // Relational and logical:
    f(x &gt; y);
    f(x &gt;= y);
    f(x &lt; y);
    f(x &lt;= y);
    f(x == y);
    f(x != y);
    //! f(!x);
    //! f(x &amp;&amp; y);
    //! f(x || y);
    // Bitwise operators:
    //! x = ~y;
    //! x = x &amp; y;
    //! x = x | y;
    //! x = x ^ y;
    //! x = x &lt;&lt; 1;
    //! x = x &gt;&gt; 1;
    //! x = x &gt;&gt;&gt; 1;
    // Compound assignment:
    x += y;
    x -= y;
    x *= y;
    x /= y;
    x %= y;
    //! x &lt;&lt;= 1;
    //! x &gt;&gt;= 1;
    //! x &gt;&gt;&gt;= 1;
    //! x &amp;= y;
    //! x ^= y;
    //! x |= y;
    // Casting:
    //! boolean b = (boolean)x;
    char c = (char)x;
    byte B = (byte)x;
    short s = (short)x;
    int i = (int)x;
    long l = (long)x;
    float f = (float)x;
  }
} ///:~ 

//: Overflow.java
// Surprise! Java lets you overflow.
 
public class Overflow {
  public static void main(String[] args) {
    int big = 0x7fffffff; // max int value
    prt("big = " + big);
    int bigger = big * 4;
    prt("bigger = " + bigger);
  }
  static void prt(String s) {
    System.out.println(s);
  }
} ///:~ 

The
output of this is:

big = 2147483647
bigger = -4

and
you get no errors or warnings from the compiler, and no exceptions at run-time.
Java is good, but it’s not
that
good.

Compound
assignments do
not
require casts for
char,
byte,
or
short,
even though they are performing promotions that have the same results as the
direct arithmetic operations. On the other hand, the lack of the cast certainly
simplifies the code.


[16]
John Kirkham writes, “I started computing in 1962 using FORTRAN II on an
IBM 1620. At that time, and throughout the 1960s and into the 1970s, FORTRAN
was an all uppercase language. This probably started because many of the early
input devices were old teletype units that used 5 bit Baudot code, which had no
lowercase capability. The ‘E’ in the exponential notation was also
always upper case and was never confused with the natural logarithm base
‘e’, which is always lower case. The ‘E’ simply stood
for exponential, which was for the base of the number system used –
usually 10. At the time octal was also widely used by programmers. Although I
never saw it used, if I had seen an octal number in exponential notation I
would have considered it to be base 8. The first time I remember seeing an
exponential using a lower case ‘e’ was in the late 1970s and I also
found it confusing. The problem arose as lowercase crept into FORTRAN, not at
its beginning. We actually had functions to use if you really wanted to use the
natural logarithm base, but they were all uppercase.”

More by Author

Must Read