InformIT

Fundamental Programming Structures in Java

By

Date: Feb 25, 2026

Sample Chapter is provided courtesy of Pearson.

Return to the article

At this point, you should have successfully installed the JDK and executed the sample programs from Chapter 2. It’s time to start programming. This chapter shows you how the basic programming concepts such as data types, branches, and loops are implemented in Java.

3.1. A Simple Java Program

Let’s look more closely at one of the simplest Java programs you can have—one that merely prints a message to console:

void main() {
    IO.println("We will not use 'Hello, World!'");
}

First and foremost, Java is case sensitive. If you made any mistakes in capitalization (such as typing Main instead of main), the program will not run.

The program declares a method called main. The term “method” is Java-speak for a function—a block of code that carries out a specific task. You must have a main method in every program. You can, of course, add your own methods and call them from the main method.

Notice the braces { } in the source code. In Java, as in C/C++, braces are used to form a group of statements (called a block). In Java, the code for any method must be started by an opening brace { and ended by a closing brace }.

Brace styles have inspired an inordinate amount of useless controversy. This book follows a compact style that is common among Java programmers, sometimes called the “Kernighan and Ritche” style. In other styles, matching braces line up. As whitespace is irrelevant to the Java compiler, you can use whatever brace style you like.

The main method calls another method, called println, defined in the IO class. You will learn a lot more about classes in the next chapter. For now, think of a class as a container for the program logic that defines the behavior of an application. Classes are the building blocks with which all Java applications are built.

In fact, everything in a Java program lives inside a class, even our main method. It is placed inside a class whose name is the name of the file, without the extension. If we place the code in a file named FirstSample.java, main is a method of a class FirstSample.

The standard naming convention (used in the name FirstSample) is that class names are nouns that start with an uppercase letter. If a name consists of multiple words, use an initial uppercase letter in each of the words. This use of uppercase letters in the middle of a name is sometimes called “camel case” or, self-referentially, “CamelCase.”

Now turn your attention to the contents inside the braces of the main method,

IO.println("We will not use 'Hello, World!'");

This is the body of the method. The body of most methods contains multiple statements, but here we have just one. As with most programming languages, you can think of Java statements as sentences of the language. In Java, every statement must end with a semicolon. In particular, carriage returns do not mark the end of a statement, so statements can span multiple lines if need be.

Here, we are calling the println method that is declared in a class called IO. Notice the period that separates the name of the IO class and the println method.

The println method receives a string argument. The method displays the string argument on the console. It then terminates the output line, so that each call to println displays its output on a new line. Notice that Java, like C/C++, uses double quotes to delimit strings. (You can find more information about strings later in this chapter.)

Methods in Java, like functions in any programming language, can use zero, one, or more arguments, which are enclosed in parentheses. Even if a method has no arguments, you must still use empty parentheses. For example, a variant of the println method with no arguments just prints a blank line. You invoke it with the call

IO.println();

You compile the file with the command

You run the sample program with this command:

java FirstSample.java

When the program executes, it simply displays the string We will not use 'Hello, World!' on the console.

If you intend to run a program multiple times, it is more efficient to compile it first:

javac FirstSample.java

You end up with a file containing the bytecodes for this class. These are instructions for the Java virtual machine. The Java compiler names the bytecode file FirstSample.class and stores it in the same directory as the source file. Whenever you want to launch the program, issue the following command:

java FirstSample

Remember to leave off the .class extension.

When you use

java ClassName

to run a compiled program, the Java virtual machine is launched, and execution starts with the code in the main method of the class you indicate.

3.2. Comments

Comments in Java, as in most programming languages, do not show up in the executable program. Thus, you can add as many comments as needed without fear of bloating the code. Java has three ways of marking comments. The most common form is a //. Use this for a comment that runs from the // to the end of the line.

IO.println("We will not use 'Hello, World!'"); // is this too cute?

When longer comments are needed, you can mark each line with a //, or you can use the /* and */ comment delimiters that let you block off a longer comment.

Finally, a third kind of comment is used to generate documentation automatically. This comment uses a /** to start and a */ to end. You can see this type of comment in Listing 3.1. For more on this type of comment and on automatic documentation generation, see Chapter 4.

Listing 3.1 FirstSample.java

/**
 * This is the first sample program in Core Java Chapter 3
 */
void main() {
    IO.println("We will not use 'Hello, World!'");
}

3.3. Data Types

Java is a strongly typed language. This means that every variable must have a declared type. There are eight primitive types in Java. Four of them are integer types; two are floating-point number types; one is the character type char, used for UTF-16 code units in the Unicode encoding scheme (see Section 3.3.3); and one is a boolean type for truth values.

3.3.1. Integer Types

The integer types are for numbers without fractional parts. Negative values are allowed. Java provides the four integer types shown in Table 3.1.

Table 3.1: Java Integer Types

Type

Storage Requirement

Range (Inclusive)

byte

1 byte

–128 to 127

short

2 bytes

–32,768 to 32,767

int

4 bytes

–2,147,483,648 to 2,147,483,647 (just over 2 billion)

long

8 bytes

–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

In most situations, the int type is the most practical. If you want to represent the number of inhabitants of our planet, you’ll need to resort to a long. The byte and short types are mainly intended for specialized applications, such as low-level file handling, or for large arrays when storage space is at a premium.

Under Java, the ranges of the integer types do not depend on the machine on which you will be running the Java code. This alleviates a major pain for the programmer who wants to move software from one platform to another, or even between operating systems on the same platform. In contrast, C and C++ programs use the most efficient integer type for each processor. As a result, a C program that runs well on a 32-bit processor may exhibit integer overflow on a 16-bit system. Since Java programs must run with the same results on all machines, the ranges for the various types are fixed.

Long integer numbers have a suffix L or l (for example, 4000000000L). Hexadecimal numbers have a prefix 0x or 0X (for example, 0xCAFE). Octal numbers have a prefix 0 (for example, 010 is 8)—naturally, this can be confusing, and few programmers use octal constants.

You can write numbers in binary, with a prefix 0b or 0B. For example, 0b1001 is 9. You can add underscores to number literals, such as 1_000_000 (or 0b1111_0100_0010_0100_0000) to denote one million. The underscores are for human eyes only. The Java compiler simply removes them.

3.3.2. Floating-Point Types

The floating-point types denote numbers with fractional parts. The two floating-point types are shown in Table 3.2.

Table 3.2: Floating-Point Types

Type

Storage Requirement

Range

float

4 bytes

Approximately ±3.40282347×1038 (6–7 significant decimal digits)

double

8 bytes

Approximately ±1.79769313486231570×10308 (15 significant decimal digits)

The name double refers to the fact that these numbers have twice the precision of the float type. (Some people call these double-precision numbers.) The limited precision of float (6-7 significant digits) is simply not sufficient for many situations. Use float values only when you work with a library that requires them, or when you need to store a very large number of them.

Java 20 adds a couple of methods (Float.floatToFloat16 and Float.float16toFloat) for storing “half-precision” 16-bit floating-point numbers in short values. These are used for implementating neural networks.

Numbers of type float have a suffix F or f (for example, 3.14F). Floating-point numbers without an F suffix (such as 3.14) are always considered to be of type double. You can optionally supply the D or d suffix (for example, 3.14D).

An E or e denotes a decimal exponent. For example, 1.729E3 is the same as 1729.

All floating-point computations follow the IEEE 754 specification. In particular, there are three special floating-point values to denote overflows and errors:

For example, the result of dividing a positive floating-point number by 0 is positive infinity. Dividing 0.0 by 0 or the square root of a negative number yields NaN.

3.3.3. The char Type

The char type was originally intended to describe individual characters. However, this is no longer the case. Nowadays, some Unicode characters can be described with one char value, and other Unicode characters require two char values. Read the next section for the gory details.

Literal values of type char are enclosed in single quotes. For example, 'A' is a character constant with value 65. It is different from "A", a string containing a single character. Values of type char can be expressed as hexadecimal values that run from \u0000 to \uFFFF.

Besides the \u escape sequences, there are several escape sequences for special characters, as shown in Table 3.3. You can use these escape sequences inside quoted character literals and strings, such as '\u005B' or "Hello\n". The \u escape sequence (but none of the other escape sequences) can even be used outside quoted character constants and strings. For example,

void main()\u007BIO.println("Hello, World!");\u007D

is perfectly legal—\u007B and \u007D are the encodings for { and }.

Table 3.3: Escape Sequences for Special Characters

Escape Sequence

Name

Unicode Value

\b

Backspace

\u0008

\t

Tab

\u0009

\n

Line feed

\u000a

\r

Carriage return

\u000d

\f

Form feed

\u000c

\"

Double quote

\u0022

\'

Single quote

\u0027

\\

Backslash

\u005c

\s

Space. Used in text blocks to retain trailing whitespace.

\u0020

\newline

In text blocks only: Join this line with the next

—

3.3.4. Unicode and the char Type

To fully understand the char type, you have to know about the Unicode encoding scheme. Before Unicode, there were many different character encoding standards: ASCII in the United States, ISO 8859-1 for Western European languages, KOI-8 for Russian, GB18030 and BIG-5 for Chinese, and so on. This caused two problems. First, a particular code value corresponds to different letters in the different encoding schemes. Second, the encodings for languages with large character sets have variable length: Some common characters are encoded as single bytes, others require two or more bytes.

Unicode was designed to solve both problems. When the unification effort started in the 1980s, a fixed 2-byte code was more than sufficient to encode all characters used in all languages in the world, with room to spare for future expansion—or so everyone thought at the time. In 1991, Unicode 1.0 was released, using slightly less than half of the available 65,536 code values. Java was designed from the ground up to use 16-bit Unicode characters, which was a major advance over other programming languages that used 8-bit characters.

Unfortunately, over time, the inevitable happened. Unicode grew beyond 65,536 characters, primarily due to the addition of a very large set of ideographs used for Chinese, Japanese, and Korean. Now, the 16-bit char type is insufficient to describe all Unicode characters.

We need a bit of terminology to explain how this problem is resolved in Java. A code point is an integer value associated with a character in an encoding scheme. In the Unicode standard, code points are written in hexadecimal and prefixed with U+, such as U+0041 for the code point of the Latin letter A. Unicode has code points that are grouped into 17 code planes, each holding 65536 characters. The first code plane, called the basic multilingual plane, consists of the “classic” Unicode characters with code points U+0000 to U+FFFF. Sixteen additional planes, with code points U+10000 to U+10FFFF, hold many more characters called supplementary characters.

How a Unicode code point (that is, an integer ranging from 0 to hexadecimal 10FFFF) is represented in bits depends on the character encoding. You could encode each character as a sequence of 21 bits, but that is impractical for computer hardware. The UTF-32 encoding simply places each code point into 32 bits, where the top 11 bits are zero. That is rather wasteful. The most common encoding on the Internet is UTF-8, using between one and four bytes per character. See Chapter 2 of Volume II for details of that encoding.

Java strings use the UTF-16 encoding. It encodes all Unicode code points in a variable-length code of 16-bit units, called code units. The characters in the basic multilingual plane are encoded as a single code unit. All other characters are encoded as consecutive pairs of code units. Each of the code units in such an encoding pair falls into a range of 2048 unused values of the basic multilingual plane, called the surrogates area ('\uD800' to '\uDBFF' for the first code unit, '\uDC00' to '\uDFFF' for the second code unit). This is rather clever, because you can immediately tell whether a code unit encodes a single character or it is the first or second part of a supplementary character. For example, the beer mug emoji 🍺 has code point U+1F37A and is encoded by the two code units '\uD83C' and '\uDF7A'. (See https://tools.ietf.org/html/rfc2781 for a description of the encoding algorithm.) Each code unit is stored as a char value. The details are not important. All you need to know is that a single Unicode character may require one or two char values.

You cannot ignore characters with code units above U+FFFF. Your customers may well write in a language where these characters are needed, or they may be fond of putting emojis such as 🍺 into their messages.

Nowadays, Unicode has become so complex that even code points no longer correspond to what a human viewer would perceive as a single character or symbol. This happens with languages whose characters are made from smaller building blocks, with emojis that can have modifiers for gender and skin tone, and with an ever-growing number of other compositions.

Consider the pirate flag 🏴‍☠️. You perceive a single symbol: the flag. However, this symbol is composed of four Unicode code points: U+1F3F4 (waving black flag), U+200D (zero width joiner), U+2620 (skull and crossbones), and U+FE0F (variation selector-16). In Java, you need five char values to represent the flag: two char for the first code point, and one each for the other three.

In summary, a visible character or symbol is encoded as a sequence of some number of char values, and there is almost never a need to look at the individual values. Always work with strings (see Section 3.6) and don’t worry about their representation as char sequences.

3.3.5. The boolean Type

The boolean type has two values, false and true. It is used for evaluating logical conditions. You cannot convert between integers and boolean values.

3.4. Variables and Constants

As in every programming language, variables are used to store values. Constants are variables whose values don’t change. In the following sections, you will learn how to declare variables and constants.

3.4.1. Declaring Variables

In Java, every variable has a type. You declare a variable by placing the type first, followed by the name of the variable. Here are some examples:

double salary;
int vacationDays;
long earthPopulation;
boolean done;

Notice the semicolon at the end of each declaration. The semicolon is necessary because a declaration is a complete Java statement, which must end in a semicolon.

The identifier for a variable name (as well as for other names) is made up of letters, digits, currency symbols, and “punctuation connectors.” The first character cannot be a digit.

Symbols like '+' or '©' cannot be used inside variable names, nor can spaces. Letter case is significant: main and Main are distinct identifiers. The length of an identifier is essentially unlimited.

The terms “letter,” “digit,” and “currency symbol” are much broader in Java than in most languages. A letter is any Unicode character that denotes a letter in a language. For example, German users can use umlauts such as ä in variable names; Greek speakers could use a π. Similarly, digits are 0–9 and any Unicode characters that denote a digit. Currency symbols are $, €, ¥, and so on. Punctuation connectors include the underscore character _, a “wavy low line” ﹏, and a few others. In practice, most programmers stick to A-Z, a-z, 0-9, and the underscore _.

You also cannot use a Java keyword such as class as a variable name.

Underscores can be parts of identifiers. This is common for constant names, such as Double.POSITIVE_INFINITY. However, a single underscore _ is a keyword.

You can declare multiple variables on a single line:

int i, j; // both are integers

I don’t recommend this style. If you declare each variable separately, your programs are easier to read.

3.4.2. Initializing Variables

After you declare a variable, you must explicitly initialize it by means of an assignment statement—you can never use the value of an uninitialized variable. For example, the Java compiler flags the following sequence of statements as an error:

int vacationDays;
IO.println(vacationDays); // ERROR--variable not initialized

You assign to a previously declared variable by using the variable name on the left, an equal sign (=), and then some Java expression with an appropriate value on the right.

int vacationDays;
vacationDays = 12;

You can both declare and initialize a variable on the same line. For example:

int vacationDays = 12;

Finally, in Java you can put declarations anywhere in your code. For example, the following is valid code in Java:

double salary = 65000.0;
IO.println(salary);
int vacationDays = 12; // OK to declare a variable here

In Java, it is considered good style to declare variables as closely as possible to the point where they are first used.

3.4.3. Constants

In Java, you use the keyword final to denote a constant. For example:

void main() {
    final double CM_PER_INCH = 2.54;
    double paperWidth = 8.5;
    double paperHeight = 11;
    IO.println("Paper size in centimeters: "
        + paperWidth * CM_PER_INCH + " by " + paperHeight * CM_PER_INCH);
}

The keyword final indicates that you can assign to the variable once, and then its value is set once and for all. It is customary to name constants in all uppercase.

It is probably more common in Java to create a constant so it’s available to all methods of a class:

final double CM_PER_INCH = 2.54;

void main() {
    double paperWidth = 8.5;
    double paperHeight = 11;
    IO.println("Paper size in centimeters: "
        + paperWidth * CM_PER_INCH + " by " + paperHeight * CM_PER_INCH);
}

// CM_PER_INCH also accessible in other methods

You will see in Chapter 4 how a class can declare constants that are usable in other classes. For example, the Math class declares a constant PI that you can use as Math.PI.

3.4.4. Enumerated Types

Sometimes, a variable should only hold a restricted set of values. For example, you may sell clothes or pizza in four sizes: small, medium, large, and extra large. Of course, you could encode these sizes as integers 1, 2, 3, 4 or characters S, M, L, and X. But that is an error-prone setup. It is too easy for a variable to hold a wrong value (such as 0 or m).

You can define your own enumerated type whenever such a situation arises. An enumerated type has a finite number of named values. For example,

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };

Now you can declare variables of this type:

Size s = Size.MEDIUM;

A variable of type Size can hold only one of the values listed in the type declaration, or the special value null that indicates that the variable is not set to any value at all. (See Chapter 4 for more information about null.)

Enumerated types are discussed in greater detail in Chapter 5.

3.5. Operators

Operators are used to combine values. As you will see in the following sections, Java has a rich set of arithmetic and logical operators and mathematical functions.

3.5.1. Arithmetic Operators

The usual arithmetic operators +, -, *, and / are used in Java for addition, subtraction, multiplication, and division.

The / operator denotes integer division if both operands are integers, and floating-point division otherwise. Integer division by 0 raises an exception, whereas floating-point division by 0 yields an infinite or NaN result.

Integer remainder (sometimes called modulus) is denoted by %. For example, 15 / 2 is 7, 15 % 2 is 1, and 15.0 / 2 is 7.5.

3.5.2. Mathematical Functions and Constants

The Math class contains an assortment of mathematical functions that you may occasionally need, depending on the kind of programming that you do.

To take the square root of a number, use the sqrt method:

double x = 4;
double y = Math.sqrt(x);
IO.println(y); // prints 2.0

The Java programming language has no operator for raising a quantity to a power: You must use the pow method in the Math class. The statement

double y = Math.pow(x, a);

sets y to be x raised to the power a (xa). The pow method’s arguments are both of type double, and it returns a double as well.

The Math class supplies the usual trigonometric functions:

Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2

and the exponential function with its inverse, the natural logarithm, as well as the decimal logarithm:

Math.exp
Math.log
Math.log10

Java 21 adds a method Math.clamp that forces a number to fit within given bounds. For example:

Math.clamp(-1, 0, 10) // too small, yields lower bound 0
Math.clamp(11, 0, 10) // too large, yields upper bound 10
Math.clamp(3, 0, 10) // within bounds, yields value 3

Finally, three constants denote the closest possible approximations to the mathematical constants π, τ = 2π, and e:

Math.PI
Math.TAU
Math.E

3.5.3. Conversions between Numeric Types

It is often necessary to convert from one numeric type to another. Figure 3.1 shows the legal conversions.

FIGURE 3.1

Figure 3.1: Legal conversions between numeric types

The six solid arrows in Figure 3.1 denote conversions without information loss. The three dotted arrows denote conversions that may lose precision. For example, a large integer such as 123456789 has more digits than the float type can represent. When the integer is converted to a float, the resulting value has the correct magnitude but loses some precision.

int n = 123456789;
float f = n; // f is 1.23456792E8

When two values are combined with a binary operator (such as n + f where n is an integer and f is a floating-point value), both operands are converted to a common type before the operation is carried out.

3.5.4. Casts

In the preceding section, you saw that int values are automatically converted to double values when necessary. On the other hand, there are obviously times when you want to consider a double as an integer. Numeric conversions are possible in Java, but of course information may be lost. Conversions in which loss of information is possible are done by means of casts. The syntax for casting is to give the target type in parentheses, followed by the variable name. For example:

double x = 9.997;
int nx = (int) x;

Now, the variable nx has the value 9 because casting a floating-point value to an integer discards the fractional part.

If you want to round a floating-point number to the nearest integer (which in most cases is a more useful operation), use the Math.round method:

double x = 9.997;
int nx = (int) Math.round(x);

Now the variable nx has the value 10. You still need to use the cast (int) when you call round. The reason is that the return value of the round method is a long, and a long can only be assigned to an int with an explicit cast because there is the possibility of information loss.

3.5.5. Assignment

There is a convenient shortcut for using binary operators in an assignment. For example, the compound assignment operator

x += 4;

is equivalent to

x = x + 4;

(In general, place the operator to the left of the = sign, such as *= or %=.)

Note that in Java, an assignment is an expression. That is, it has a value—namely, the value that is being assigned. You can use that value—for example, to assign it to another variable. Consider these statements:

int x = 1;
int y = x += 4;

The value of x += 4 is 5, since that’s the value that is being assigned to x. Next, that value is assigned to y.

Many programmers find such nested assignments confusing and prefer to write them more clearly, like this:

int x = 1;
x += 4;
int y = x;

3.5.6. Increment and Decrement Operators

Programmers, of course, know that one of the most common operations with a numeric variable is to add or subtract 1. Java, following in the footsteps of C and C++, has both increment and decrement operators: n++ adds 1 to the current value of the variable n, and n-- subtracts 1 from it. For example, the code

int n = 12;
n++;

changes n to 13. Since these operators change the value of a variable, they cannot be applied to numbers themselves. For example, 4++ is not a legal statement.

There are two forms of these operators; you’ve just seen the postfix form of the operator that is placed after the operand. There is also a prefix form, ++n. Both change the value of the variable by 1. The difference between the two appears only when they are used inside expressions. The prefix form does the addition first; the postfix form evaluates to the old value of the variable.

int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16, m is 8
int b = 2 * n++; // now b is 14, n is 8

Many programmers find this behavior confusing. In Java, using ++ inside expressions is uncommon.

3.5.7. Relational and boolean Operators

Java has the full complement of relational operators. To test for equality, use a double equal sign, ==. For example, the value of

3 == 7

is false.

Use a != for inequality. For example, the value of

3 != 7

is true.

Finally, you have the usual < (less than), > (greater than), <= (less than or equal), and >= (greater than or equal) operators.

Java, following C++, uses && for the logical “and” operator and || for the logical “or” operator. As you can easily remember from the != operator, the exclamation point ! is the logical negation operator. The && and || operators are evaluated in “short-circuit” fashion: The second operand is not evaluated if the first operand already determines the value. If you combine two expressions with the && operator,

expression1 && expression2

and the truth value of the first expression has been determined to be false, then it is impossible for the result to be true. Thus, the value for the second expression is not calculated. This behavior can be exploited to avoid errors. For example, in the expression

x != 0 && 1 / x > x + y // no division by 0

the second operand is never evaluated if x equals zero. Thus, 1 / x is not computed if x is zero, and no divide-by-zero error can occur.

Similarly, the value of expression1 || expression2 is automatically true if the first expression is true, without evaluating the second expression.

3.5.8. The Conditional Operator

Java provides the conditional ?: operator that selects a value, depending on a Boolean expression. The expression

condition ? expression1 : expression2

evaluates to the first expression if the condition is true, and to the second expression otherwise. For example,

x < y ? x : y

gives the smaller of x and y.

3.5.9. Switch Expressions

If you need to choose among more than two values, then you can use a switch expression, which was introduced in Java 14. It looks like this:

String seasonName = switch (seasonCode) {
    case 0 -> "Spring";
    case 1 -> "Summer";
    case 2 -> "Fall";
    case 3 -> "Winter";
    default -> "???";
};

The expression following the switch keyword is called the selector expression, and its value is the selector. For now, we only consider selectors and case labels that are numbers, strings, or constants of an enumerated type. In Chapter 5, you will see how to use switch expressions with other types for pattern matching.

A case label must be a compile-time constant whose type matches the selector type. You can provide multiple labels for each case, separated by commas:

int numLetters = switch (seasonName) {
    case "Spring", "Summer", "Winter" -> 6;
    case "Fall" -> 4;
    default -> -1;
};

When you use the switch expression with enumerated constants, you need not supply the name of the enumeration in each label—it is deduced from the switch value. For example:

enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
. . .
Size itemSize = . . .;
String label = switch (itemSize) {
    case SMALL -> "S"; // no need to use Size.SMALL
    case MEDIUM -> "M";
    case LARGE -> "L";
    case EXTRA_LARGE -> "XL";
};

In the example, it was legal to omit the default since there was a case for each possible value.

3.5.10. Bitwise Operators

For any of the integer types, you have operators that can work directly with the bits that make up the integers. This means that you can use masking techniques to get at individual bits in a number. The bitwise operators are

& ("and")   | ("or")   ^ ("xor")   ~ ("not")

These operators work on bit patterns. For example, if n is an integer variable, then

int fourthBitFromRight = (n & 0b1000) / 0b1000;

gives you a 1 if the fourth bit from the right in the binary representation of n is 1, and 0 otherwise. Using & with the appropriate power of 2 lets you mask out all but a single bit.

There are also >> and << operators which shift a bit pattern right or left. These operators are convenient when you need to build up bit patterns to do bit masking:

int fourthBitFromRight = (n & (1 << 3)) >> 3;

Finally, a >>> operator fills the top bits with zero, unlike >> which extends the sign bit into the top bits. There is no <<< operator.

3.5.11. Parentheses and Operator Hierarchy

Table 3.4 shows the precedence of operators. If no parentheses are used, operations are performed in the hierarchical order indicated. Operators on the same level are processed from left to right, except for those that are right-associative, as indicated in the table. For example, && has a higher precedence than ||, so the expression

a && b || c

means

(a && b) || c

Since += associates right to left, the expression

a += b += c

means

a += (b += c)

That is, the value of b += c (which is the value of b after the addition) is added to a.

Table 3.4: Operator Precedence

Operators

Associativity

[] . () (method call)

Left to right

! ~ ++ -- + (unary) - (unary) () (cast) new

Right to left

* / %

Left to right

+ -

Left to right

<< >> >>>

Left to right

< <= > >= instanceof

Left to right

== !=

Left to right

&

Left to right

^

Left to right

|

Left to right

&&

Left to right

||

Left to right

?:

Right to left

= += -= *= /= %= &= |= ^= <<= >>= >>>=

Right to left

3.6. Strings

Conceptually, Java strings are sequences of Unicode characters. As you have seen in Section 3.3.4, the concept of what exactly a character is has become complicated. And the encoding of the characters into char values has also become complicated.

However, most of the time, you don’t care. You get strings from string literals or from methods, and you operate on them with methods of the String class. The following sections cover the details.

3.6.1. Concatenation

Java, like most programming languages, allows you to use + to join (concatenate) two strings.

String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;

The preceding code sets the variable message to the string "Expletivedeleted". (Note the lack of a space between the words: The + operator joins two strings in the order received, exactly as they are given.)

When you concatenate a string with a value that is not a string, the latter is converted to a string. For example,

int age = 13;
String rating = "PG" + age;

sets rating to the string "PG13".

This feature is commonly used in output statements. For example,

IO.println("The answer is " + answer);

is perfectly acceptable and prints what you would expect (and with correct spacing because of the space after the word is).

If you need to put multiple strings together, separated by a delimiter, use the join method:

String all = String.join(" / ", "S", "M", "L", "XL");
    // all is the string "S / M / L / XL"

The repeat method produces a string that repeats a given string a number of times:

String repeated = "Java".repeat(3); // repeated is "JavaJavaJava"

3.6.2. Static and Instance Methods

At the end of the preceding section, you saw two methods of the String class, join and repeat. There is a crucial difference between these two methods. When you call

String all = String.join(" / ", "S", "M", "L", "XL");

you provide all arguments that the method needs inside the parentheses. Contrast this with the call

String repeated = "Java".repeat(3);

To compute the repetition of a string, two pieces of information are required: the string itself, and the number of times that it should be repeated.

Note that the string is written before the name of the method, with a dot (.) separating the two. The repeat method is an example of an instance method. As you will see in Chapter 4, an instance method has one special argument; in this case, a string. That value precedes the method name. Supplementary arguments are provided after the method name in parentheses.

The String.join method, on the other hand, is a static method. It doesn’t have a special argument. The dot serves a different function, separating the name of the class in which the method is declared from the method name.

To tell the two apart, locate the dot. Is it preceded by a value (such as the string "Java")? Then you are looking at the call to an instance method. Or is it preceded by the name of a class (such as String)? Then it is a static method.

Many of the methods that you have seen so far, including IO.println, Integer.parseInt, and Math.sqrt, are static methods. However, as you learn more about Java, you will mostly use instance methods.

3.6.3. Indexes and Substrings

Java strings are sequences of char values. As you saw in Section 3.3.4, the char data type is used for representing Unicode code points in the UTF-16 encoding. Some characters can be represented with a single char value, but many characters and symbols require more than one char value.

The length instance method yields the number of char values required for a given string. For example:

String greeting = "Ahoy 🏴‍☠️";
int n = greeting.length(); // is 10

The call s.charAt(n) returns the char value at position n, where n is between 0 and s.length() – 1. (Like C and C++, Java counts positions in a string starting with 0.) For example:

char first = greeting.charAt(0); // first is 65 or 'A'
char last = greeting.charAt(9); // last is 65039

However, these calls are not very useful. The last char value is just a part of the flag symbol, and you won’t generally care what these values are.

Still, you sometimes need to know where a substring is located in a string. Use the indexOf method:

String sub = " ";
int start = greeting.indexOf(sub); // 4

As it happens, the position or index of the space is 4, but the exact value doesn’t matter. It depends on the characters preceding the substring, and the number of char values needed to encode each of them. Always treat an index as an opaque number, not the count of perceived characters preceding it.

You can compute where the next character starts:

int nextStart = start + sub.length(); // 5

The string " " has length 1, but do not hard-code the length of a string. Always use the length method instead.

You can extract a substring from a larger string with the substring method of the String class. For example,

String greeting = "Hello, World!";
int a = greeting.indexOf(",") + 2; // 7
int b = greeting.indexOf("!"); // 12
String s = greeting.substring(a, b);

creates a string consisting of the characters "World".

The second argument of substring is the first position that you do not want to copy. In our case, we copy everything from the beginning up to, but not including, the comma.

Note that the string s.substring(a, b) always has length b − a. For example, the substring "World" has length 12 − 7 = 5.

3.6.4. Strings Are Immutable

The String class gives no methods that let you change a character in an existing string. If you want to turn greeting into "Help!", you cannot directly change the last positions of greeting into 'p' and '!'. If you are a C programmer, this can make you feel pretty helpless. How are we going to modify the string? In Java, it is quite easy: Concatenate the substring that you want to keep with the characters that you want to replace.

String greeting = "Hello";
int n = greeting.indexOf("lo");
greeting = greeting.substring(0, n) + "p!";

This declaration changes the current value of the greeting variable to "Help!".

Since you cannot change the individual characters in a Java string, the documentation refers to the objects of the String class as immutable. Just as the number 3 is always 3, the string "Hello" will always contain the code-unit sequence for the characters H, e, l, l, o. You cannot change these values. Yet you can, as you just saw, change the contents of the string variable greeting and make it refer to a different string, just as you can make a numeric variable currently holding the value 3 hold the value 4.

Isn’t that a lot less efficient? It would seem simpler to change the characters than to build up a whole new string from scratch. Well, yes and no. Indeed, it is some amount of work to generate a new string that holds the concatenation of "Hel" and "p!". But immutable strings have one great advantage: The compiler can arrange that strings are shared.

To understand how this works, think of the various strings as sitting in a common pool. String variables then point to locations in the pool. If you copy a string variable, both the original and the copy share the same characters.

Overall, the designers of Java decided that the efficiency of sharing outweighs the inefficiency of string creation. Look at your own programs; most of the time, you probably don’t change strings—you just compare them. (There is one common exception—assembling strings from individual characters or from shorter strings that come from the keyboard or a file. For these situations, Java provides a separate class—see Section 3.6.9.)

3.6.5. Testing Strings for Equality

To test whether two strings are equal, use the equals method. The expression

s.equals(t)

returns true if the strings s and t are equal, false otherwise. Note that s and t can be string variables or string literals. For example, the expression

"Hello".equals(greeting)

is perfectly legal. To test whether two strings are identical except for the upper/lowercase letter distinction, use the equalsIgnoreCase method.

"Hello".equalsIgnoreCase("hello")

Do not use the == operator to test whether two strings are equal! It only determines whether or not the strings are stored in the same location. Sure, if strings are in the same location, they must be equal. But it is entirely possible to store multiple copies of identical strings in different places.

String greeting = "Hello"; // initialize greeting to a string
greeting == "Hello" // true
greeting.substring(0, greeting.indexOf("l")) == "He" // false
greeting.substring(0, greeting.indexOf("l")).equals("He") // true

If the virtual machine always arranges for equal strings to be shared, then you could use the == operator for testing equality. But only string literals are shared, not strings that are computed at runtime. Therefore, never use == to compare strings. Always use equals instead.

3.6.6. Empty and Null Strings

The empty string "" is a string of length 0. You can test whether a string is empty by calling

if (str.length() == 0)

or

if (str.equals(""))

or , for optimum efficiency

if (str.isEmpty())

An empty string is a Java object which holds the string length (namely, 0) and an empty contents. However, a String variable can also hold a special value, called null, that indicates that no object is currently associated with the variable. To test whether a string is null, use

if (str == null)

Sometimes, you need to test that a string is neither null nor empty. Then use

if (str != null && str.length() != 0)

You need to test that str is not null first. As you will see in Chapter 4, it is an error to invoke a method on a null value.

3.6.7. The String API

The String class in Java contains close to 100 methods. The following API note summarizes the most useful ones.

These API notes, found throughout the book, will help you understand the Java Application Programming Interface (API). Each API note starts with the name of a class, such as java.lang.String. (The significance of the so-called package name java.lang is explained in Chapter 4.) The class name is followed by the names, explanations, and parameter descriptions of one or more methods. A parameter variable of a method is the variable that receives a method argument. For example, as you will see in the first API note below, the charAt method has a parameter called index of type int. If you call the method, you supply an argument of that type, such as str.charAt(0).

The API notes do not list all methods of a particular class but present the most commonly used ones in a concise form. For a full listing, consult the online documentation (see Section 3.6.8).

The number following the class name is the JDK version number in which it was introduced. If a method has been added later, it has a separate version number.

3.6.8. Reading the Online API Documentation

As you just saw, the String class has lots of methods. Furthermore, there are thousands of classes in the standard libraries, with many more methods. It is plainly impossible to remember all useful classes and methods. Therefore, it is essential that you become familiar with the online API documentation that lets you look up all classes and methods in the standard library. You can download the API documentation from Oracle and save it locally, or you can point your browser to https://docs.oracle.com/en/java/javase/25/docs/api.

The API documentation has a search box (see Figure 3.2). Older versions have frames with lists of packages and classes. You can still get those lists by clicking on the Frames menu item. For example, to get more information on the methods of the String class, type “String” into the search box and select the type java.lang.String, or locate the link in the frame with class names and click it. You get the class description, as shown in Figure 3.3.

FIGURE 3.2

Figure 3.2: The Java API documentation

FIGURE 3.3

Figure 3.3: Class description for the String class

When you scroll down, you reach a summary of all methods, sorted in alphabetical order (see Figure 3.4). Click on any method name for a detailed description of that method (see Figure 3.5). For example, if you click on the compareToIgnoreCase link, you’ll get the description of the compareToIgnoreCase method.

FIGURE 3.4

Figure 3.4: Method summary of the String class

FIGURE 3.5

Figure 3.5: Detailed description of a String method

3.6.9. Building Strings

Occasionally, you need to build up strings from shorter strings, such as keystrokes or words from a file. It would be inefficient to use string concatenation for this purpose. Every time you concatenate strings, a new String object is constructed. This is time consuming and wastes memory. Using the StringBuilder class avoids this problem.

Follow these steps if you need to build a string from many small pieces. First, construct an empty string builder:

StringBuilder builder = new StringBuilder();

You can also provide initial content:

StringBuilder builder = new StringBuilder("INVOICE\n");

Each time you need to add another part, call the append method.

builder.append(str); // appends a string
builder.appendCodePoint(cp); // appends a single code point

The latter method is occasionally useful when you need to compute a code point. Here is an example. Flag emojis are made up of two code points, each in the range between 127462 (regional indicator symbol letter A) to 127487 (regional indicator symbol letter Z). Now suppose you have a country string such as "IT". Then you can compute the code points as follows:

final int REGIONAL_INDICATOR_SYMBOL_LETTER_A = 127462;
String country = . . .;
builder.appendCodePoint(country.charAt(0) - 'A' + REGIONAL_INDICATOR_SYMBOL_LETTER_A);
builder.appendCodePoint(country.charAt(1) - 'A' + REGIONAL_INDICATOR_SYMBOL_LETTER_A);

When you are done building the string, call the toString method. You will get a String object with the character sequence contained in the builder.

String completedString = builder.toString();

Cleverly, the StringBuilder methods return the builder object, so that you can chain multiple method calls:

String completedString = new StringBuilder()
    .append(str)
    .appendCodePoint(cp)
    .toString();

The String class doesn’t have a method to reverse the Unicode characters of a string, but StringBuilder does. To reverse a string, use this code snippet:

String reversed = new StringBuilder(original).reverse().toString();

The following API notes contain the most important methods for the StringBuilder class.

3.6.10. Text Blocks

The text block feature, added in Java 15, makes it easy to provide string literals that span multiple lines. A text block starts with """, followed by a line feed. The block ends with another """:

String greeting = """
Hello
World
""";

A text block is easier to read and write than the equivalent string literal:

"Hello\nWorld\n"

This string contains two \n: one after Hello and one after World. The newline after the opening """ is not included in the string literal.

If you don’t want a newline after the last line, put the closing """ immediately after the last character:

String prompt = """
Hello, my name is Hal.
Please enter your name:""";

Text blocks are particularly suited for including code in some other language, such as SQL or HTML. You can just paste it between the triple quotes:

String html = """
<div class="Warning">
   Beware of those who say "Hello" to the world
</div>
""";

All escape sequences from regular strings work the same way in text blocks.

Note that you don’t have to use escape sequences with the quotation marks around Hello. There are just two situations where you need to use the \" escape sequence in a text block:

Unfortunately, you still need the escape sequence \\ to denote a backslash in a text block.

There is one escape sequence that only works in text blocks. A \ directly before the end of a line joins this line and the next. For example,

"""
Hello, my name is Hal. Please enter your name:""";

is the same as

"Hello, my name is Hal. Please enter your name:"

Line endings are normalized by removing trailing whitespace and changing any Windows line endings (\r\n) to simple newlines (\n). If you need to preserve trailing spaces, turn the last one into a \s escape. In fact, that’s what you probably want for prompt strings. The following string ends in a space:

"""
Hello, my name is Hal. Please enter your name:\s""";

The story is more complex for leading whitespace. Consider a typical variable declaration that is indented from the left margin. You can indent the text block as well:

         String html = """
            <div class="Warning">
               Beware of those who say "Hello" to the world
            </div>
            """;

The indentation that is common to all lines in the text block is subtracted. The actual string is

"<div class=\"Warning\">\n   Beware of those who say \"Hello\" to the world\n</div>\n"

Note that there are no indentations in the first and third lines.

You can always avoid this indentation stripping by having no whitespace in the last line, before the closing """. But many programmers seem to find that it looks neater when text blocks are indented. Your IDE may cheerfully offer to indent all text blocks, using tabs or spaces.

Java wisely does not prescribe the width of a tab. The whitespace prefix has to match exactly for all lines in the text block.

Entirely blank lines are not considered when stripping common indentation. However, the whitespace before the closing """ is significant. Be sure to indent to the end of the whitespace that you want to have stripped.

3.7. Input and Output

To make our example programs more interesting, we want to accept input and properly format the program output. Of course, modern programs use a GUI for collecting user input. However, programming such an interface requires more tools and techniques than we have at our disposal at this time. Our first order of business is to become more familiar with the Java programming language, so we use the humble console for input and output.

3.7.1. Reading Input

You saw that it is easy to print output to the console window just by calling IO.println. Reading from the console is just as simple.

The readln method reads one line of input and returns it as a string value. You can optionally pass a prompt string as an argument.

String name = IO.readln("What is your name? ");

To read an integer, use the Integer.parseInt method to convert the entered string into an integer.

int age = Integer.parseInt(IO.readln("How old are you? "));

Similarly, the parseDouble method converts a string to a floating-point number.

double rate = Double.parseDouble(IO.readln("Interest rate: "));

The program in Listing 3.2 asks for the user’s name and age and then prints a message like

Hello, Cay. Next year, you'll be 65.

Listing 3.2 InputDemo.java

/**
 * This program demonstrates console input.
 */
void main() {
    // get first input
    String name = IO.readln("What is your name? ");

    // get second input
    int age = Integer.parseInt(IO.readln("How old are you? "));

    // display output on console
    IO.println("Hello, " + name + ". Next year, you'll be " + (age + 1) + ".");
}

3.7.2. Formatting Output

You can print a number x to the console with the statement IO.print(x). That command will print x with the maximum number of nonzero digits for that type. For example,

double x = 10000.0 / 3.0;
IO.print(x);

prints

3333.3333333333335

That is a problem if you want to display, for example, dollars and cents.

The remedy is the formatted method, which follows the venerable conventions from the C library. For example, the call

IO.print("%8.2f".formatted(x));

prints x with a field width of 8 characters and a precision of 2 characters. That is, the printout contains a leading space and the seven characters

3333.33

You can supply multiple arguments to formatted. For example:

IO.print("Hello, %s. Next year, you'll be %d.".formatted(name, age + 1));

Each of the format specifiers that start with a % character is replaced with the corresponding argument. The conversion character that ends a format specifier indicates the type of the value to be formatted: f is a floating-point number, s a string, and d a decimal integer. Table 3.5 shows all conversion characters.

The uppercase variants produce uppercase letters. For example, "%8.2E" formats 3333.33 as 3.33E+03, with an uppercase E.

Table 3.5: Conversions for formatted

Conversion Character

Type

Example

d

Decimal integer

159

x or X

Hexadecimal integer. For more control over hexadecimal formatting, use the HexFormat class.

9f

o

Octal integer

237

f or F

Fixed-point floating-point

15.9

e or E

Exponential floating-point

1.59e+01

g or G

General floating-point (the shorter of e and f)

—

a or A

Hexadecimal floating-point

0x1.fccdp3

s or S

String

Hello

c or C

Character

H

b or B

boolean

true

h or H

Hash code

42628b2

tx or Tx

Legacy date and time formatting. Use the java.time classes instead—see Chapter 6 of Volume II.

—

%

The percent symbol

%

n

The platform-dependent line separator

—

In addition, you can specify flags that control the appearance of the formatted output. Table 3.6 shows all flags. For example, the comma flag adds group separators. That is,

IO.println("%,.2f".formatted(10000.0 / 3.0));

prints

3,333.33

You can use multiple flags, for example "%,(.2f" to use group separators and enclose negative numbers in parentheses.

Table 3.6: Flags for printf

Flag

Purpose

Example

+

Prints sign for positive and negative numbers.

+3333.33

space

Adds a space before positive numbers.

| 3333.33|

0

Adds leading zeroes.

003333.33

-

Left-justifies field.

|3333.33 |

(

Encloses negative numbers in parentheses.

(3333.33)

,

Adds group separators.

3,333.33

# (for f format)

Always includes a decimal point.

3,333.

# (for x or o format)

Adds 0x or 0 prefix.

0xcafe

$

Specifies the index of the argument to be formatted. For example, %1$d %1$x prints the first argument in decimal and hexadecimal.

159 9F

<

Formats the same value as the previous specification. For example, %d %<x prints the same number in decimal and hexadecimal.

159 9F

Figure 3.6 shows a syntax diagram for format specifiers.

FIGURE 3.6

Figure 3.6: Format specifier syntax

3.8. Control Flow

Java, like any programming language, supports both conditional statements and loops to determine control flow. I will start with the conditional statements, then move on to loops, to end with a thorough discussion of the four forms of switch.

3.8.1. Block Scope

Before learning about control structures, you need to know more about blocks.

A block, or compound statement, consists of a number of Java statements, surrounded by a pair of braces. Blocks define the scope of your variables. A block can be nested inside another block. Here is a block that is nested inside the block of the main method:

void main() {
    int n;
    . . .
    {
        int k;
        . . .
    } // k is only defined up to here
}

You may not declare identically named local variables in two nested blocks. For example, the following is an error and will not compile:

void main() {
    int n;
    . . .
    {
        int k;
        int n; // ERROR--can't redeclare n in inner block
        . . .
    }
}

3.8.2. Conditional Statements

The conditional statement in Java has the form

if (condition) statement

The condition must be surrounded by parentheses.

In Java, as in most programming languages, you will often want to execute multiple statements when a single condition is true. In this case, use a block statement that takes the form

{
    statement1
    statement2
    . . .
}

For example:

if (yourSales >= target) {
    performance = "Satisfactory";
    bonus = 100;
}

In this code all the statements surrounded by the braces will be executed when yourSales is greater than or equal to target (see Figure 3.7).

FIGURE 3.7

Figure 3.7: Flowchart for the if statement

The more general conditional in Java looks like this (see Figure 3.8):

if (condition) statement1 else statement2
FIGURE 3.8

Figure 3.8: Flowchart for the if/else statement

For example:

if (yourSales >= target) {
    performance = "Satisfactory";
    bonus = 100 + 0.01 * (yourSales - target);
}
else {
    performance = "Unsatisfactory";
    bonus = 0;
}

The else part is always optional. An else groups with the closest if. Thus, in the statement

if (x <= 0) if (x == 0) sign = 0; else sign = -1;

the else belongs to the second if. Of course, it is a good idea to use braces to clarify this code:

if (x <= 0) { if (x == 0) sign = 0; else sign = -1; }

Repeated if . . . else if . . . alternatives are common (see Figure 3.9). For example:

if (yourSales >= 2 * target) {
    performance = "Excellent";
    bonus = 1000;
}
else if (yourSales >= 1.5 * target) {
    performance = "Fine";
    bonus = 500;
}
else if (yourSales >= target) {
    performance = "Satisfactory";
    bonus = 100;
}
else {
    IO.println("You're fired");
}
FIGURE 3.9

Figure 3.9: Flowchart for the if/else if (multiple branches)

3.8.3. Loops

The while loop executes a statement (which may be a block statement) while a condition is true. The general form is

while (condition) statement

The while loop will never execute if the condition is false at the outset (see Figure 3.10).

FIGURE 3.10

Figure 3.10: Flowchart for the while statement

The program in Listing 3.3 determines how long it will take to save a specific amount of money for your well-earned retirement, assuming you deposit the same amount of money per year and the money earns a specified interest rate.

In the example, we are incrementing a counter and updating the amount currently accumulated in the body of the loop until the total exceeds the targeted amount.

while (balance < goal) {
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    years++;
}
IO.println(years + " years.");

(Don’t rely on this program to plan for your retirement. It lacks a few niceties such as inflation and your life expectancy.)

A while loop tests at the top. Therefore, the code in the block might never be executed. If you want to make sure a block is executed at least once, you need to move the test to the bottom, using the do/while loop. Its syntax looks like this:

do statement while (condition);

This loop executes the statement (which is typically a block) and only then tests the condition. If it’s true, it repeats the statement and retests the condition, and so on. The code in Listing 3.4 computes the new balance in your retirement account and then asks if you are ready to retire:

do {
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    years++;
    // print current balance
    . . .
    // ask if ready to retire and get input
    . . .
} while (input.equals("N"));

As long as the user answers "N", the loop is repeated (see Figure 3.11). This program is a good example of a loop that needs to be entered at least once, because the user needs to see the balance before deciding whether it is sufficient for retirement.

FIGURE 3.11

Figure 3.11: Flowchart for the do/while statement

Listing 3.3 Retirement.java

/**
 * This program demonstrates a <code>while</code> loop.
 */
void main() {
    // read inputs
    double goal = Double.parseDouble(IO.readln("How much money do you need to retire? "));
    double payment
        = Double.parseDouble(IO.readln("How much money will you contribute every year? "));
    double interestRate = Double.parseDouble(IO.readln("Interest rate in %: "));

    double balance = 0;
    int years = 0;

    // update account balance while goal isn't reached
    while (balance < goal) {
        // add this year's payment and interest

        balance += payment;
        double interest = balance * interestRate / 100;
        balance += interest;
        years++;
    }

    IO.println("You can retire in " + years + " years.");
}

Listing 3.4 Retirement2.java

/**
 * This program demonstrates a <code>do/while</code> loop.
 */
void main() {
    double payment = Double.parseDouble(
        IO.readln("How much money will you contribute every year? "));
    double interestRate = Double.parseDouble(IO.readln("Interest rate in %: "));

    double balance = 0;
    int year = 0;

    String input;

    // update account balance while user isn't ready to retire
    do {
        // add this year's payment and interest
        balance += payment;
        double interest = balance * interestRate / 100;
        balance += interest;

        year++;

        // print current balance
        IO.println("After year %d, your balance is %,.2f".formatted(year,
                balance));

        // ask if ready to retire and get input
        input = IO.readln("Ready to retire? (Y/N) ");
    }
    while (input.equals("N"));
}

3.8.4. Determinate Loops

The for loop is a general construct to support iteration controlled by a counter or similar variable that is updated after every iteration. As Figure 3.12 shows, the following loop prints the numbers from 1 to 10 on the screen:

for (int i = 1; i <= 10; i++)
    IO.println(i);

The first slot of the for statement usually holds the counter initialization. The second slot gives the condition that will be tested before each new pass through the loop, and the third slot specifies how to update the counter.

FIGURE 3.12

Figure 3.12: Flowchart for the for statement

Although Java, like C++, allows almost any expression in the various slots of a for loop, it is an unwritten rule of good taste that the three slots should only initialize, test, and update the same counter variable. One can write very obscure loops by disregarding this rule.

Even within the bounds of good taste, much is possible. For example, you can have loops that count down:

for (int i = 10; i > 0; i--)
    IO.println("Counting down . . . " + i);
IO.println("Blastoff!");

When you declare a variable in the first slot of the for statement, the scope of that variable extends until the end of the body of the for loop.

for (int i = 1; i <= 10; i++) {
    . . .
}
// i no longer defined here

In particular, if you define a variable inside a for statement, you cannot use its value outside the loop. Therefore, if you wish to use the final value of a loop counter outside the for loop, be sure to declare it outside the loop header.

int i;
for (i = 1; i <= 10; i++) {
    . . .
}
// i is still defined here

On the other hand, you can define variables with the same name in separate for loops:

for (int i = 1; i <= 10; i++) {
    . . .
}
. . .
for (int i = 11; i <= 20; i++) { // OK to define another variable named i
    . . .
}

A for loop is merely a convenient shortcut for a while loop. For example,

for (i = 10; i > 0; i--)
    IO.println("Counting down . . . " + i);

can be rewritten as follows:

i = 10;
while (i > 0) {
    IO.println("Counting down . . . " + i);
    i--;
}

The first slot of a for loop can declare multiple variables, provided they are of the same type. And the third slot can contain multiple comma-separated expressions:

for (int i = 1, j = 10; i <= 10; i++, j--) { . . . }

While technically legal, this stretches the intuitive meaning of the for loop, and you should consider a while loop instead.

Listing 3.5 shows a typical example of a for loop.

The program computes the odds of winning a lottery. For example, if you must pick six numbers from the numbers 1 to 50 to win, then there are (50 × 49 × 48 × 47 × 46 × 45)/(1 × 2 × 3 × 4 × 5 × 6) possible outcomes, so your chance is 1 in 15,890,700. Good luck!

In general, if you pick k numbers out of n, there are

math

possible outcomes. The following for loop computes this value:

int lotteryOdds = 1;
for (int i = 1; i <= k; i++)
    lotteryOdds = lotteryOdds * (n - i + 1) / i;

Listing 3.5 LotteryOdds.java

/**
 * This program demonstrates a <code>for</code> loop.
 */
void main() {
    int k = Integer.parseInt(IO.readln("How many numbers do you need to draw? "));
    int n = Integer.parseInt(IO.readln("What is the highest number you can draw? "));

    // Binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)

    int lotteryOdds = 1;
    for (int i = 1; i <= k; i++)
        lotteryOdds = lotteryOdds * (n - i + 1) / i;

    IO.println("Your odds are 1 in " + lotteryOdds + ". Good luck!");
}

3.8.5. Multiple Selections with switch

The if/else construct can be cumbersome when you have to deal with multiple alternatives for the same expression. The switch statement makes this easier, particularly with the form that has been introduced in Java 14.

For example, if you set up a menu system with four alternatives like that in Figure 3.13, you could use code that looks like this:


int choice = Integer.parseInt(IO.readln("Select an option (1, 2, 3, 4) "));
switch (choice) {
    case 1 ->
        . . .
    case 2 ->
        . . .
    case 3 ->
        . . .
    case 4 ->
        . . .
    default ->
        IO.println("Bad input");
}
FIGURE 3.13

Figure 3.13: Flowchart for the switch statement

Note the similarity to the switch expressions that you saw in Section 3.5.9. Unlike a switch expression, a switch statement has no value. Each case carries out an action.

The “classic” form of the switch statement, which dates all the way back to the C language, has been supported since Java 1.0. It has the form:

int choice = . . .;
switch (choice) {
    case 1:
        . . .
        break;
    case 2:
        . . .
        break;
    case 3:
        . . .
        break;
    case 4:
        . . .
        break;
    default:
        IO.println("Bad input");
}

Execution starts at the case label that matches the value on which the selection is performed and continues until the next break or the end of the switch. If none of the case labels match, then the default clause is executed, if it is present.

For symmetry, Java 14 also introduced a switch expression with fallthrough, for a total of four forms of switch. Table 3.7 shows them all.

Table 3.7: The four forms of switch

Expression Statement
No Fallthrough
int numLetters = switch (seasonName) {
    case "Spring" -> {
        IO.println("spring time!");
        yield 6;
    }
    case "Summer", "Winter" -> 6;
    case "Fall" -> 4;
    default -> -1;
};
switch (seasonName) {
    case "Spring" -> {
        IO.println("spring time!");
        numLetters = 6;
    }
    case "Summer", "Winter" ->
        numLetters = 6;
    case "Fall" ->
        numLetters = 4;
    default ->
        numLetters = -1;
}
Fallthrough
int numLetters = switch (seasonName) {
    case "Spring":
        IO.println("spring time!");
    case "Summer", "Winter":
        yield 6;
    case "Fall":
        yield 4;
    default:
        yield -1;
};
switch (seasonName) {
    case "Spring":
        IO.println("spring time!");
    case "Summer", "Winter":
        numLetters = 6;
        break;
    case "Fall":
        numLetters = 4;
        break;
    default:
        numLetters = -1;
}

In the fallthrough variants, each case ends with a colon. If the cases end with arrows ->, then there is no fallthrough. You can’t mix colons and arrows in a single switch statement.

Each branch of a switch expression must yield a value. Most commonly, each value follows an -> arrow:

case "Summer", "Winter" -> 6;

If you cannot compute the result in a single expression, use braces and a yield statement. Like break, it terminates execution. Unlike break, it also yields a value—the value of the expression:

case "Spring" -> {
    IO.println("spring time!");
    yield 6;
}

With so many variations of switch, which one should you choose?

  1. Avoid the fallthrough forms. It is very uncommon to need fallthrough.

  2. Prefer switch expressions over statements.

For example, consider:

switch (seasonName) {
    case "Spring", "Summer", "Winter":
        numLetters = 6;
        break;
    case "Fall":
        numLetters = 4;
        break;
    default:
        numLetters = -1;
}

Since every case ends with a break, there is no need to use the fallthrough form. The following is an improvement:

switch (seasonName) {
    case "Spring", "Summer", "Winter" ->
        numLetters = 6;
    case "Fall" ->
        numLetters = 4;
    default ->
        numLetters = -1;
}

Now note that each branch assigns a value to the same variable. It is much more elegant to use a switch expression here:

numLetters = switch (seasonName) {
    case "Spring", "Summer", "Winter" -> 6
    case "Fall" -> 4
    default -> -1
};

3.8.6. Statements That Break Control Flow

Although the designers of Java kept goto as a keyword, they decided not to include it in the language. In general, goto statements are considered poor style. Some programmers feel the anti-goto forces have gone too far (see, for example, the famous article of Donald Knuth called “Structured Programming with goto statements”). They argue that unrestricted use of goto is error-prone but that an occasional jump out of a loop is beneficial. The Java designers agreed and even added a new statement, the labeled break, to support this programming style.

Let us first look at the unlabeled break statement. The same break statement that you use to exit a switch statement can also be used to break out of a loop. For example:

while (years <= 100) {
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;
    if (balance >= goal) break;
    years++;
}

Now the loop is exited if either years > 100 occurs at the top of the loop or balance >= goal occurs in the middle of the loop. Of course, you could have computed the same value for years without a break, like this:

while (years <= 100 && balance < goal) {
    balance += payment;
    double interest = balance * interestRate / 100;
    balance += interest;

    if (balance < goal) years++;
}

But note that the test balance < goal is repeated twice in this version. To avoid this repeated test, some programmers prefer the break statement.

The labeled break statement lets you break out of multiple nested loops. Occasionally something weird happens inside a deeply nested loop. In that case, you may want to break completely out of all the nested loops. It is inconvenient to program that simply by adding extra conditions to the various loop tests.

Here’s an example that shows the labeled break statement at work. Notice that the label must precede the outermost loop out of which you want to break. It also must be followed by a colon.

int n;
read_data:
while (. . .) { // this loop statement is tagged with the label
    . . .
    for (. . .) { // this inner loop is not labeled
        IO.print();
        n = Integer.parseInt(IO.readln("Enter a number >= 0: "));
        if (n < 0) { // should never happen—can't go on
            break read_data; // break out of read_data loop
        }
        . . .
    }
}
// this statement is executed immediately after the labeled break

if (n < 0) { // check for bad situation
    // deal with bad situation
}
else {
    // carry out normal processing
}

If there is a bad input, the labeled break moves past the end of the labeled block. As with any use of the break statement, you then need to test whether the loop exited normally or as a result of a break.

Finally, there is a continue statement that, like the break statement, breaks the regular flow of control. The continue statement transfers control to the header of the innermost enclosing loop. Here is an example:

while (sum < goal) {
    n = Integer.parseInt(IO.readln("Enter a number: "));
    if (n < 0) continue;
    sum += n; // not executed if n < 0
}

If n < 0, then the continue statement jumps immediately to the loop header, skipping the remainder of the current iteration.

If the continue statement is used in a for loop, it jumps to the “update” part of the for loop. For example:

for (count = 1; count <= 100; count++) {
    n = Integer.parseInt(IO.readln("Enter a number, -1 to quit: "));
    if (n < 0) continue;
    sum += n; // not executed if n < 0
}

If n < 0, then the continue statement jumps to the count++ statement.

There is also a labeled form of the continue statement that jumps to the header of the loop with the matching label.

3.9. Big Numbers

If the precision of the basic integer and floating-point types is not sufficient, you can turn to a couple of handy classes in the java.math package: BigInteger and BigDecimal. These are classes for manipulating numbers with an arbitrarily long sequence of digits. The BigInteger class implements arbitrary-precision integer arithmetic, and BigDecimal does the same for floating-point numbers.

Use the static valueOf method to turn an ordinary number into a big number:

BigInteger a = BigInteger.valueOf(100);

For longer numbers, use a constructor with a string argument:

BigInteger reallyBig
    = new BigInteger("222232244629420445529739893461909967206666939096499764990979600");

There are also constants BigInteger.ZERO, BigInteger.ONE, BigInteger.TWO, and BigInteger.TEN.

Unfortunately, you cannot use the familiar mathematical operators such as + and * to combine big numbers. Instead, you must use methods such as add and multiply in the big number classes.

BigInteger c = a.add(b); // c = a + b
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c * (b + 2)

Listing 3.6 shows a modification of the lottery odds program of Listing 3.5, updated to work with big numbers. For example, if you are invited to participate in a lottery in which you need to pick 60 numbers out of a possible 490 numbers, you can use this program to tell you your odds of winning. They are 1 in 716395843461995557415116222540092933411717612789263493493351013459481104668848. Good luck!

The program in Listing 3.5 computed the statement

lotteryOdds = lotteryOdds * (n - i + 1) / i;

When big integers are used for lotteryOdds and n, the equivalent statement becomes

lotteryOdds = lotteryOdds
    .multiply(n.subtract(BigInteger.valueOf(i - 1)))
    .divide(BigInteger.valueOf(i));

3.10. Arrays

Arrays hold sequences of values of the same type. In the following sections, you will see how to work with arrays in Java.

3.10.1. Declaring Arrays

Declare an array variable by specifying the array type—which is the element type followed by []—and the array variable name. For example, here is the declaration of an array a of integers:

int[] a;

However, this statement only declares the variable a. It does not yet initialize a with an actual array. Use the new operator to create the array.

int[] a = new int[100]; // or var a = new int[100];

This statement declares and initializes an array of 100 integers.

The array length need not be a constant: new int[n] creates an array of length n.

Once you create an array, you cannot change its length (although you can, of course, change an individual array element). If you frequently need to expand the length of arrays while your program is running, you should use array lists, which are covered in Chapter 5.

The type of an array variable does not include the length. For example, the variable a in the preceding example has type int[] and can be set to an int array of any length.

Java has a shortcut for creating an array object and supplying initial values:

int[] smallPrimes = { 2, 3, 5, 7, 11, 13 };

Notice that you do not use new with this syntax, and you don’t specify the length.

A comma after the last value is allowed, which can be convenient for an array to which you keep adding values over time:

String[] authors = {
    "James Gosling",
    "Bill Joy",
    "Guy Steele",
    // add more names here and put a comma after each name
};

You can declare an anonymous array:

new int[] { 17, 19, 23, 29, 31, 37 }

This expression allocates a new array and fills it with the values inside the braces. It counts the number of initial values and sets the array length accordingly. You can use this syntax to reinitialize an array without creating a new variable. For example,

smallPrimes = new int[] { 17, 19, 23, 29, 31, 37 };

is shorthand for

int[] anonymous = { 17, 19, 23, 29, 31, 37 };
smallPrimes = anonymous;

3.10.2. Accessing Array Elements

You access each individual element of an array through an integer index, using the bracket operator. For example, if a is an array of integers, then a[i] is the element with index i in the array.

The array elements are numbered starting from 0. The last valid index is one less than the length. In the example below, the index values range from 0 to 99. Once the array is created, you can fill the elements in an array, for example, by using a loop:

int[] a = new int[100];
for (int i = 0; i < 100; i++)
    a[i] = i; // fills the array with numbers 0 to 99

When you create an array of numbers, all elements are initialized with zero. Arrays of boolean are initialized with false. Arrays of objects are initialized with the special value null, which indicates that they do not (yet) hold any objects. This can be surprising for beginners. For example,

String[] names = new String[10];

creates an array of ten strings, all of which are null. If you want the array to hold empty strings, you must supply them:

for (int i = 0; i < 10; i++) names[i] = "";

To find the number of elements of an array, use array.length. For example:

for (int i = 0; i < a.length; i++)
    IO.println(a[i]);

3.10.3. The “for each” Loop

Java has a powerful looping construct that allows you to loop through each element in an array (or any other collection of elements) without having to fuss with index values.

The enhanced for loop

for (variable : collection) statement

sets the given variable to each element of the collection and then executes the statement (which, of course, may be a block). The collection expression must be an array or an object of a class that implements the Iterable interface, such as ArrayList. Array lists are covered in Chapter 5 and the Iterable interface in Chapter 9.

For example,

for (int element : a)
    IO.println(element);

prints each element of the array a on a separate line.

You should read this loop as “for each element in a.” The designers of the Java language considered using keywords, such as foreach and in. But this loop was a late addition to the Java language, and in the end nobody wanted to break the old code that already contained methods or variables with these names (such as System.in).

Of course, you could achieve the same effect with a traditional for loop:

for (int i = 0; i < a.length; i++)
    IO.println(a[i]);

However, the “for each” loop is more concise and less error-prone, as you don’t have to worry about those pesky start and end index values.

The “for each” loop is a pleasant improvement over the traditional loop if you need to process all elements in a collection. However, there are still plenty of opportunities to use the traditional for loop. For example, you might not want to traverse the entire collection, or you may need the index value inside the loop.

3.10.4. Array Copying

You can copy one array variable into another, but then both variables refer to the same array:

int[] luckyNumbers = smallPrimes;
luckyNumbers[5] = 12; // now smallPrimes[5] is also 12

Figure 3.14 shows the result.

FIGURE 3.14

Figure 3.14: Copying an array variable

If you actually want to copy all values of one array into a new array, use the copyOf method in the Arrays class:

int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);

The second argument is the length of the new array. A common use of this method is to increase the length of an array:

luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);

The additional elements are filled with 0 if the array contains numbers, false if the array contains boolean values. Conversely, if the length is less than the length of the original array, only the initial values are copied.

3.10.5. Command-Line Arguments

If you want to process arguments that a user of your program specified on the command line, your main method needs a parameter that is an array of strings.

For example, consider this program in a file Message.java:

void main(String[] args) {
    IO.print(switch (args[0])) {
        case "-a" -> "🏴‍☠️";
        case "-b" -> "🍺";
        case "-h" -> "Hello,";
        default -> args[0];
    }
    IO.print(" " + args[1]);
    IO.println("!");
}

If the program is called as

java Message.java -h World

or

javac Message.java
java Message -h World

then args[0] is the string "-h", and args[1] is "World".

3.10.6. Array Sorting

To sort an array of numbers, you can use one of the sort methods in the Arrays class:

int[] a = new int[10000];
. . .
Arrays.sort(a)

This method uses a tuned version of the QuickSort algorithm that is claimed to be very efficient on most data sets. The Arrays class provides several other convenience methods for arrays that are included in the API notes at the end of this section.

The program in Listing 3.7 puts arrays to work. This program draws a random combination of numbers for a lottery game. For example, if you play a “choose 6 numbers from 49” lottery, the program might print this:

Bet the following combination. It'll make you rich!
4
7
8
19
30
44

To select such a random set of numbers, first fill an array numbers with the values 1, 2, . . ., n:

int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
    numbers[i] = i + 1;

A second array holds the numbers to be drawn:

int[] result = new int[k];

Now draw k numbers. The Math.random method returns a random floating-point number that is between 0 (inclusive) and 1 (exclusive). Multiplying the result with n yields a random number between 0 and n – 1.

int r = (int) (Math.random() * n);

Set the ith result to be the number at that index. Initially, that is just r + 1, but as you’ll see presently, the contents of the numbers array are changed after each draw.

result[i] = numbers[r];

Now, you must be sure never to draw that number again—all lottery numbers must be distinct. Therefore, overwrite numbers[r] with the last number in the array and reduce n by 1.

numbers[r] = numbers[n - 1];
n--;

The point is that in each draw we pick an index, not the actual value. The index points into an array that contains the values that have not yet been drawn.

After drawing k lottery numbers, sort the result array for a more pleasing output:

Arrays.sort(result);
for (int r : result)
    IO.println(r);

Listing 3.7 LotteryDrawing.java

/**
 * This program demonstrates array manipulation.
 */
void main() {
    int k = Integer.parseInt(IO.readln("How many numbers do you need to draw? "));
    int n = Integer.parseInt(IO.readln("What is the highest number you can draw? "));

    // fill an array with numbers 1 2 3 . . . n
    int[] numbers = new int[n];
    for (int i = 0; i < numbers.length; i++)
        numbers[i] = i + 1;

    // draw k numbers and put them into a second array
    int[] result = new int[k];
    for (int i = 0; i < result.length; i++) {
        // make a random index between 0 and n - 1
        int r = (int) (Math.random() * n);

        // pick the element at the random location
        result[i] = numbers[r];

        // move the last element into the random location
        numbers[r] = numbers[n - 1];
        n--;
    }

    // print the sorted array
    Arrays.sort(result);
    IO.println("Bet the following combination. It'll make you rich!");
    for (int r : result)
        IO.println(r);
}

3.10.7. Multidimensional Arrays

Multidimensional arrays use more than one index to access array elements. They are used for tables and other more complex arrangements. You can safely skip this section until you have a need for this storage mechanism.

Suppose you want to make a table of numbers that shows how much an investment of $10,000 will grow under different interest rate scenarios in which interest is paid annually and reinvested.

        5%        6%        7%        8%        9%       10%
  10000.00  10000.00  10000.00  10000.00  10000.00  10000.00
  10500.00  10600.00  10700.00  10800.00  10900.00  11000.00
  11025.00  11236.00  11449.00  11664.00  11881.00  12100.00
  11576.25  11910.16  12250.43  12597.12  12950.29  13310.00
  12155.06  12624.77  13107.96  13604.89  14115.82  14641.00
  12762.82  13382.26  14025.52  14693.28  15386.24  16105.10
  13400.96  14185.19  15007.30  15868.74  16771.00  17715.61
  14071.00  15036.30  16057.81  17138.24  18280.39  19487.17
  14774.55  15938.48  17181.86  18509.30  19925.63  21435.89
  15513.28  16894.79  18384.59  19990.05  21718.93  23579.48

You can store this information in a two-dimensional array named balances.

Declaring a two-dimensional array in Java is simple enough. For example:

double[][] balances;

You cannot use the array until you initialize it. In this case, you can do the initialization as follows:

balances = new double[NYEARS][NRATES];

In other cases, if you know the array elements, you can use a shorthand notation for initializing a multidimensional array without a call to new. For example:

int[][] magicSquare = {
    { 16, 3, 2, 13 },
    { 5, 10, 11, 8 },
    { 9, 6, 7, 12 },
    { 4, 15, 14, 1 }
};

Once the array is initialized, you can access individual elements by supplying two pairs of brackets—for example, balances[i][j].

The example program stores a one-dimensional array interestRates of interest rates and a two-dimensional array balances of account balances, one for each year and interest rate. Initialize the first row of the array with the initial balance:

for (int j = 0; j < balances[0].length; j++)
    balances[0][j] = 10000;

Then compute the other rows, as follows:

for (int i = 1; i < balances.length; i++) {
    for (int j = 0; j < balances[i].length; j++) {
        double oldBalance = balances[i - 1][j];
        double interest = . . .;
        balances[i][j] = oldBalance + interest;
    }
}

Listing 3.8 shows the full program. In this program, you can see how to use multiple methods. The main method calls a printTable method that prints the table of balances.

Listing 3.8 CompoundInterest.java

/**
 * This program shows how to store tabular data in a 2D array.
 */
void main() {
    final double STARTRATE = 5;
    final int NRATES = 6;
    final int NYEARS = 10;

    // set interest rates to 5 . . . 10%
    double[] interestRates = new double[NRATES];
    for (int j = 0; j < interestRates.length; j++)
        interestRates[j] = (STARTRATE + j) / 100.0;

    double[][] balances = new double[NYEARS][NRATES];

    // set initial balances to 10000
    for (int j = 0; j < balances[0].length; j++)
        balances[0][j] = 10000;

    // compute interest for future years
    for (int i = 1; i < balances.length; i++) {
        for (int j = 0; j < balances[i].length; j++) {
            // get last year's balances from previous row
            double oldBalance = balances[i - 1][j];

            // compute interest
            double interest = oldBalance * interestRates[j];

            // compute this year's balances
            balances[i][j] = oldBalance + interest;
        }
    }

    printTable(interestRates, balances);
}

void printTable(double[] headers, double[][] values) {
    for (double header : headers) {
        IO.print("%10.2f".formatted(header));
    }
    IO.println();
    IO.println("-".repeat(10 * headers.length));
    // print balance table
    for (double[] row : values) {
        // print table row
        for (double value : row)
            IO.print("%10.2f".formatted(value));

        IO.println();
    }
}

3.10.8. Ragged Arrays

So far, what you have seen is not too different from other programming languages. But there is actually something subtle going on behind the scenes that you can sometimes turn to your advantage: Java has no multidimensional arrays at all, only one-dimensional arrays. Multidimensional arrays are faked as “arrays of arrays.”

For example, the balances array in the preceding example is actually an array that contains ten elements, each of which is an array of six floating-point numbers (Figure 3.15).

FIGURE 3.16

Figure 3.15: A two-dimensional array

The expression balances[i] refers to the ith subarray—that is, the ith row of the table. It is itself an array, and balances[i][j] refers to the jth element of that array.

Since rows of arrays are individually accessible, you can actually swap them!

double[] temp = balances[i];
balances[i] = balances[i + 1];
balances[i + 1] = temp;

Note that the number of rows and columns is not a part of the type of an array variable. The variable balances has type double[][]: an array of double arrays.

Therefore, you can make “ragged” arrays—that is, arrays in which different rows have different lengths. Here is the standard example. Let us make an array in which the element at row i and column j equals the number of possible outcomes of a “choose j numbers from i numbers” lottery.

1
1  1
1  2  1
1  3  3  1
1  4  6  4  1
1  5 10 10  5 1
1  6 15 20 15 6 1

As j can never be larger than i, the matrix is triangular. The ith row has i + 1 elements. (It is OK to choose 0 elements; there is one way to make such a choice.) To build this ragged array, first allocate the array holding the rows:

final int NMAX = 10;
int[][] odds = new int[NMAX + 1][];

Next, allocate the rows:

for (int n = 0; n <= NMAX; n++)
    odds[n] = new int[n + 1];

Now that the array is allocated, you can access the elements in the normal way, provided you do not overstep the bounds:

for (int n = 0; n < odds.length; n++) {
    for (int k = 0; k < odds[n].length; k++) {
        // compute lotteryOdds
        . . .
        odds[n][k] = lotteryOdds;
    }
}

Listing 3.9 gives the complete program.

Listing 3.9 LotteryArray.java

/**
 * This program demonstrates a triangular array.
 */
void main() {
    final int NMAX = 10;

    // allocate triangular array
    int[][] odds = new int[NMAX + 1][];
    for (int n = 0; n <= NMAX; n++)
        odds[n] = new int[n + 1];

    // fill triangular array
    for (int n = 0; n < odds.length; n++)
        for (int k = 0; k < odds[n].length; k++) {
            /*
             * compute binomial coefficient
             * n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
             */
            int lotteryOdds = 1;
            for (int i = 1; i <= k; i++)
                lotteryOdds = lotteryOdds * (n - i + 1) / i;

            odds[n][k] = lotteryOdds;
        }

    // print triangular array
    for (int[] row : odds) {
        for (int odd : row)
            IO.print("%4d".formatted(odd));
        IO.println();
    }
}

You have now seen the fundamental programming structures of the Java language. The next chapter covers object-oriented programming in Java.

800 East 96th Street, Indianapolis, Indiana 46240