Using Java operators
Posted
on March 1st, 2001
WEBINAR:
OnDemand
Building the Right Environment to Support AI, Machine Learning and Deep Learning
Bruce Eckel's Thinking in Java  Contents  Prev  Next 
All
operators produce a value from their operands. In addition, an operator can
change the value of an operand. This is called a
side
effect.
The most common use for operators that modify their operands is to generate the
side effect, but you should keep in mind that the value produced is available
for your use just as in operators without side effects.
Almost
all operators work only with primitives. The exceptions are
‘=’,
‘
==’
and ‘
!=’,
which work with all objects (and are a point of confusion for objects). In
addition, the
String
class
supports ‘
+’
and ‘
+=’.
Precedence
Assignment
Assignment
is performed with the operator =. It means “take the value of the
righthand side (often called the
rvalue)
and copy it into the lefthand side (often called the
lvalue).
An rvalue is any constant, variable or expression that can produce a value, but
an lvalue must be a distinct, named variable. (That is, there must be a
physical space to store a value.) For instance, you can assign a constant value
to a variable (
A
= 4;
),
but you cannot assign anything to constant value – it cannot be an
lvalue. (You can’t say
4
= A;.
)
When
you assign
objects, however, things change. Whenever you manipulate an object, what
you’re manipulating is the handle, so when you assign “from one
object to another” you’re actually copying a handle from one place
to another. This means that if you say
C
= D
for objects, you end up with both
C
and
D
pointing to the object that, originally, only
D
pointed to. The following example will demonstrate this.
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.
In
order to run the program, you must ensure that the classpath
contains the root directory where you installed the source code for this book.
(From this directory, you’ll see the subdirectories
c02,
c03,
c04,
etc.)
//: 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.)
This
phenomenon is often called aliasing
and it’s a fundamental way that Java works with objects. But what if you
don’t want aliasing to occur in this case? You could forego the
assignment and say:
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
The
basic mathematical operators are the same as the ones available in most
programming languages: addition (+),
subtraction
(
),
division
(
/),
multiplication
(
*)
and modulus
(
%,
produces the remainder from integer division). Integer division truncates,
rather than rounds, the result.
//: 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); // Floatingpoint 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
The
unary minus ()
and unary plus (+)
are the same operators as binary minus and plus. The compiler figures out which
use is intended by the way you write the expression. For instance, the statement
The
unary minus produces the negative of the value. Unary plus provides symmetry
with unary minus, although it doesn’t do much.
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.
Two
of the nicer shortcuts are the increment
and decrement
operators (often referred to as the autoincrement
and autodecrement
operators). The decrement operator is

and means “decrease by one unit.” The increment operator is
++
and means “increase by one unit.” If
A
is an
int,
for example, the expression
++A
is equivalent to (
A
= A + 1
).
Increment and decrement operators produce the value of the variable as a result.
//: AutoInc.java // Demonstrates the ++ and  operators public class AutoInc { public static void main(String[] args) { int i = 1; prt("i : " + i); prt("++i : " + ++i); // Preincrement prt("i++ : " + i++); // Postincrement prt("i : " + i); prt("i : " + i); // Predecrement prt("i : " + i); // Postdecrement 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.)
The
increment operator is one explanation for the name C++, implying “one
step beyond C.” In an early Java speech, Bill
Joy (one of the creators), said that “Java=C++“ (C plus plus
minus minus), suggesting that Java is C++ with the unnecessary hard parts
removed and therefore a much simpler language. As you progress in this book
you’ll see that many parts are simpler, and yet Java isn’t
that
much
easier than C++.
Relational operators
Relational
operators generate a
boolean
result. They evaluate the relationship between the values of the operands. A
relational expression produces
true
if the relationship is true, and
false
if the relationship is untrue. The relational operators are less than (<),
greater than (>),
less than or equal to (<=),
greater than or equal to (>=),
equivalent (==)
and not equivalent (!=).
Equivalence and nonequivalence works with all builtin data types, but the
other comparisons won’t work with type
boolean.
Testing
object equivalence
//: 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.
What
if you want to compare the actual contents of an object for equivalence? You
must use the special method equals( )
that exists for all objects (not primitives,
which work fine with
==
and
!=).
Here’s how it’s used:
//: 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
The
logical operators AND (&&),
OR ()
and NOT (!) produce a
boolean
value of
true
or
false
based
on the logical relationship of its arguments. This example uses the relational
and 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); } } ///:~
You
can apply AND, OR, or NOT to
boolean
values only. You can’t use a non
boolean
as if it were a boolean
in a logical expression as you can in C and C++. You can see the failed
attempts at doing this commented out with a
//!
comment marker. The subsequent expressions, however, produce
boolean
values using relational comparisons, then use logical operations on the results.
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.
You
can replace the definition for
int
in the above program with any other primitive data type except
boolean.
Be aware, however, that the comparison of floatingpoint numbers is very
strict. A number that is the tiniest fraction different from another number is
still “not equal.” A number that is the tiniest bit above zero is
still nonzero.
Shortcircuiting
When
dealing with logical
operators you run into a phenomenon called “short circuiting.” This
means that the expression will be evaluated only until the truth or falsehood
of the entire expression can be unambiguously determined. As a result, all the
parts of a logical expression might not be evaluated. Here’s an example
that demonstrates shortcircuiting:
//: ShortCircuit.java // Demonstrates shortcircuiting 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 shortcircuiting, 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 allow you to manipulate individual bits in an integral
primitive data type. Bitwise operators perform boolean algebra
on the corresponding bits in the two arguments to produce the result.
The
bitwise operators come from C’s lowlevel orientation; you were often
manipulating hardware directly and had to set the bits in hardware registers.
Java was originally designed to be embedded in TV settop
boxes, so this lowlevel orientation still made sense. However, you probably
won’t use the bitwise operators much.
The
bitwise AND operator (&)
produces a one in the output bit if both input bits are one; otherwise it
produces a zero. The bitwise OR operator ()
produces a one in the output bit if either input bit is a one and produces a
zero only if both input bits are zero. The bitwise, EXCLUSIVE OR, or XOR (^),
produces a one in the output bit if one or the other input bit is a one, but
not both. The bitwise NOT
(~, also called the ones complement operator)
is a unary operator;
it takes only one argument. (All other bitwise operators are binary operators.)
Bitwise NOT produces the opposite of the input bit – a one if the input
bit is zero, a zero if the input bit is one.
Bitwise
operators can be combined with the = sign to unite the operation and
assignment: &=,
=
and ^=
are all legitimate. (Since ~ is a unary operator it cannot be combined with the
= sign.)
The
boolean
type is treated as a onebit value so it is somewhat different. You can perform
a bitwise AND, OR and XOR, but you can’t perform a bitwise NOT
(presumably to prevent confusion with the logical NOT). For
booleans
the bitwise operators have the same effect as the logical operators except that
they do not short circuit. Also, the bitwise operators on
booleans
gives you a XOR logical operator that is not included under the list of
“logical” operators. You’re prevented from using
booleans
in shift expressions, which is described next.
Shift operators
The
shift operators also manipulate bits. They can be used solely with primitive,
integral types. The leftshift 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
lowerorder bits). The signed rightshift 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 higherorder bits; if the
value is negative, ones are inserted at the higherorder bits. Java has also
added the unsigned right shift
>>>,
which
uses
zero
extension
:
regardless of the sign, zeroes are inserted at the higherorder bits.
This
operator does not exist in C or C++.
If
you shift a
char,
byte,
or
short,
it will be promoted to
int
before the shift takes place, and the result will be an
int.
Only the five loworder bits of the righthand side will be used. This prevents
you from shifting more than the number of bits in an
int.
If you’re operating on a
long,
long
will be the result. Only the six loworder bits of the righthand side will be
used so you can’t shift more than the number of bits in a
long.
There is a problem, however, with the unsigned right shift. If you use it with
byte
or
short
you might not get the correct results. (It’s broken in Java
1.0 and Java 1.1.)
These are promoted to
int
and right shifted, but the zero extension does
not
occur, so you get
1
in those cases. The following example can be used to test your implementation:
//: URShift.java // Test of unsigned right shift public class URShift { public static void main(String[] args) { int i = 1; i >>>= 10; System.out.println(i); long l = 1; l >>>= 10; System.out.println(l); short s = 1; s >>>= 10; System.out.println(s); byte b = 1; b >>>= 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.
//: 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 & j", i & j); pBinInt("i  j", i  j); pBinInt("i ^ j", i ^ j); pBinInt("i << 5", i << 5); pBinInt("i >> 5", i >> 5); pBinInt("(~i) >> 5", (~i) >> 5); pBinInt("i >>> 5", i >>> 5); pBinInt("(~i) >>> 5", (~i) >>> 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 & m", l & m); pBinLong("l  m", l  m); pBinLong("l ^ m", l ^ m); pBinLong("l << 5", l << 5); pBinLong("l >> 5", l >> 5); pBinLong("(~l) >> 5", (~l) >> 5); pBinLong("l >>> 5", l >>> 5); pBinLong("(~l) >>> 5", (~l) >>> 5); } static void pBinInt(String s, int i) { System.out.println( s + ", int: " + i + ", binary: "); System.out.print(" "); for(int j = 31; j >=0; j) if(((1 << j) & 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 >=0; i) if(((1L << i) & 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 & j, int: 58720644, binary: 00000011100000000000000110000100 i  j, int: 199212028, binary: 00001011110111111011101111111100 i ^ j, int: 140491384, binary: 00001000010111111011101001111000 i << 5, int: 1890614912, binary: 01110000101100000111111010000000 i >> 5, int: 1846303, binary: 00000000000111000010110000011111 (~i) >> 5, int: 1846304, binary: 11111111111000111101001111100000 i >>> 5, int: 1846303, binary: 00000000000111000010110000011111 (~i) >>> 5, int: 132371424, binary: 00000111111000111101001111100000
Ternary ifelse operator
This
operator is unusual because it has three operands. It is truly an operator
because it produces a value, unlike the ordinary ifelse statement that
you’ll see in the next section of this chapter.
The expression is of the form
booleanexp ? value0 : value1
If booleanexp evaluates to true, value0 is evaluated and its result becomes the value produced by the operator. If booleanexp is false, value1 is evaluated and its result becomes the value produced by the operator.
static int ternary(int i) { return i < 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 < 10) return i * 100; return i * 10; }
The
second form is easier to understand, and doesn’t require a lot more
typing. So be sure to ponder your reasons when choosing the ternary operator.
The comma operator
The
comma is used in C and C++ not only as a separator in function argument lists,
but also as an operator for sequential evaluation. The sole place that the comma
operator
is used in Java is in
for
loops, which will be described later in this chapter.
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.
earlier
versions of Java will signal an error. (Later versions, however, will turn
x
into
a
String.)
So if you’re putting together a
String
(using an earlier version of Java) with addition, make sure the first element
is a
String
(or a quoted sequence of characters, which the compiler recognizes as a
String).
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) { // .... }
The
programmer was trying to test for equivalence (
==)
rather than do an assignment. In C and C++ the result of this assignment will
always be
true
if
y
is
nonzero, and you’ll probably get an infinite loop. In Java, the result of
this expression is not a
boolean,
and the compiler expects a
boolean
and won’t convert from an
int,
so it will conveniently give you a compiletime error and catch the problem
before you ever try to run the program. So the pitfall never happens in Java.
(The only time you won’t get a compiletime error is when
x
and
y
are
boolean,
in which case
x
= y
is a legal expression, and in the above case, probably an error.)
A
similar problem in C and C++ is using bitwise AND and OR instead of logical.
Bitwise AND and OR use one of the characters (
&
or
)
while logical AND and OR use two (
&&
and
).
Just as with
=
and
==,
it’s easy to type just one character instead of two.
In Java, the compiler again prevents this because it won’t let you
cavalierly use one type where it doesn’t belong.
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 floatingpoint 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.
In
C and C++, casting can cause some headaches. In Java, casting is safe, with the
exception that when you perform a socalled narrowing
conversion
(that is, when you go from a data type that can hold more information to one
that doesn’t hold as much) you run the risk of losing information.
Here
the compiler forces you to do a cast, in effect saying “this can be a
dangerous thing to do – if you want me to do it anyway you must make the
cast explicit.” With a widening
conversion
an explicit cast is not needed because the new type will more than hold the
information from the old type so that no information is ever lost.
Java
allows you to cast any primitive type to any other primitive type, except for boolean,
which doesn’t allow any casting at all. Class types do not allow casting.
To convert one to the other there must be special methods. (
String
is a special case, and you’ll find out later in the book that objects can
be cast within a
family
of types; an
Oak
can be cast to a
Tree
and viceversa, but not to a foreign type such as a
Rock.)
Literals
//: 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 = 1e45f; // 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 } ///:~
Hexadecimal
(base
16), which works with all the integral data types, is denoted by a leading
0x
or
0X
followed by 0–9 and a–f either in upper or lower case. If you try
to initialize a variable with a value bigger than it can hold (regardless of
the numerical form of the value), the compiler will give you an error message.
Notice in the above code the maximum possible hexadecimal values for
char,
byte,
and
short.
If you exceed these, the compiler will automatically make the value an
int
and tell you that you need a narrowing cast for the assignment. You’ll
know you’ve stepped over the line.
Octal
(base
8) is denoted by a leading zero in the number and digits from 07. There is no
literal representation for binary
numbers in C, C++ or Java.
A
trailing character after a literal value establishes its type. Upper or
lowercase
L
means
long,
upper or lowercase
F
means float
and upper or lowercase
D
means double.
Exponents
use a notation that I’ve always found rather dismaying:
1.39
e47f
.
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
e47f
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 = 1e47f; // 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
You’ll
discover that if you perform any mathematical or bitwise operations on
primitive data types that are smaller than an
int
(that is,
char,
byte,
or
short),
those values will be promoted
to
int
before performing the operations, and the resulting value will be of type
int.
So if you want to assign back into the smaller type, you must use a cast. (And,
since you’re assigning back into a smaller type, you might be losing
information.) In general, the largest data type in an expression is the one
that determines the size of the result of that expression; if you multiply a
float
and a
double,
the result will be
double;
if you add an
int
and a
long,
the result will be
long.
Java has no “sizeof”
In
C and C++, the sizeof( )
operator satisfies a specific need: it tells you the number of bytes allocated
for data items. The most compelling need for
sizeof( )
in C and C++ is portability.
Different data types might be different sizes on different machines, so the
programmer must find out how big those types are when performing operations
that are sensitive to size. For example, one computer might store integers in
32 bits, whereas another might store integers as 16 bits. Programs could store
larger values in integers on the first machine. As you might imagine,
portability is a huge headache for C and C++ programmers.
Java
does not need a
sizeof( )
operator for this purpose because all the data types are the same size on all
machines. You do not need to think about portability on this level – it
is designed into the language.
Precedence revisited
Upon
hearing me complain about the complexity of remembering operator
precedence during one of my seminars, a student suggested a mnemonic that is
simultaneously a commentary: “Ulcer Addicts Really Like C A lot.”
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 *=)

Of
course, with the shift and bitwise operators distributed around the table it is
not a perfect mnemonic, but for nonbit operations it works.
A compendium of operators
The
following example shows which primitive
data types can be used with particular operators. Basically, it is the same
example repeated over and over, but using different primitive data types. The
file will compile without error because the lines that would cause errors are
commented out with a
//!.
//: 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 > y); //! f(x >= y); //! f(x < y); //! f(x <= y); f(x == y); f(x != y); f(!y); x = x && y; x = x  y; // Bitwise operators: //! x = ~y; x = x & y; x = x  y; x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Compound assignment: //! x += y; //! x = y; //! x *= y; //! x /= y; //! x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: x= (char)~y; x = (char)(x & y); x = (char)(x  y); x = (char)(x ^ y); x = (char)(x << 1); x = (char)(x >> 1); x = (char)(x >>> 1); // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: x = (byte)~y; x = (byte)(x & y); x = (byte)(x  y); x = (byte)(x ^ y); x = (byte)(x << 1); x = (byte)(x >> 1); x = (byte)(x >>> 1); // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: x = (short)~y; x = (short)(x & y); x = (short)(x  y); x = (short)(x ^ y); x = (short)(x << 1); x = (short)(x >> 1); x = (short)(x >>> 1); // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: x = ~y; x = x & y; x = x  y; x = x ^ y; x = x << 1; x = x >> 1; x = x >>> 1; // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: x = ~y; x = x & y; x = x  y; x = x ^ y; x = x << 1; x = x >> 1; x = x >>> 1; // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: //! x = ~y; //! x = x & y; //! x = x  y; //! x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; //! x &= 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 > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x  y); // Bitwise operators: //! x = ~y; //! x = x & y; //! x = x  y; //! x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Compound assignment: x += y; x = y; x *= y; x /= y; x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; //! x &= 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; } } ///:~
Note
that boolean
is quite limited. You can assign to it the values
true
and
false,
and you can test it for truth or falsehood, but you cannot add booleans or
perform any other type of operation on them.
In
char,
byte,
and
short
you can see the effect of promotion
with the arithmetic operators. Each arithmetic operation on any of those types
results in an
int
result, which must be explicitly cast back to the original type (a narrowing
conversion that might lose information) to assign back to that type. With
int
values, however, you do not need to cast, because everything is already an
int.
Don’t be lulled into thinking everything is safe, though. If you multiply
two
ints
that are big enough, you’ll overflow
the result. The following example demonstrates this:
//: 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 runtime.
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.
You
can see that, with the exception of
boolean,
any primitive
type can be cast to any other primitive type. Again, you must be aware of the
effect of a narrowing conversion when casting to a smaller type, otherwise you
might unknowingly lose information during the cast.
[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.”
Comments
There are no comments yet. Be the first to comment!