3. Primitive Types and Strings

Originality exists in every individual because each of us differs from the others. We are all primary numbers divisible only by ourselves.

— Jean Guitton

3.1. Problem: College cost calculator

Perhaps you’re a student. Perhaps you aren’t. In either case, you must be aware of the rapidly rising cost of a college education. The motivating problem for this chapter is to create a Java program that can estimate this cost, including room and board. It starts by reading a first name, a last name, the per-semester cost of tuition, the monthly cost of rent, and the monthly cost of food as input from a user. Many students take out loans for college. In fact, student debt has surpassed credit card debt in the United States. We can implement a feature to read in an interest rate and the number of years expected to pay off the loan.

After taking all this data as input, we want to calculate the yearly cost of such an education, the four year cost, the monthly loan payment, and the total cost of the loan over time. Furthermore, we want to output this information on the command line in an attractive way, customized with the user’s name. Below is a sample execution of this program.

Welcome to the College Cost Calculator!
Enter your first name:       Holden
Enter your last name:        Caulfield
Enter tuition per semester:  $17415
Enter rent per month:        $350
Enter food cost per month:   $400
Annual interest rate:        .0937
Years to pay back your loan: 10

College costs for Holden Caulfield
***************************************
Yearly cost:                 $43830.00
Four year cost:              $175320.00
Monthly loan payment:        $2256.14
Total loan cost:             $270736.5

Samples from a command line interface can be confusing because it’s difficult to see what’s output and what’s input. In this case, we have marked the input in bold so that it’s clear what the user enters. In this program, the names, the tuition, the rent, the food, the interest rate, and the years to pay back the loan are taken as input. Note that the dollar signs are not part of the input and are printed as a cue to the user and to give visual polish.

We hope you already have a good understanding of this problem, but there are a few mathematical details worth addressing. First, the yearly cost is twice the semester tuition plus 12 times the rent and food costs. The four year cost is simply four times the yearly cost. The monthly loan payment amount, however, requires a formula from financial mathematics. Let P be the amount of the loan (the principal). Let J be the monthly interest rate (the annual interest rate divided by 12). Let N be the number of months to pay back the loan (years of the loan times 12). Let M be the monthly payment we want to calculate, given by the following formula.

payment

If you use the concepts and syntax from the previous chapter carefully, you might be able to solve this problem without reading further. However, there’s a depth to the ideas of types and operations that we haven’t explored fully. Getting a program to work is not enough. Programmers must understand every detail and quirk of their tools to avoid potential bugs.

3.2. Concepts: Types

Every operation inside of a Java program manipulates data. Often, this data is stored in variables, which look similar to variables from mathematics.

Consider the mathematical equation x + 3 = 7. In it, x has the value 4, and it always will. You can set up another equation in which x has a different value, but it won’t change in this one. Java variables are different. They’re locations where you can store something. If you decide later that you want to change what you stored there, you can put something else into the same location, overwriting the old value.

In contrast to a variable is a literal. A literal is a concrete value that does not change, though it can be stored in a variable. Numbers like 4 or 2.71828183 are literals. We need to represent text and single characters in Java as well, and there are literals like "grapefruit segment" and 'X' that fill these roles.

In Java, both variables and literals have types. If you think of a variable as a box where you can hold something, its type is the shape of that box. The kinds of literals that can be stored in that box must have a matching shape. In the last chapter, we introduced the type int to store integer values and the type double to store floating-point values. Java is a strongly typed language, meaning that, if we declare a variable of type int, we can only put int values into it (with a few exceptions).

This idea of a type takes some getting used to. From a mathematical perspective 3 and 3.0 are identical. However, if you have an int variable in Java, you can store 3 into it, but you can’t store 3.0. The type of a value will never change, but you can convert a value from one type to an equivalent value in another type.

3.2.1. Types as sets and operations

Before we go any further, let’s look deeper at what a type really is. We can think of a type as a set of elements. For example, int represents a set of integer values (specifically, the integers between -2,147,483,648 and 2,147,483,647, inclusive). Consider the following declarations:

int x;
int y;

These declarations indicate that the variables named x and y can only contain integer values in the int range. Furthermore, a type only allows specific operations. In Java, the int type allows addition, subtraction, multiplication, division, and several other operations we’ll talk about in Section 3.3, but there’s no built-in operation to raise an int value to a power in Java. Let’s assume that x has type int. As we discussed in the previous chapter, the expression x + 2 performs addition between the variable represented by x and the literal 2. Some languages use the operator ^ to mean raising a number to a power. Following this notation, some beginning Java programmers are tempted to write x ^ 2 to compute x squared. The ^ operator does have a meaning in Java, but it doesn’t raise values to a power. Other combinations of operators are simply illegal, such as x # 2.

The idea of using types this way gives structure to a program. All operations are well-defined. For example, you know that adding two int values together will give you another int value. Java is a statically typed language. This means that it can analyze all the types you’re using in your program at compile-time and warn you if you’re doing something illegal. Consequently, you’ll get a lot of compiler warnings and errors, but you can be more confident that your program is correct if all the types make sense.

3.2.2. Primitive and reference types in Java

As shown in Figure 3.1(a), there are two kinds of types in Java: primitive types and reference types. Primitive types are like boxes that hold single, concrete values. The primitive types in Java are byte, short, int, long, float, double, boolean, and char. These are types provided by the designers of Java, and it’s not possible to create new ones. Each primitive type has a set of operators that are legal for it. For example, all the numerical types can be used with + and -. We’ll talk about these types and their operators in great detail in Section 3.3.

typesInJavaFigure
Figure 3.1 (a) The two categories of types in Java. The int primitive type (b), the boolean primitive type (c), the String reference type (d), and a possible Aircraft reference type (e) are represented as sets of items with operations.

Reference types work differently from primitive types. For one thing, a reference variable points at an object. This means that when you assign one reference variable to another, you aren’t getting a whole new copy of the object. Instead, you’re getting another arrow that points at the same object. The result is that performing an operation on one reference can effectively change another reference, if they’re both pointing at the same object.

Another difference is that reference variables (or simply references) do not have a large set of operators that work on them. Every variable can be used with the assignment (=) and the comparison (==) operators. Every variable can also be concatenated with a String by using the ` operator, but even if two objects represent numerical values, they can't be added together with the ` operator.

References should still be thought of as types defining a set of objects and operations that can be done with them. Instead of using operators, however, references use methods. You’ve seen methods such as System.out.print() and JOptionPane.showInputDialog() in the previous chapter. A method generally has a dot (.) before it and always has parentheses afterward. These parentheses contain the input parameters or arguments that you give to a method. Using operators on primitive types is convenient, but on the other hand, there is no limit to the number, kind, or complexity of methods that can be used on references.

Another important feature of reference types is that anyone can define them. So far, you’ve seen the reference types String and JOptionPane. As we’ll discuss later, String is an unusual reference type in that it is built deeply into the language. There are a few other types like this (such as Object), but most reference types could have been written by anyone, even you.

To create a new type, you write a class and define data and methods inside of it. If you wanted to define a type to hold airplanes, you might create the Airplane class and give it methods such as takeOff(), fly(), and land() because those are operations that any airplane should be able to do.

Once a class has been defined, it’s possible to instantiate an object. An object is a specific instance of the type. For example, the type might be Airplane, but the object might be referenced by a variable called sr71Blackbird. Presumably, this object has a weight, a maximum speed, and other characteristics that mark it as a Lockheed SR-71 “Blackbird,” the famous spy plane. To summarize: An object is a concrete instance of data. A reference is a variable that gives a name to (points to) an object. A type is a class that both the variable and the object have that defines what kinds of data the object contains and what operations it can perform.

The following table lists some of the differences between primitive types and reference types.

Primitive Types Reference Types

Created by the designers of Java

Created by any Java programmer

Use operators to perform operations

Use methods to perform operations

There are only eight different primitive types

The number of reference types is unlimited and grows every time someone creates a new class

Hold a specific numbers of bytes of data depending on the type

The referenced object can hold arbitrary amounts of data

Assignment copies a value from one place to another

Assignment copies an arrow that points at an object

Declaration creates a box to hold values

Declaration creates an arrow that can point at an object, but only instantiation creates a new object

3.2.3. Type safety

Why do we have types? There are weakly typed languages where you can store any value into almost any variable. Why bother with all these complicated rules? Most assembly languages have no notion of types and allow the programmer to manipulate memory directly.

Because Java is strongly typed, the type of every variable, whether primitive or reference, must be declared prior to its use. This constraint allows the Java compiler to perform many safety and sanity checks during compilation, and the JVM performs a few more during execution. These checks avoid errors during program execution that might otherwise be hard to find, errors that could lead to catastrophic failures of the program.

The Ariane 5 rocket is an example of a catastrophic failure due to a type error. On its first flight, the rocket left its flight path and eventually exploded. The failure was caused because of errors from converting a 64-bit floating-point to 16-bit signed integer value. The converted value was larger than the integer could hold, resulting in a meaningless value.

Converting from one type to another is called casting. The Ariane 5 failure was due to a problem with casting that was not caught. Even in Java, it’s possible for a human being to circumvent type safety with irresponsible casting.

3.3. Syntax: Types in Java

In this section we dig deeper into the type system in Java, starting with variables and moving on to the properties of the eight primitive types and the properties of String and other reference types.

3.3.1. Variables and literals

To use a variable in Java, you must first declare it, which sets aside memory to hold the variable and attaches a name to that space. Declarations always follows the same pattern. The type is written first followed by the identifier, or name, for the variable. Below we declare a variable named value of type int.

int value;

Note how we use the same pattern to declare a reference variable named creature of type Wombat.

Wombat creature;

You’re free to declare a variable and then end the line with a semicolon (;), but it’s common to initialize a variable at the same time. The following line simultaneously declares value and initializes it to 5.

int value = 5;
Pitfall: Multiple declarations

Don’t forget that you’re both declaring and initializing in a line like the above. Beginning Java programmers sometimes try to declare a variable more than once, as in the following:

int value = 5;
int value = 10;

Java won’t allow two variables with the same name to exist in the same block of code. The programmer probably intended the following, which reuses variable value and replaces its contents with 10.

int value = 5;
value = 10;

This error is more common when several other lines of code lie between the two assignments.

In some of the examples above, we’ve stored the value 5 into our variable value. The symbol 5 is an example of a literal. A literal is a value represented directly in code. It cannot be changed, but it can be stored into variables that have a matching type. The values stored into variables come from literals, input, or more complicated expressions. Just like variables, literals have types. The type of 5 is int while the type of 5.0 is double. Other types have literals written in ways we’ll discuss below.

3.3.2. Primitive types

The building blocks of all Java programs are primitive types. All objects must fundamentally contain primitive types deep down inside. There are eight primitive types. Half of them are used to represent integer values, and we’ll start by looking at those.

Integers: byte, short, int, and long

A variable intended to hold integer values can be declared with any of the four types byte, short, int, or long. All of them are signed (holding positive and negative numbers) and represent numbers in two’s complement. They only differ by the range of values that each type can hold. These ranges and the number of bytes used to represent variables from each type are given below.

Table 3.1 Ranges for primitive integer types in Java.
Type Bytes Range
byte

1

-128

to

127

short

2

-32,768

to

32,767

int

4

-2,147,483,648

to

2,147,483,647

long

8

-9,223,372,036,854,775,808

to

9,223,372,036,854,775,807

Note that the entire range of byte is included in that of short, of short in that of int, and so on. We say that short is broader than byte, int is broader than short, and long is broader than int.

A variable declared with type byte can only represent 256 different values, the integers in the range -128 to 127. Why use byte at all, then? Since a byte value only takes up a single byte, it can save memory, especially if you have a list of variables called an array, which we will discuss in Chapter 6. However, too narrow of a range will result in underflow and overflow. Java programmers are advised to stick with int for general use. If you need to represents values larger than 2 billion or smaller than -2 billion, use long. Once you’re an experienced programmer, you may occasionally use byte and short to save space, but they should be used sparingly and for a clear purpose.

Example 3.1 Integer variables

Consider the following declarations.

byte age;
int numberOfRooms;
long foreverCounter = 0;

The first of these statements declares age to be a variable of type byte. This declaration means that age can assume any value from the range for byte. For a human being, this limitation is reasonable (but dangerously close to the limit) since there is no documented case of a person living more than 122 years. Similarly, the next declaration declares numberOfRooms to be of type int. The last declaration declares foreverCounter to be of type long and initializes it to 0.

Since age is a variable, its value can change during program execution. Note that the above declaration of age does not assign a value to it. When they are declared, all integer variables are set to 0 by Java. However, to make sure that the programmer is explicit about what he or she wants, the compiler will give an error in most cases if a variable is used without first having its value set.

Like any other integer variable, we can assign age a value as follows.

age = 32;

Doing so assigns the value 32 to variable age. Note that the Java compiler would not complain if you were to assign -10 to the variable age, even though it’s impossible for a human to have a negative age (at least, without a time machine). Java attaches no meaning to the name you give to a variable.

Earlier, we said that variables had to match the type of literals you want to store into them. In the example above, we declared age with type byte and then stored 32 into it. What is the type of 32? Is it byte, short, int, or long? By default, all integer literals have type int, but they can be used with byte or short variables provided that they fit within the range. Thus, the following line causes an error.

byte fingers = 128;

If you want to specify a literal to have type long, you can append l or L to it. Thus, 42 is an int literal, but 42L is a long literal. You should always use the capital L since l can be difficult to distinguish from 1.

At the time of this writing, Java 11 is the newest version of Java, but Java 8 is most commonly used. In Java 7 and higher, you’re allowed to put any number of underscores (_) inside of numerical literals to break them up for the sake of readability. Thus, 123_45_6789 might represent a social security number, or you could use underscores instead of commas to write three million as 3_000_000. Since most compilers support Java 7 or higher now, it’s reasonable to use this underscore notation in your literals. Note that you should never use a comma in a numerical Java literal, no matter which version of Java you’re using.

Floating-point numbers: float and double

To represent numbers with fractional parts, Java provides two floating-point types, double and float. Because of limits on floating-point precision discussed in Chapter 1, Java cannot represent all real or rational numbers, but these types provide good approximations. If you have a variable that takes on floating-point values such as 3.14, 1.707 × 1025, 9.8, or similar, it ought to be declared as a double or a float.

Example 3.2 Floating-point declarations

Consider the following declarations.

float roomArea;
double avogadro = 6.02214179E23

The first of the above two statements declares roomArea to be of type float. Note that the declaration does not initialize roomArea to any value. Similar to integer primitive types, an uninitialized floating-point variable contains 0.0, but Java usually forces the programmer to assign a value to a variable before using it. The second of the above two statements declares avogadro to be a variable of type double and initializes it to the well-known Avogadro constant 6.02214179 × 1023. Note the use of E to mean “ten to the power of.” In Java, you could write 0.33 × 10-12 as 0.33E-12, or the number -4.325 × 1018 as -4.325E18 (or even -4.325E+18 if you’d like to write the sign of the exponent explicitly).

Accuracy in number representation

As discussed in Chapter 1, integer types within their specified ranges have exact representations. For example, if you assign 19 to a variable of type int and then print this value, you always get exactly 19. Floating-point numbers do not have this guarantee of exact representation.

Example 3.3 Floating-point accuracy

Try executing the following statements within a Java program.

double test = 0.0;
test += 0.1;
System.out.println(test);
test += 0.1;
System.out.println(test);
test += 0.1;
System.out.println(test);

Since we’re adding 0.1 each time, one would expect to see outputs of 0.1, 0.2, and 0.3. The first two numbers print as expected, but the third number prints out as 0.30000000000000004. It may seem counterintuitive, but 0.1 is a repeating decimal in binary, meaning that it cannot be represented exactly using the 64-bit IEEE floating-point standard. The System.out.println() method hides this ugliness by rounding the output past a certain level of precision, but by the third addition, the number has drifted far enough away from 0.3 that an unexpected number peeks out.

Variables of type float give you an accuracy of about 6 decimal digits while those of type double give about 15 decimal digits. Does the accuracy of floating-point number representation matter? The answer to this question depends on your application. In some applications, 6-digit accuracy may be adequate. However, when doing large-scale simulations, such as computing the trajectory of a spacecraft on a mission to Mars, 15-digit accuracy might be a matter of life or death. In fact, even double precision may not be enough. There is a special BigDecimal class which can perform arbitrarily high precision calculations, but due to its low speed and high complexity, it should only be used in those rare situations when a programmer requires a much higher level of precision than what double provides.

Java programmers are recommended to use double for general purpose computing. The float type should only be used in special cases where storage or speed are critical and accuracy is not. Because of its greater accuracy, double is considered a broader type than float. You can store float values in a double without losing precision, but the reverse is not true.

All floating-point literals in Java have type double unless they have an f or F appended on the end. Thus, 3.14 is a double literal, but 3.14f is a float literal.

Floating-point output

Formatting output for floating-point numbers has an extra complication compared with integer output: How many digits after the decimal point should be displayed? If you’re representing money, it’s common to show exactly two digits after the decimal point. By default, all of the non-zero digits are shown.

Instead of using System.out.print(), you can use System.out.format() to control formatting. When using System.out.format(), the first argument to the method is a format string, a piece of text that gives all the text you want to output as well as special format specifiers that indicate where other data is to appear and how it should be formatted. This method takes an additional argument for each format specifier you use. The specifier %d is for integer values, the specifier %f is for floating-point values (including both float and double types), and the specifier %s is for text. Consider the following example:

System.out.format("%s! I broke %d records in %f seconds.\n", "Bob", 3, 2.4985);

The output of this code is

Bob! I broke 3 records in 2.4985 seconds.

This kind of output is based on the printf() function used for output in the C programming language. It allows the programmer to have a holistic picture of what the final output might look like, but it also gives control of formatting through the format specifiers. For example, you can choose the number of digits for a floating-point value to display after the decimal point by putting a . and the number between the % and the f.

System.out.format("$%.2f\n", 123.456789 );

The output of this code is:

$123.46

Note that the last visible digit is rounded instead of truncated. Note that %n is a special format specifier that indicates a newline. To learn about other ways to use format strings to manipulate output, read the Oracle Formatter documentation.

Basic arithmetic

The following table lists the arithmetic operators available in Java. All of these operators can be used on both the integer primitive types and the floating-point primitive types.

Operator Meaning
+

Add

-

Subtract

*

Multiply

/

Divide

%

Modulus (remainder)

The first four of these should be familiar. Addition, subtraction, and multiplication work as you would expect, provided that the result is within the range defined for the types you’re using, but division is a little confusing. If you divide two integer values in Java, you’ll get an integer as a result. If there would have been a fractional part, it will be truncated, not rounded. Consider the following.

int x = 1999/1000;

In normal mathematics, 1,999 ÷ 1,000 = 1.999. In Java, 1999/1000 yields 1, and that’s what is stored in x. For floating-point numbers, Java works much more like normal mathematics.

double y = 1999.0/1000.0;

In this case, y contains 1.999. The literals 1999.0 and 1000.0 have type double. The type of y does not affect the division, but it had to be double to be a legal place to store the result.

Pitfall: Unexpected integer division

It’s easy to focus on the variable and forget about the types involved in the operation. Consider the following.

double z = 1999/1000;

Because z has type double, it seems that the result of the division should be 1.999. However, the dividend and the divisor have type int, and the result is 1. This value is converted into double and stored in z as 1.0. This mistake is more commonly seen in the following scenario.

double half = 1/2;

The code looks fine at first, but 1/2 yields 0. If the result is to be stored in a double variable, it’s better to multiply by 0.5 instead of dividing by 2.

You may not have thought about this idea since elementary school, but the division operator (/) finds the quotient of two numbers. The modulus operator (%) finds the remainder. For example, 15 / 6 is 2 while 15 % 6 is 3 because 6 goes into 15 twice with 3 left over. The modulus operator is usually used with integer values, but it’s also defined to work with floating-point values in Java. It’s easy to dismiss the modulus operator because we don’t often use it in daily life, but it’s incredibly useful in programming. On its face, it allows us to see the remainder after division. This idea can be applied to see if a number is even or odd. It can also be used to compress a large range of random integers to a smaller range or perform a kind of circular arithmetic useful for cryptography. Keep an eye out for it. We’ll use it many times in this book.

Precedence

Although all the previous examples use only one mathematical operator, you can combine several operators and operands into a larger expression like the following.

((a + b) * (c + d)) % e

Such expressions are evaluated from left to right, using the standard order of operations: The * and / (and also %) operators are given precedence over the + and - operators. Like in mathematics, parentheses have the highest precedence and can be used to add clarity. Thus, the order of evaluation of a + b / c is the same as a + (b / c) but different from (a + b) / c.

Example 3.4 Order of operations

Consider the following lines of code.

int a = 31;
int b = 16;
int c = 1;
int d = 2;
a = b + c * d - a / b / d;

What’s the result? The first operation to be evaluated is c * d, yielding 2. The next is a / b, yielding 1, which is then divided by d, yielding 0. Next, b + 2 gives 18, and 18 - 0 is still 18. Thus, the value stored in a is 18.

Your inner mathematician might be nervous that a is used in the expression on the right side of the assignment and is also the variable where the result is stored, but this situation is very common in programming. The value of a doesn’t change until after all the math has been done. The assignment always happens last.

All of the operators we’ve discussed so far are binary operators. This use of the word “binary” has nothing to do with base 2. A binary operator takes two things and does something, like adding them together. A unary operator takes a single operand and does something. The - operator can be used as a unary operator to negate a literal, variable, or expression. A unary negation has a higher precedence than the other operators, just like in mathematics. In other words, the variable or expression will be negated before it’s multiplied or divided. The + operator can be used anywhere you’d use a unary negation, although it doesn’t actually do anything. Consider the following statements.

int a = - 4;
int b = -c + d / -(e * f);
int s = +t + (-r);
Shortcuts

Some operations happen frequently in Java. For example, increasing a variable by some amount is a common task. If you want to increase the contents of variable value by 10, you can write the following.

value = value + 10;

Although the statement above is not excessively long, increasing a variable is common enough that there’s shorthand for it. To achieve the same effect, you can use the += operator.

value += 10;

The += operator gets the contents of the variable, in this case value, adds whatever is on its right side, in this case 10, and stores the result back into the variable. Essentially, it saves you from writing the name of the variable twice. And += is not the only shortcut. It’s only one member of a family of shortcut operators that perform a binary operation between the variable on the left side and the expression on the right side and then store the value back into the variable. There’s a -= operator that decreases a variable, a *= operator that scales a variable, and several others, including shortcuts for bitwise operations we cover in the next subsection.

Operator Example Meaning
+=
a += b;
a = a + b;
-=
a -= b;
a = a - b;
*=
a *= b;
a = a * b;
/=
a /= b;
a = a / b;
%=
a %= b;
a = a % b;
&=
a &= b;
a = a & b;
^=
a ^= b;
a = a ^ b;
|=
a |= b;
a = a | b;
<<=
a <<= b;
a = a << b;
>>=
a >>= b;
a = a >> b;
>>>=
a >>>= b;
a = a >>> b;

These assignment shortcuts are useful and can make a line shorter and easier to read.

Pitfall: Weak type checking with assignment shortcuts

Because you can lose precision, it’s not allowed to store a double value into an int variable. Thus, the following lines of code are illegal and will not compile.

int x = 0;
x = x + 0.1;

In this case, the check makes a lot of sense. If you could add 0.1 to 0 and then store that value into an int variable, the fractional part would be truncated, keeping 0 in the variable. However, this safeguard against lost precision is not done with assignment shortcuts. Even though we expect the following lines to be functionally identical to the previous ones, they will compile (but still do nothing).

int x = 0;
x += 0.1;

This kind of error can cause problems when the program expects the value of x to grow and eventually reach some level.

There are also two unary shortcuts. Incrementing a value by one and decrementing a value by one are such common operations that they get their own special operators, ++ and --.

Operator Example Meaning
++
a++;
a = a + 1;
--
a--;
a = a - 1;

Using either an increment or decrement changes the value of a variable. In all other cases, the use of an assignment operator is required to change a variable. Even in the binary shortcuts given before, the programmer is reminded that an assignment is occurring because the = symbol is present.

Both the increment and decrement operators come in prefix and postfix flavors. You can write the ++ (or the --) in front of the variable you’re changing or behind it.

int value = 5;
value++; // Now value is 6
++value; // Now value is 7
value--; // value is 6 again

When used in a line by itself, either flavor works exactly the same. However, the incremented (or decremented) variable can also be used as part of a larger expression. In a larger expression, the prefix form increments (or decrements) the variable before the value is used in the expression. Conversely, the postfix form gives back a copy of the original value, effectively incrementing (or decrementing) the variable after the value is used in the expression. Consider the following example.

int prefix = 7;
int prefixResult = 5 + ++prefix;

int postfix = 7;
int postfixResult = 5 + postfix++;

After the code is executed, the values of prefix and postfix are both 8. However, prefixResult is 13 while postfixResult is only 12. The original value of postfix, which is 7, is added to 5, and then the increment operation happens afterward.

Pitfall: Increment confusion

Incrementing a variable in Java is a very common operation. Expressions like i++ and ++i pop up so often that it’s easy to forget exactly what they mean. Programmers occasionally forget that they’re shorthand for i = i + 1 and begin to think of them as a fancy way to write i + 1.

When confused, a programmer might write something like the following.

int i = 14;
i = i++;

At first glance, it may appear that the second line of code really means i = i = i + 1. Assigning i an extra time is pointless, but it seems like it shouldn’t do any harm. Remember that the postfix version gives back a copy of the original value before it’s been incremented. In this case, i will be incremented, but then its original value will be stored back into itself. In the code given above, the final value of i is still 14.

In general it’s unwise to perform increment or decrement operations in the middle of larger expressions, and we advise against doing so. In some cases, code can be shortened by cleverly hiding an increment in the middle of some other expression. However, when reading back over the code, it always takes a moment to be sure that increment or decrement is doing exactly what it should. The additional confusion caused by this cleverness is not worth the line of code saved. Furthermore, the compiler will translate the operations into exactly the same bytecode, meaning that the shorter version is no more efficient than the longer version when executed.

Nevertheless, many programmers enjoy squeezing their code down to the smallest number of lines of code possible. You may have to read code that uses increments and decrements in clever (if obscure) ways, but you should always strive to make your own code as readable as possible.

Bitwise operators

In addition to normal mathematical operators, Java provides a set of bitwise operators corresponding to the operations we discussed in Chapter 1. These operators perform bitwise operations on integer values. The bitwise operators are &, |, ^, and ~ (which is unary). In addition, there are bitwise shift operators: << for signed left shift, >> for signed right shift, and >>> for unsigned right shift. There is no unsigned left shift operator in Java.

Operator Name Description
&

Bitwise AND

Combines two binary representations into a new representation which has a 1 in every position where both the original representations have a 1

|

Bitwise OR

Combines two binary representations into a new representation which has a 1 in every position where either of the original representations has a 1

^

Bitwise XOR

Combines two binary representations into a new representation which has a 1 in every position that the original representations have different values

~

Bitwise NOT

Takes a representation and creates a new representation in which every bit is flipped from 0 to 1 and 1 to 0

<<

Signed left shift

Moves all the bits the specified number of positions to the left, shifting 0s into the rightmost bits

>>

Signed right shift

Moves all the bits the specified number of positions to the right, padding the left with copies of the sign bit

>>>

Unsigned right shift

Moves all the bits the specified number of positions to the right, padding with 0s

When used with byte and short, all bitwise operators will automatically convert their operands to 32-bit int values. It’s crucial to remember this conversion since the number of bits used for representation is a fundamental part of bitwise operators.

The following example shows these operators in use. In order to understand the output, you need to understand how integers are represented in the binary number system, which is discussed in Section 1.3.

Example 3.5 Binary operators in Java

The following code shows a sequence of bitwise operations performed with the values 3 and -7. To understand the results, remember that, in 32-bit two’s complement representation, 3 = 0000 0000 0000 0000 0000 0000 0000 0011 and -7 = 1111 1111 1111 1111 1111 1111 1111 1001.

int x = 3;
int y = -7;
int z = x & y;
System.out.println("x & y\t= " + z);
z = x | y;
System.out.println("x | y\t= " + z);
z = x ^ y;
System.out.println("x ^ y\t= " + z);
z = x << 2;
System.out.println("x << 2\t= " + z);
z = y >> 2;
System.out.println("y >> 2\t= " + z);
z = y >>> 2;
System.out.println("y >>> 2\t= " + z);

The output of this fragment of code is:

x & y   = 1
x | y   = -5
x ^ y   = -6
x << 2  = 12
y >> 2  = -2
y >>> 2 = 1073741822

Note how the escape sequence \t is used to put a tab character in the output, making the results line up.

Why use the bitwise operators at all? Sometimes you may read data as individual byte values, and you might need to combine four of these values into a single int value. Although the signed left shift (<<) and signed right shift (>>) are, respectively, equivalent to repeated multiplications by 2 or repeated divisions by 2, they’re faster than doing these operations over and over. Finally, some of these operations are used for cryptographic or random number generation purposes.

Casting

Sometimes you need to use different types (like integers and floating-point values) together. Other times, you have a value in one type but need to store it in another (like when you’re rounding a double to the nearest int). Some combinations of operators and types are allowed, but others cause compiler errors.

The guiding rule is that Java allows an assignment from one type to another, provided that no precision is lost. That is, we can copy a value of one type into a variable of another type, provided that the destination variable has a broader type than the source value. The next few examples illustrate how to convert between different numerical types.

Example 3.6 Upcast with integers

Consider the following statements.

short x = 341;
int y = x;

Because the type of y is int, which is broader than short, it i’s legal to assign the value in x to variable y. In the assignment, a value with the narrower type short is converted to an equivalent value with the broader type int. Converting from a narrower type to a broader type is called an upcast or a promotion, and Java allows it with no complaint. Most languages allow upcasts without any special syntax because it’s always safe to move from a narrower, more restrictive type to a broader, less restrictive one.

Example 3.7 Downcast error

Consider these statements that declare variables a, b, and c and compute a value for c.

int a = 10;
int b = 2;
byte c;
c = a + b;

If you try compiling these statements as part of a Java program, you get an error message like the following.

Error: possible loss of precision
found: int
required: byte

The compiler generates the error above because the sum of two int values is another int value, which could be greater than the maximum value you can store in the byte variable c. In this example, you know that the value 12 doesn’t exceed the maximum of 127, but the Java compiler is inherently cautious. It complains whenever the type of the expression to be evaluated is broader than the type of the destination variable.

Example 3.8 Upcast from integers to floating-point

Integers are automatically converted to floating-point when needed. Consider the following statement.

double tolerance = 3;

The literal 3 has type int, but it’s automatically converted to the floating-point value 3.0 with type double. Again, double (and also float) are considered broader types than any integer types. Consequently, this type conversion is an upcast and is completely legal.

Upcasts also occur with arithmetic operations. Whenever you try to do arithmetic with two different numerical types, the narrower type is automatically upcast to the broader one.

double value = 3 + 7.2;

In this statement, 3 is automatically upcast to its double version 3.0 because 7.2 has the broader double type.

In order to perform a downcast, the programmer has to mark that he or she intends for the conversion to happen. A downcast is marked by putting the result type in parentheses before the expression you want converted. The next example illustrates how to cast a double value to type int.

Example 3.9 Downcast from double to int

The following statements cause a compiler error because an expression with type double cannot be stored into a variable with type int.

double roomArea = 3.5;
int houseArea = roomArea * 4.0;

A downcast can lose precision, and that’s why Java doesn’t allow it. Since a downcast is sometimes necessary, you can override Java’s type system with an explicit cast. To do so, we put the expected (or desired) result type in parentheses before the expression. In this case and many others, it’s also necessary to surround the expression with parentheses so that the entire expression (and not just roomArea) is converted to type int.

double roomArea = 3.5;
int houseArea = (int) (roomArea * 4.0);

In this case, the expression has value 14.0. Consequently, the int version is 14. In general, the value could have a fractional part. When casting from a floating-point type to an integer type, the fractional part is truncated not rounded. Consider the following statement:

int count = (int) 15.99999;

Mathematically, it seems obvious that 15.99999 should be rounded to the nearest int value of 16, but Java does not do this. Instead, the code above stores 15 into count. If you want to round the value, Java provides a method for rounding in the Math class. The rounding (instead of truncating) version is given below.

int count = (int) Math.round(15.99999);

The value given back by Math.round() has type long. The designers of the Math class chose long so that the same method could be used to round large double values into a long value, since the result might not fit in an int value. Since long is a broader type than int, we have to downcast the result to an int so that we can store it in count.

Example 3.10 Conversion from double to float

Consider the following declaration and assignment of variable roomArea.

float roomArea;
roomArea = 2.0;

This assignment is illegal in Java, and the compiler gives an error message like the following.

Error: possible loss of precision
found: double
required: float

As we mentioned earlier, the literal 2.0 has type double. When you try to assign a double value to a float variable, there’s always a risk that precision will be lost. The best way to avoid the error above is to declare roomArea with type double. Alternatively, we could store the float literal 2.0f into roomArea. We could also assign 2 instead of 2.0 to roomArea, since the upcast from int is done automatically.

Remember, you should almost always use the double type to represent floating-point numbers. Only in rare cases when you need to save memory should you use float values. By making it illegal to store 2.0 into a float variable, Java’s encouraging you to use high precision storage.

Numerical types and the conversions between them are critical elements of programming in Java, which has a strong mathematical foundation. In addition to these numerical types, Java also provides two other types that represent individual characters and Boolean values. We examine these next.

Characters: char

Sentences are made up of words. Words are made up of letters. Although we have discussed powerful tools for representing numbers in Java, we need a way to represent the letters and other characters we might find in printed text. Values with the char type are used to represent individual characters.

In the older languages of C and C++, the char type used 8 bits for storage. From Chapter 1, you know that you can represent up to 28 = 256 values with 8 bits. The Latin alphabet, which is used to write English, uses 26 letters. If we need to represent upper- and lowercase letters, the 10 decimal digits, punctuation marks, and quite a few other special symbols, 256 values is plenty. However, people all over the world use computers and want to store text from their language written in their script digitally. Taking the Chinese character system alone, some Chinese dictionaries list over 100,000 characters!

Java uses a standard called UTF-16 encoding to represent characters. UTF-16 is part of a larger international standard called Unicode, which is an attempt to represent most of the world’s writing systems as numbers that can be stored digitally. Most of the inner workings of Unicode aren’t important for day-to-day Java programming, but you can visit the Unicode site if you want more information.

In Java, each variable of type char uses 16 bits of storage. Therefore, each character variable could assume any value from among a total of 216 = 65,536 possibilities (although a few of these are not legal characters). Here are a few declarations and assignments of variables of type char.

char letter = 'A';
char punctuation = '?';
char digit = '7';

We’re storing char literals into each of the variables above. Most of the char literals you’ll use commonly are made by typing the single character you want in single quotes ('), such a 'z'. These characters can be upper- or lowercase letters, single numerical digits, or other symbols.

The space character literal is ' ', but some characters are harder to represent. For example, a new line (the equivalent of pressing <enter>) is represented as a single character, but we can’t type a single quote, hit <enter>, and then type the second quote. Instead, the character to represent a new line is '\n', which we will refer to simply as a newline. Every char variable can only hold a single character. It appears that '\n' has multiple characters in it, but it doesn’t. The use of the backslash (\) marks an escape sequence, which is a combination of characters used to represent a specific difficult to type or represent character. Here is a table of common escape sequences.

Escape Sequence Character
\n

Newline

\t

Tab

\'

Single quote

\\

Backslash

Remember, everything inside of a computer is represented with numbers, and each char value has some numerical equivalent. These numbers are arbitrary but systematic. For example, the character 'a' has a numerical value of 97, and 'b' has a numerical value of 98. The codes for all of the lowercase Latin letters are sequential in alphabetical order. (The codes for uppercase letters are sequential too, but there’s a gap between them and the lowercase codes.)

Some Unicode characters are difficult to type because your keyboard or operating system has no easy way to produce the character. Another kind of escape sequence allows you to specify any character by its Unicode value. There are large tables listing all possible Unicode characters by numerical values. If you want to represent a specific literal, you type '\uxxxx' where xxxx is a hexadecimal number representing the value. For example, '\u0064' converted into decimal is 16 × 6 + 4 = 100, which is the letter 'd'.

Example 3.11 Printing single characters

If you print a char variable or literal directly, it prints the character representation on the screen. For example, the following statement prints A not 65, the Unicode value of 'A'.

System.out.println('A');

However, the Unicode values are numbers. If you try to perform arithmetic on them, Java will treat them like numbers. For example, the following statement adds the integer equivalents of the characters (65 + 66 = 131), concatenates the sum with the String "C", and concatenates the result with a String representation of the int literal 999. The surprising final output is 131C999.

System.out.println('A' + 'B' + "C" + 999);
Booleans: boolean

If you’re new to programming, it may seem useless to have a type designed to hold only true and false values. These values are called Boolean values, and the logic used to manipulate them turns out to be crucial to almost every program. We use them to represent conditions in Chapter 4, Chapter 5, and beyond.

To store these truth values, Java uses the type boolean. There are exactly two literals for type boolean: true and false. Here are two declarations and assignments of boolean variables.

boolean awesome = true;
boolean testFailed = false;

If we could only store these two literals, boolean variables would have limited usefulness. However, Java provides a full range of relational operators that allow us to compare values. Each of these operators generates a boolean result. For example, we can test to see if two numbers are equal, and the answer is either true or false. All Java relational operators are listed in the table below. Assume that all variables used in the Example column have a numeric type.

Symbol Read as Example
==

equal to

x + 3 == y * 2
!=

not equal to

x !=  y / 4
<

less than

x < 3.5
<=

less than or equal to

x <= y
>

greater than

x > y+1
>=

greater than or equal to

x + y >= z
Example 3.12 Boolean variables

The following declarations and assignments illustrate some uses of boolean variables. Note the use of the relational operators == and >.

int x = 3;
int y = 4;
boolean same = (x == 3);
same = (x == y);
boolean xIsGreater = (x > y);

In the first use of == above, the value of same is true because the value of x is 3. In the second comparison, the value of same is false because the values of x and y are different. The value of xIsGreater is also false since the value of x is not greater than the value of y. All of the parentheses in this example are unnecessary and are used only for clarity.

In addition to the relational operators, Java also provides logical operators that can be used to combine or negate boolean values. These are the logical AND (&&), logical OR (||), logical XOR (^), and logical NOT (!) operators.

Name Operator Description

AND

&&

Returns true if both values are true

OR

||

Returns true if either value is true

XOR

^

Returns true if values are different

NOT

!

Returns the opposite of the value

All of these operators, except for NOT, are binary operators. Logical AND is used when you want your result to be true only if both the operands being combined evaluate to true. Logical OR is used when you want your result to be true if either operand is true. Logical XOR is used when you want your result to be true if one but not both of your operands is true. The unary logical NOT operator (!) results in the opposite value of its operand, switching true to false or false to true. Both the relational operators and the logical operators are described in greater detail in Chapter 4.

3.3.3. Reference types

Now we’ll move on to reference types, which vastly outnumber the primitive types, with new types created all the time. Nevertheless, the primitive types in Java are important, partly because they are the building blocks for reference types.

Recall that a variable with a reference type does not contain a concrete value like a primitive variable. Instead, the value it holds is a reference or arrow pointing to the “real” object. It’s like a name for an object. When you declare a reference variable in Java, it doesn’t initially point at anything, and you’ll get a compiler error if you try to use its value. For example, the following code creates a Wombat variable called w, but it doesn’t yet point at anything.

Wombat w;

To create an object in Java, you use the new keyword followed by the name of the type and parentheses, which can either be empty or contain data you want to use to initialize the object. This process is called invoking the constructor, which creates space for the object and then initializes it with the values you specify or with default values if you leave the parentheses empty. Below, we invoke the default Wombat constructor and point the variable w at the resulting object.

w = new Wombat();

Alternatively, a Wombat constructor might allow you to specify its mass in kilograms when creating one, as follows.

w = new Wombat(26.3);

Assignment of reference types points the two references to the same object. Thus, we can have two different Wombat references pointing at the same object.

Wombat w1 = new Wombat(26.3);
Wombat w2 = w1;
wombat
Figure 3.2 Two Wombat references pointing at the same object.

Then, anything we do to w1 will affect w2 and vice versa. For example, we can tell w1 to eat leaves using the eatLeaves() method.

w1.eatLeaves();

Perhaps this will increase the mass of the object that w1 points at to 26.9 kilograms. But the mass of the object that w2 points at will be increased as well, because they are the same object. Since primitive variables hold values and not references to objects, this kind of code works very differently with them. Consider the following.

int a = 10;
int b = a;
a = a + 5;

In this code, a is initialized to have a value of 10, and b is initialized to have whatever value a has, namely 10. The third line increases the value of a to 15, but b remains at 10.

primitive
Figure 3.3 Because they’re primitive, int variables store values, not references.

Now that we’ve highlighted some of the differences between primitive and reference types, we explain the String type more deeply. You use it frequently, but it has a few unusual features that are not shared by other reference types.

String basics

The String type is used to represent text in Java. A String object contains a sequence of zero or more char values. Unlike every other reference type, there is a literal form for String objects. These literals are written with the text you want to represent inside of double quotes ("), such as "Fight the power!". You can declare a String reference and initialize it by setting it equal to another String reference or a String literal. Like any other reference, you could leave it uninitialized.

There’s a difference between an uninitialized String (a reference that points to null) and a String of length 0. A String of length 0 is also known as an empty string and is written "". The space character (' ') and escape sequences such as '\n' can also be parts of a String and add to its length. For example, "ABC" contains three characters, but the String "A B C" has five, because the spaces on each side of 'B' count. The next example illustrates some ways of defining and using the String type.

Example 3.13 String assignment

The following declarations define two String references named greeting and title and initialize each with a literal.

String greeting = "Bonjour!"
String title = "French Greeting";

As you’ve seen in Chapter 2, we can output String values using System.out.print() and JOptionPane methods.

System.out.println(greeting);
JOptionPane.showMessageDialog(null, greeting, title, JOptionPane.INFORMATION_MESSAGE);

The first statement above displays Bonjour! on the terminal. The second statement creates a dialog box with the title French Greeting and the message Bonjour!

String operations

In Chapter 2, you saw that we can concatenate two String objects into a third String object using the + operator. This operator is unusual for a reference type. Almost all other reference types are only able to use the assignment operator (=) and the comparison operator (==). Like other reference types, the String class provides methods for interaction. We introduce a few String methods in this section and subsequent sections, but the String class defines many more.

Example 3.14 String concatenation

Here’s another example of combining String objects using the + operator.

String argument = "the cannon";
String phrase = "No argument but " + argument + "!";

In these statements, we initialize argument to "the cannon". We then compute the value of phrase by adding, or concatenating, three String values: "No argument but ", the value of argument, and "!". The result is "No argument but the cannon!". If argument had been initialized to "a pie in the face", then phrase would instead point to "No argument but a pie in the face!".

Another way of concatenating two String objects is by using the String concat() method.

String argument = "the cannon";
String exclamation = "!";
String phraseStart = "No argument but ";
String phrase = phraseStart.concat(argument);
phrase = phrase.concat(exclamation);

This sequence of statements gives the same result as the one above using the ` operator. In practice, the `concat()` method is rarely used because the ` operator is so convenient. Note that String objects in Java are immutable, meaning that calling a method on a String object will never change it. In the code above, calling concat() creates new String objects. The phrase reference points first at one String and then at a new String on the next line. In this case the reference can be changed, but a String object never changes once it’s been created. This distinction is a subtle but important one.

A host of other methods can be used on a String just like concat(). For example, the length of a String can be found using the length() method. The following statements prints 30 to the terminal.

String motto = "Fight for your right to party!";
System.out.println(motto.length()):

String literals are String objects as well, and you can call methods on them. The following code stores 11 into letters.

int letters = "cellar door".length();

Remember that a String is a sequence of char values. If you want to find out what char value sits at a particular location within a String, you can use the charAt() method.

This method is called with an int value giving the index you want to know about. Indexes inside of a String start at 0, not at 1. Zero-based numbering is used extensively in programming, and we discuss it further in Chapter 6. It may help if you think of the index as the number of characters that appear before the character at the specified index. The next example shows how charAt() can be used.

Example 3.15 Examining the char value at an index

To see what char is at a given location, we call charAt() with the index in question, as shown below.

String word = "antidisestablishmentarianism";
char letter = word.charAt(11);

In this case, letter is assigned the value 'b'. Remember, indexes for char values inside of a String start with 0. Thus, the char at index 0 is 'a', the char at index 1 is 'n', the char at index 2 is 't', and so on. If you count up to the twelfth char (which has index 11), it should be 'b'.

Every char inside of a String counts, whether it’s a letter, a digit, a space, punctuation, or some other symbol.

String text = "^_^ l337 #haxor# skillz!";
System.out.println(text.charAt(10));

This code prints out h since 'h' is the eleventh char (with index 10) in text.

A contiguous sequence of characters inside of a String is called a substring. For example, a few substrings of "Throw your hands in the air!" are "T", "Throw", "hands", and "ur ha". Note that "Ty" is not a substring because these characters don’t appear next to each other.

The next example shows how to use the substring() method to retrieve a substring from an existing String.

Example 3.16 Retrieving a substring

You can generate a substring of a String (which is, itself, a String) using the substring() method. The substring() method takes two arguments: the index where the substring starts and the index just after it ends, as shown in the following code.

String description = "slovenly";
String emotion = description.substring(1,5);
System.out.println(emotion);

This snippet of code prints love, since those are the characters at indexes 1 through 4 of "slovenly". Remember that String indexes are always zero-based. Also, the second argument of substring() is the index after the last one you want in your substring. Although this behavior is confusing, it’s a common design in many different string libraries in many different languages. One way to think about it is that the length of the substring is the second parameter minus the first. In this case, 5 - 1 = 4, the length of "love".

The String class also provides the indexOf() method to find the position of a substring, as shown in the next example.

Example 3.17 String search

Suppose we wish to find a String inside of another String. To do so, we call the indexOf() method on the String we’re searching inside of, with the String we’re searching for as the argument.

String countries = "USA Mexico China Canada";
String search = "China";
System.out.println(countries.indexOf(search));

The indexOf() method returns an int value that gives the position of the String we’re searching for. In the code above, the output is 11 because "China" appears starting at index 11 inside the countries String. Another way to think about it is that there are 11 characters before "China" in countries. If the given substring cannot be found, the indexOf() method returns -1. For example, -1 will be printed to the terminal if we replace the print statement above with the following.

System.out.println(countries.indexOf("Honduras"));

There are several other methods provided by String that we introduce as the need arises. If you are curious, you should look into the Java documentation for String in the Oracle String documentation for a complete list of available methods.

3.3.4. Assignment and comparison

Both assigning one variable to another and testing two variables to see if they’re equal to each other are important operations in Java. These operations are used on both primitive and reference types, but there are subtle differences between the two that we discuss below.

Assignment statements

Assignment is the act of setting one variable to the value of another. With a primitive type, the value held inside one variable is copied to the other. With a reference type, the arrow that points at the object is copied. All types in Java perform assignment with the assignment operator (=).

As we’ve discussed, values can be computed and then assigned to variables as in the following statement.

int data = Integer.parseInt(response);

In Java, a statement that computes a value and assigns it is called an assignment statement. The generic form of the assignment statement is as follows.

identifier = expression;

Here, identifier gives the name of some variable. For example, in the statement above, data is the name of the variable.

The right-hand side of an assignment statement is an expression that returns a value that’s assigned to the variable on the left-hand side. Even an assignment statement can be considered an expression, allowing us to stack multiple assignments into one line, as in the following code.

int a, b, c;
a = b = c = 15;

The Java compiler checks for type compatibility between the left and the right sides of an assignment statement. If the right-hand side is a broader type than the left-hand side (or is completely mismatched), the compiler gives an error, as in the following cases.

int number = 4.9;
String text = 9;
Comparison

Comparing two values to see if they’re the same uses the comparison operator (==) in Java. With primitive types, this kind of check is intuitive: The result is true if the two values are the same. With reference types, the value held by the variable is the arrow pointing to the object. Two reference variables could point to different objects with identical contents and return false when compared to each other. The following gives examples of these comparisons.

Example 3.18 Comparison

Consider the following lines of code.

int x = 5;
int y = 2 + 3;
boolean z = (x == y);

The value of variable z is true because x and y contain the same values. If x were assigned 6 instead, z would be false.

Now, consider the following code:

String thing1 = new String("Magical mystery");
String thing2 = new String("Magical mystery");
String thing3 = new String("Tragical tapestry");
differentObjectsFigure
Figure 3.4 Objects thing1, thing2, and thing3 (a) in their initial states and (b) after the assignment thing1 = thing2;.

This code declares and initializes three String values. Although it’s possible to store String literals directly without invoking a String constructor, we use this style of String creation to make our point since Java can do some confusing optimizations otherwise. Variables thing1 and thing2 point to String values that contain identical sequences of characters. Variable thing3 points to a different String. Consider the following statement.

boolean same = (thing1 == thing3);

In this case the value of same is clearly false because the two String values are not the same. What about the following case?

boolean same = (thing1 == thing2);

Again, same contains false. Although, thing1 and thing2 point at identical objects, they point at different identical objects. Since the value held by a reference is the arrow that points to the object, the comparison operator only shows that two references are the same if they point at the same object.

To better understand comparison between reference types, consider Figure 3.4(a), which shows three different objects. Note that each reference points at a distinct object, even though two objects have the same contents.

Now consider the following assignment.

thing1 = thing2;

As shown in Figure 3.4(b), this assignment points reference thing1 to the same location as reference thing2. Then, (thing1 == thing2) would be true.

The == operator is generally not very useful with references, and the equals() method should be used instead. This method compares the contents of objects in whatever way the designer of the type specifies. For example:

thing1.equals(thing2)

This statement is true when thing1 and thing2 are pointing at identical String objects even if they’re different objects.

3.3.5. Constants

In addition to normal variables, we can define named constants. A named constant is similar to a variable of the same type except that its value cannot be changed once set. A constant in Java is declared like any other variable with the addition of the keyword final before the declaration.

The convention in Java (and many other languages) is to name constants with all capital letters. Because camel case can no longer be used to tell where one word starts and another ends, an underscore (_) is used to separate words. Here are a few examples of named constant declarations.

final int POPULATION = 25000;
final double PLANCK_CONSTANT = 6.626E-34;
final boolean FLAG = false;
final char FIRST_INITIAL = 'A';
final String MESSAGE = "All your base are belong to us.";

In this code, the value of POPULATION is 25000 and cannot be changed. For example, if you now write POPULATION = 30000; on a later line, your compiler will give an error. PLANCK_CONSTANT, FLAG, FIRST_INITIAL, and MESSAGE are also defined as named constants. Because of the syntax Java uses, these constants are sometimes referred to as final variables.

In the case of MESSAGE and all other reference variables, being final means that the reference can never point at a different object. Even with a final reference, the objects themselves can change if their methods allow it. However, String objects can never change since they’re immutable.

Named constants are useful in two ways. First, a well-named constant can make your code more readable than using a literal. Second, if you do need to change the value to a different constant, you only have to change it in one place. For example, if you have used 25000 in five different places in your program, changing it to 30000 requires five changes. If you have used POPULATION throughout your program instead of a literal, you only have to change its value once.

3.4. Syntax: Useful libraries

Computer software is difficult to write, but many of the same problems come up over and over. If we had to solve these problems every time we wrote a program, we’d never get anywhere. Java allows us to use code other people have written called libraries. One selling point of Java is its large standard library that can be used by any Java programmer without special downloads. You’ve already used the Scanner class, the Math class, and perhaps the JOptionPane class, which are all part of libraries. Below, we’ll go deeper into the Math class and a few other useful libraries.

3.4.1. The Math library

Basic arithmetic operators are useful, but Java also provides a rich set of mathematical methods through the Math class. The following table lists a few of the methods available. For a complete list of methods provided by the Math class at the time of writing, see the Oracle Math documentation.

Table 3.2 A sample of methods available in the Java Math class. Arguments to trigonometric methods are given in radians.
Method Sample use Purpose

Trigonometric functions

cos()
double adjacent = hypotenuse * Math.cos(theta);

Find the cosine of the argument.

sin()
double opposite = hypotenuse * Math.sin(theta);

Find the sine of the argument.

tan()
double opposite = adjacent * Math.tan(theta);

Find the tangent of the argument.

Exponentiation and logarithms

exp()
double population = 250 * Math.exp(0.03 * time);

Compute ex, where x is the argument.

log()
double digits = Math.log(1000000);

Compute the natural logarithm of the argument.

pow()
double money = principal * Math.pow(1.0 + rate, time);

Compute ab, where a and b are the first and second arguments.

Miscellaneous

random()
double percent = Math.random();

Generate a random number x where 0.0 ≤ x < 1.0.

round()
long items = Math.round(material);

Round to the nearest long (or nearest int when rounding a float).

sqrt()
double hypotenuse = Math.sqrt(a*a+b*b);

Compute the square root of the argument.

Example 3.19 Math library usage

Here’s a program that uses the Math.pow() method to compute compound interest. Unlike Scanner and JOptionPane, the Math class is imported by default in Java programs and requires no explicit import statement.

Program 3.1 Computes interest earned and new balance.
import java.util.*;

class CompoundInterestCalculator {   
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);        
        System.out.println("Compound Interest Calculator");
        System.out.println();
        System.out.print("Enter starting balance: ");
        double startingBalance = in.nextDouble();
        System.out.print("Enter interest rate: ");
        double rate = in.nextDouble();
        System.out.print("Enter time in years: ");
        double years = in.nextDouble();        
        System.out.print("Enter compounding frequency: ");
        double frequency = in.nextDouble();
        double newBalance = startingBalance * 
            Math.pow(1.0 + rate/frequency, frequency*years);
        double interest = newBalance - startingBalance;
        System.out.println("Interest earned: $" + interest);
        System.out.println("New balance: $" + newBalance);
    }
}

In addition to methods, the Math library contains named constants including Euler’s number e and π. These are written in code as Math.E and Math.PI, respectively. For example, the following assignment statement computes the circumference of a circle with radius given by the variable radius, using the formula 2πr.

double circumference = 2*Math.PI*radius;

3.4.2. Random numbers

Random numbers are often needed in applications such as games and scientific simulations. For example, card games require a random distribution of cards. To simulate a deck of 52 cards, we could associate an integer from 1 to 52 with each card. If we had a list of these values, we could swap each value in the list with a value at a random location later in the list. Doing so is equivalent to shuffling the deck.

Java provides the Random class in package java.util to generate random values. Before you can generate a random number with this class, you need to create a Random object as follows.

Random random = new Random();

Here we’ve created an object named random of type Random. Depending on the kind of random value you need, you can use the nextInt(), nextBoolean(), or nextDouble() to generate a random value of the corresponding type.

// Random integer with all values possible
int balance = random.nextInt();

// Random integer between 0 (inclusive) and 130 (exclusive)
int humanAge = random.nextInt(130);

// Random boolean value
 boolean gender = random.nextBoolean();

// Random floating-point value between 0.0 (inclusive) and 1.0 (exclusive)
double percent = random.nextDouble();

In these examples, inclusive means that the number could be generated, while exclusive means that the number cannot be. Thus, the call random.nextInt(130) generates the integers 0 through 129 but never 130. Exclusive upper bounds on ranges of random values are very common in programming.

To generate a random int between values a and b, not including b, use the following code, assuming you have a Random object named random.

int count = random.nextInt(b - a) + a;

The nextInt() method call generates a value between 0 and b - a, and adding a shifts it into the range from a up to (but not including) b.

Generating a random double between values a and b is similar except that nextDouble() always generates a value between 0.0 and 1.0, not including 1.0. Thus, you must scale the output by b - a as shown below.

double value = random.nextDouble()*(b - a) + a;

The following example illustrates a potential use of random numbers in a video game.

Example 3.20 Dragon attribute generation

Suppose you’re designing a video in which the hero must fight a dragon with random attributes. Program 3.2 generates random values for the age, height, gender, and hit points of the dragon.

Program 3.2 Sets attributes of a randomly generated dragon for a video game.
import java.util.*; (1)

public class DragonAttributes {    
    public static void main(String[] args) {
        Random random = new Random(); (2)
        int age = random.nextInt(100) + 1; (3)
        double height = random.nextDouble()*30; (4)
        boolean gender = random.nextBoolean(); (5)
        int hitPoints = random.nextInt(51) + 25; (6)
        System.out.println("Dragon Statistics");
        System.out.println("Age:\t\t" + age);
        System.out.format("Height:\t\t%.1f feet\n", height);
        System.out.println("Female:\t\t" + gender);
        System.out.println("Hit points:\t" + hitPoints);
    }
}
1 We begin by importing java.util.* to include all the classes in the java.util package, including Random.
2 Then, we create an object random of type Random.
3 We use random to generate a random int between 0 and 99, to which we add 1, making an age between 1 and 100.
4 To generate the height, we multiply a random double by 75, yielding a value between 0.0 and 75.0 (exclusive).
5 Since there are only two choices for a dragon’s gender, we generate a random boolean value, interpreting true as female and false as male.
6 Finally, we determine the number of hit points the dragon has by generating a random int between 0 and 50, then add 25 to it, yielding a value between 25 and 75.

Because we are using random values, the output of Program 3.2 changes every time we run the program. Sample output is given below.

Dragon Statistics
Age:            90
Height:         13.7 feet
Female:         true
Hit points:     67

If you only need a random double value, you can generate a number between 0.0 and 1.0 (exclusive) using the Math.random() method from the Math class. This method is a quick and dirty way to generate random numbers without importing java.util.Random or creating a Random object.

The random numbers generated by the Random class and by Math.random() are pseudorandom numbers, meaning that they’re generated by a mathematical formula instead of truly random events. Each number is computed using the previous one, and the starting number is determined using time information from the OS. For most purposes, these pseudorandom numbers are good enough. Since each number can be predicted from the previous one, pseudorandom numbers are insufficient for some security applications. For those cases, Java provides the SecureRandom class which is slower than Random but produces random numbers that are much harder to predict.

3.4.3. Wrapper classes

Reference types have methods that allow a user to interact with them in many useful ways. The primitive types (byte, short, int, long, float, double, char, and boolean) do not have methods, but we sometimes need to manipulate them with methods or store them in a place that requires a reference type.

To deal with such situations, Java uses wrapper classes, reference types that correspond to each primitive type. Following Java conventions for class names, the wrapper types all start with an uppercase letter but are otherwise similar to the name of the primitive type they support: Byte, Short, Integer, Long, Float, Double, Character, and Boolean.

String to numerical conversions

A common task for a wrapper class is to convert a String representation of a number such as "37" or "2.097" to its corresponding numeric value. We had such a situation in Program 2.3, where we did the conversion as follows.

String response = JOptionPane.showInputDialog(null,
	"Enter the height: ", title, JOptionPane.QUESTION_MESSAGE);
height = Double.parseDouble(response);

This code uses the JOptionPane.showInputDialog() method to read from the user the height at which a ball is dropped. This method always returns data as a String. In order for us to do computation with the value, we need to convert it to a numeric type, such as an int or a double. To do so, we use the appropriate Byte.parseByte(), Short.parseShort(), Integer.parseInt(), Long.parseLong(), Float.parseFloat(), or Double.parseDouble() method.

The following example shows conversions from a String to a number using three of these methods.

Example 3.21 String to numeric conversion

Consider the following statements that show how a string can be converted to a numerical value.

String text = "15";
int count = Integer.parseInt(text);
float value = Float.parseFloat(text);
double tolerance = Double.parseDouble(text);

In this example, we declare a String object named text and initialize it to "15". Since text is a String and not a number, arithmetic expressions such as (text*29) are illegal.

To use the String "15" in a numerical computation, we need to convert it to a number. We used the Integer.parseInt(), Float.parseFloat(), and Double.parseDouble() methods to convert the String to int, float, and double values, respectively. Each method gives us 15 stored as the appropriate type.

What happens if the String "15.5" (or even "cinnamon") is given as input to the Integer.parseInt() method? If the String is not formatted as the appropriate kind of number, Java throws a NumberFormatException, probably crashing the program. An exception is an error or other unexpected situation that happens in the middle of running a program. We discuss how to work with exceptions in Chapter 12.

Character methods

When working with char values, it can be useful to know whether a particular value is a digit, a letter, or has a particular case. It may also be useful to convert a char to upper- or lowercase. Here is a partial list of the methods provided by the Character wrapper class to do these tasks.

Method Purpose
isDigit(char value)

Returns true if value is a numerical digit and false otherwise.

isLetter(char value)

Returns true if value is a letter and false otherwise.

isLetterOrDigit(char value)

Returns true if value is a digit or a letter and false otherwise.

isLowerCase(char value)

Returns true if value is a lowercase letter and false otherwise.

isUpperCase(char value)

Returns true if value is an uppercase letter and false otherwise.

isWhitespace(char value)

Returns true if value is a whitespace character such as space, tab, or newline and false otherwise.

toLowerCase(char value)

Returns a lowercase version of value, with no change if it is not a letter.

toUpperCase(char value)

Returns an uppercase version of value, with no change if it is not a letter.

For example, the variable test contains true after the following code is executed.

boolean test = Character.isLetter('x');

And the variable letter contains 'M' after the following code is executed.

char letter = Character.toUpperCase('m');

These methods can be especially useful when processing input.

Maximum and minimum values

As you recall from Chapter 1, integer arithmetic in Java has limitations. If you increase a large positive number past its maximum value, it becomes a large-magnitude negative number, a phenomenon called overflow. Conversely, if you decrease a large-magnitude negative number past its minimum value, it becomes a large positive number, a phenomenon called underflow.

With floating-point numbers, increasing their magnitudes past their maximum values results in special values that Java reserves to represent either positive or negative infinity, as the case may be. If a floating-point value gets too close to zero, it eventually rounds to zero.

In addition to useful conversion methods, the numerical wrapper classes also have constants for the maximum and minimum values for each type. Instead of trying to remember that the largest positive int value is 2,147,483,647, you can use the equivalent Integer.MAX_VALUE.

The MAX_VALUE constants are always the largest positive number that can be represented with the corresponding type. The MIN_VALUE is more confusing. For integer types, it’s the largest magnitude negative number. For floating-point types, it’s the smallest positive non-zero value that can be represented. Here is a table listing these constants.

Constant Meaning
Byte.MAX_VALUE

Most positive value a byte value can have

Byte.MIN_VALUE

Most negative value a byte value can have

Short.MAX_VALUE

Most positive value a short value can have

Short.MIN_VALUE

Most negative value a short value

Integer.MAX_VALUE

Most positive value an int value can have

Integer.MIN_VALUE

Most negative value an int value can have

Long.MAX_VALUE

Most positive value a long value can have

Long.MIN_VALUE

Most negative value a long value can have

Float.MAX_VALUE

Largest absolute value a float value can have

Float.MIN_VALUE

Smallest absolute value a float value can have

Double.MAX_VALUE

Largest absolute value a double value can have

Double.MIN_VALUE

Smallest absolute value a double value can have

The wrap-around nature of integer arithmetic means that adding 1 to Integer.MAX_VALUE results in Integer.MIN_VALUE. Note that all integer arithmetic in Java is done assuming type int, unless explicitly specified otherwise. Thus, Short.MAX_VALUE + 1 does not overflow to a negative value unless you store the result into a short. The same rules apply to underflow.

Overflow and underflow do not work in the same way with the floating-point numbers represented by float and double. The expression Double.MAX_VALUE + 1 results in Double.MAX_VALUE because 1 is so small in comparison that it’s lost in rounding error. However, 1.5*Double.MAX_VALUE results in Double.POSITIVE_INFINITY, a constant used to represent any value larger than Double.MAX_VALUE. Since Double.MIN_VALUE is the smallest non-zero number, Double.MIN_VALUE - 1 evaluates to -1.0.

Using wrapper classes for storage

Wrapper classes in Java have a split personality. On the one hand, the classes themselves can be used for the utility methods and constants we described above. However, objects of these same wrapper classes can be used in an entirely separate way to store primitive values. Each primitive type can be stored in its wrapper type as shown below.

Integer fingers = new Integer(5);
Double pi = new Double(3.141592);
Character question = new Character('?');

Why would we want to do this? There are many situations in which a library method or data structure requires a reference type, not a primitive type. These wrappers were designed for these cases when you have to treat a primitive type as an object.

Object value = new Integer(42);

To make working with wrapper classes easier, Java 5 and higher support automatic boxing and unboxing, meaning that primitive types will automatically be converted to their wrapper types (and vice versa) when appropriate. Thus, the earlier code could be written as follows.

Integer fingers = 5;
Double pi = 3.141592;
Character question = '?';

Programmers who don’t understand wrapper classes will sometimes use primitive types and wrapper classes interchangeably, mixing double and Double, for example. Avoid using wrapper classes unnecessarily, since they require more memory and more computation to perform operations.

Fortunately, automatic boxing and unboxing reduce the need to think about wrapper classes, and most programmers will rarely need to declare an explicit wrapper reference. We’ll discuss wrapper classes further in Chapter 18, where they are used to allow generic classes to store primitive types as well as reference types.

3.5. Solution: College cost calculator

In this chapter, we introduced and more fully explained many aspects of manipulating data in Java, including declaring variables, assigning values, performing simple arithmetic and more advanced math, inputting and outputting data, and using the type system, which includes subtle differences between primitive and reference types. Our solution to the college cost calculator problem posed at the beginning of the chapter uses all of these features at some level. We present this solution below.

import java.util.*; (1)

public class CollegeCosts {
    public static void main(String[] args) { (2)
        System.out.println("Welcome to the College Cost Calculator!"); (3)
        Scanner in = new Scanner(System.in); (4)
1 The first step in our solution is to import java.util.* so that we can use the Scanner class.
2 After we start the enclosing CollegeCosts class, we begin the main() method.
3 We print a welcome message for the user.
4 Then, we create a Scanner object.

Next is a sequence of prompts to the user interspersed with input done with the Scanner object.

        System.out.print("Enter your first name:\t\t");
        String firstName = in.next(); (1)
        System.out.print("Enter your last name:\t\t");
        String lastName = in.next(); (2)
        System.out.print("Enter tuition per semester:\t$");
        double semesterTuition = in.nextDouble(); (3)
        System.out.print("Enter rent per month:\t\t$");
        double monthlyRent = in.nextDouble(); (4)
        System.out.print("Enter food cost per month:\t$");
        double monthlyFood = in.nextDouble(); (5)
        System.out.print("Annual interest rate:\t\t");
        double annualInterest = in.nextDouble(); (6)
        System.out.print("Years to pay back your loan:\t");
        int years = in.nextInt(); (7)
1 The program reads the user’s first name as a String,
2 the user’s last name as a String,
3 the per-semester tuition cost as a double,
4 the monthly cost of rent as a double,
5 the monthly cost of food as a double,
6 the interest rate for the loan as a double,
7 and the number of years needed to pay back the loan as an int.

The next segment of code completes the computations needed.

        double yearlyCost = semesterTuition * 2.0 + (monthlyRent + monthlyFood) * 12.0; (1)
        double fourYearCost = yearlyCost * 4.0; (2)
        double monthlyInterest = annualInterest / 12.0; (3)
        double monthlyPayment = fourYearCost * monthlyInterest / (4)
            (1.0 - Math.pow(1.0 + monthlyInterest, -years * 12.0));
        double totalLoanCost = monthlyPayment * 12.0 * years; (5)
1 It finds the total yearly cost by doubling the semester cost, multiplying the monthly rent and food costs by 12, and summing the answers together.
2 The four year cost is simply four times the yearly cost.
3 To find the monthly payment, we find the monthly interest by dividing the annual interest rate by 12 and plugging this value into the formula from the beginning of the chapter.
4 Finally, the total cost of the loan is the monthly payment times 12 times the number of years.

All that remains is to print out the output.

        System.out.println("\nCollege costs for " + firstName + " " + lastName ); (1)
        System.out.println("***************************************");
        System.out.format("Yearly cost:\t\t\t$%.2f%n", yearlyCost); (2)
        System.out.format("Four year cost:\t\t\t$%.2f%n", fourYearCost);
        System.out.format("Monthly loan payment:\t\t$%.2f%n", monthlyPayment);
        System.out.format("Total loan cost:\t\t$%.2f%n", totalLoanCost );
    }
}
1 First, we output a header describing the following output as college costs for the user.
2 Using System.out.format() as described in Section 3.3.2, we print out the yearly cost, four year cost, monthly loan payment, and total cost, all formatted with dollar signs, two places after the decimal point, and tabs so that the output lines up.

3.6. Concurrency: Expressions

In Section 2.5, we introduced the ideas of task and domain decomposition that could be used to solve a problem in parallel. By splitting up the jobs to be done (as in task decomposition) or dividing a large amount of data into pieces (as in domain decomposition), we can attack a problem with several workers to finish the work more quickly.

3.6.1. Splitting expressions

Performing arithmetic is some of the only Java syntax we’ve introduced that can be used to solve problems directly, but evaluating a single mathematical expression usually does not warrant concurrency. If the terms in the expression are themselves complex functions (such as numerical integrations or simulations that produce answers), it might be reasonable to evaluate these functions concurrently.

In this section, we give an example of splitting an expression into smaller sub-expressions that could be evaluated concurrently. The basic steps underlying the concurrent evaluation of expressions are the following.

  • Identify sub-expressions that are independent of each other.

  • Create a separate thread to evaluate each sub-expression.

  • Combine the results from each thread to obtain a final answer.

While this sequence of steps looks simple, each step could be complex. Worse, being careless at any step could result in a concurrent solution that runs slower than the sequential solution or even gives the wrong answer. The following example illustrates these steps.

Example 3.22 Split expression

Consider the following statement:

double value = f(a,b)*g(c);

This statement evaluates methods f() and g(), multiplies the computed values, and assigns the result to variable value. In Figure 3.5, we show two ways of evaluating the expression f(a,b)*g(c). Figure 3.5(a) shows sequential evaluation of the expression, where f() is computed, g() follows, and then the two results are multiplied to get the final value. Figure 3.5(b) shows evaluation of the expression in which f() and g() are evaluated concurrently instead.

splitExpressionFigure
Figure 3.5 Computation of value = f(a,b)*g(c) with (a) sequential and (b) concurrent approaches.

On a multicore processor, the computation of f() and g() could be carried out on separate cores. We can create one thread for each method and wait for the threads to complete. Upon completion, we can retrieve the results of each computation and multiply them together as in Figure 3.5(b). Program 3.3 illustrates this concurrent approach.

Program 3.3 Concurrent evaluation of an expression.
public class SplitExpression {
    public static void main(String[] args) {
        ComputeF fThread = new ComputeF(3.14, 2.99); (1)
        ComputeG gThread = new ComputeG(5.55); (2)
        fThread.start(); (3)
        gThread.start(); (4)
        try {
            fThread.join();  (5)
            gThread.join(); (6)
            double fResult = fThread.getResult(); (7)
            double gResult = gThread.getResult(); (8)
            double answer = fResult*gResult; (9)
            System.out.println("Result of f: " + fResult );
            System.out.println("Result of g: " + gResult );
            System.out.println("Final answer: " + answer);        
        }
        catch(InterruptedException e){
            System.out.println("Computation interrupted!");
        }             
    }   
}
1 We create an object named fThread which takes two arguments, 3.14 and 2.99 in this example.
2 We create another object named gThread that takes one, 5.55. Both of these objects have types that extend the Thread class, which means that they can be made to run independently.
3 We start the first thread running.
4 We start the second thread running. Every object whose type is Thread (or a child of Thread, which we will discuss in Chapter 11) has a start() method which begins its execution as a separate thread.
5 We wait for the first thread to finish. How do we know when a thread is done executing? Every Thread object has a join() method. If some code calls a thread’s join() method, the method will not return until the thread is finished. When code is waiting for a thread to finish, it’s possible for it to be interrupted if some other thread has gotten tired of the code waiting around doing nothing. If that happens, an InterruptedException is thrown. Exceptions are the way that Java deals with errors and other unusual situations. We’ll discuss them further in Chapter 12, but for now, you only need to know that code (like the join() method) that can cause certain kinds of exceptions (like the InterruptedException) needs to be enclosed in a try block. After the try block comes a catch block that says what to do in the even of that exception. In our case, we print out "Computation interrupted!"
6 We wait for the second thread to finish.
7 Once the threads have completed their respective tasks, the execution of Program 3.3 resumes, and we obtain the result of the computation done by fThread by calling its getResult() method.
8 On the next line, we call the getResult() method on gThread to obtain its result. Note that we could have called these getResult() methods before the join() calls, but the computations might not have completed, yielding invalid or incorrect results (or crashing the program).
9 Finally, these two computed values are multiplied to get the final result, which is assigned to answer and printed.

We would like to show how classes ComputeF and ComputeG are written, but we’ll hold off since they use concepts relating to methods, class design, and inheritance that we’ll cover in Chapter 8, Chapter 9, and Chapter 11.

If you don’t understand all the elements of Program 3.3, don’t despair! We’re trying to give you an example of what concurrency looks like in Java, but you can’t be expected to master all the details at this stage. However, concurrency in Java will often follow the steps shown:

  1. Creation of Thread (or children of Thread) objects

  2. Calling the start() method on these objects to start them executing

  3. Calling the join() method on them to wait for them to finish

  4. Retrieving the results (if any) of the computations done by the objects

3.6.2. Care in splitting expressions

The above example illustrates how you could split an expression and evaluate it concurrently. Note the following points when deciding whether or not to use concurrency. First, your program will run faster concurrently only if the work done is complex enough that its computation takes significantly longer than the time to create the necessary threads. In the example above, the methods f() and g() must be complex enough that it takes a significant amount of time to evaluate them. Otherwise, concurrency won’t reduce the running time. This aspect of speedup is explained in detail in Chapter 13.

Second, splitting an expression (or any complex sequence of computations) is easy when its individual components are independent. If they are interdependent, splitting requires care to avoid subtle programming errors. Consider the expression f(a) + g(b) and suppose that f() modifies the value of b during execution. Such a modification is called a side effect. This side effect creates a dependency between f() and g(). Concurrent execution of these two methods must be done carefully, if it can be done at all. Chapter 14 discusses concurrency in the presence of dependencies.

3.7. Summary

In a strongly typed language such as Java, types are an important concept. Every literal and variable in Java has a type, which specifies the possible values items with that type could have and the operations that can be done with them. Types are used to catch programming errors at compile time.

Java has a small set of primitive types such as int and double that hold single values and use operators to manipulate them. Java also has reference types, which use primitive types as building blocks, can be created by any Java programmer, can contain arbitrarily complex data, and are manipulated with methods. One of the most commonly used reference types is String, which is used to store text of any length.

A number of library classes have been provided by the developers of Java. Programs performing mathematical operations beyond simple arithmetic may need to use methods from the Math class. Programs that need to generate random numbers can use methods from the Random class. Conversions and other useful manipulations of primitive types are provided by wrapper classes.

We also gave a taste of the syntax for creating, running, and waiting for the completion of threads. Such threads could be used to speed up the evaluation of computations on multicore processors, but only if the computations are long, complex, and not too interdependent.

3.8. Exercises

Conceptual Problems

  1. What is the difference between the set of integers from mathematics and the sets defined by int and long?

  2. In Example 3.7, the sum of two int variables was another int value, which could not be stored into a byte variable. Would this code have worked if variables a and b had been declared with type byte? What if a was assigned 121 and b was assigned 98?

  3. The following three statements are legal Java (if properly included inside of a method). However, if we changed 2 to 2.0 or 5 to 5.0, the statements would not be legal. Explain why.

    float roomArea = 2;
    float homeArea = 5;
    float area = roomArea * homeArea;
  4. Consider the following variable declarations.

    int x = 3, y = 4, z = -9;
    float p = 3.99f, q = -9.89f;
    int population1 = 15000, population2 = 8000;
    final double MAXIMUM_LEVEL = 350;
    double limitPerCapita = 0.03;
    int age = 14;
    final int MAXIMUM_AGE = 23;
    boolean allowed = false;

    Now evaluate each of the following expressions to true or false.

    1. MAXIMUM_LEVEL/population1 > limitPerCapita && MAXIMUM_LEVEL/population2 < limitPerCapita

    2. MAXIMUM_LEVEL/population1 > limitPerCapita || MAXIMUM_LEVEL/population2 < limitPerCapita

    3. age < MAXIMUM_AGE && allowed

    4. (x < y && y > z) || (p > q && population1 < population2)

  5. Evaluate the following expressions by hand and then check the results with a Java compiler.

    1. 5 & 6

    2. 5 | 6

    3. 5 ^ 6

    4. ~5

    5. 5 >> 2

    6. 5 << 2

    7. 5 >>> 2

  6. Evaluate the following expressions by hand and then check the results with a Java compiler.

    1. Byte.MIN_VALUE - 1

    2. Byte.MAX_VALUE + 1

    3. Integer.MIN_VALUE - 1

  7. Evaluate the following expressions by hand and then check the results with a Java compiler.

    1. Float.MAX_VALUE + 1

    2. Double.MAX_VALUE - 1

    3. -Double.MAX_VALUE - 1

    4. -Double.MIN_VALUE - 1

    5. -Double.MIN_VALUE + 1

  8. When evaluated in Java, the expression 2*Double.MAX_VALUE results in Double.POSITIVE_INFINITY to indicate that the maximum representable value has been exceeded. However, the expression Double.MAX_VALUE + 1 results in Double.MAX_VALUE. Why doesn’t the second case yield Double.POSITIVE_INFINITY as well?

  9. Explain what is printed when the following statements are executed.

    System.out.println(15 + 20);
    System.out.println("15" + 20);
    System.out.println("" + 15 + 20);
  10. For each of the following Java expressions, indicate the types of each value being used and the type of the result when the expression is evaluated.

    1. 3 + 4

    2. 3 + 4.0

    3. 3.0 + 4.0

    4. 3.0f + 4.0

    5. (double)(3 + 4)

    6. (double)(3.0 + 4.0)

    7. Math.round(3 * 4.2)

    8. Math.round(3.2 * 4.9)

    9. Math.round(15.5 * 4.0)

    10. (int)(15.5 * 4.0)

    11. Math.round(3.154)

  11. For each of the following expressions, determine the maximum amount of concurrency that can be achieved. Using a diagram similar to Figure 3.5(b), show how the computation of each expression will proceed. Assume there are no side effects. Note that you can create separate threads for multiple instances of method f().

    1. f(a) + f(b) + f(c)

    2. f(a * g(b))

    3. f(g(a)) + f(b) + f(c)

  12. Answer the following questions about types, values, and references.

    1. What is the difference between a value and the type that the value has?

    2. In Java, the primitive type int represents a limited set of integers, not the entire set of integers from mathematics. Why is this the case? Why didn’t the designers of Java allow int to represent all integers?

    3. How are operations defined for reference types?

    4. Explain the subtle difference between a reference and an object in Java.

  13. Consider the following declarations of three Car objects.

     Car car1 = new Car("Mercedes", "C300 Sport", 75000);
     Car car2 = new Car("Pontiac", "Vibe", 17000);
     Car car3 = new Car("Mercedes", "C300 Sport", 75000);

    Let same be a variable of type boolean. What is the value of variable same after each of the following statements? Assume that the equals() method will return true if all of the attributes specified by the constructors for the two objects are the same.

    1. same = (car1 == car2);

    2. same = (car1 == car3);

    3. same = car1.equals(car3);

    4. car2 = car3;

    5. same = (car2 == car1);

    6. same = (car2 == car3);

    7. same = car2.equals(car3);

  14. Characters 'a' and 'A' have Unicode values \u0061 and \u0041, respectively. Give the representation of these two characters as 16-bit unsigned binary integers.

  15. Assuming that each character occupies 16 bits (two bytes) in memory and is encoded using Unicode, use hexadecimal numbers to show how the word "Java" will be represented in computer memory. Unicode values for the Latin alphabet are the same as the values for the older ASCII standard. You can find a listing of these values on many websites such as AsciiTable.

  16. What is the output from the following sequence of statements?

    String p = "Break it";
    String q = "down like this!";
    System.out.println((p + q).length());
  17. What is the output from the following sequence of statements? Note that r contains a single space character.

    String p = "This is not a string.";
    String q = "";
    String r = " ";
    System.out.println((p + q + r).length());

Programming Practice

  1. Try compiling the following program and observe the error reported by the compiler.

    public class UninitializedString {
         public static void main(String[] args) {
              String greeting;
              System.out.println(greeting);
        }
    }

    Now initialize the greeting object and rerun the program. Why does the program compile now?

  2. Write a Java program that prompts the user to enter the number of rooms in her home, uses a Scanner object to read the input into an int variable named rooms, and then outputs the value on the screen. If you compile and execute your program and type in the value 3.5, can you explain the output you see?

  3. Convert the college cost calculator solution given in Section 3.5 to use JOptionPane methods for both input and output. Use the header giving the user’s first and last name for the title of the output dialog and omit the line of asterisks. If you put all the output in a single String with a newline (\n) separating each line, the output will display properly.