Module 2, Part 2: Console I/O, Conditionals, Strings#

This is the second part of Module 2, and a follow-up to Module 2, Part 1: a First Taste of Java. This second part introduces some more elements of Java, allowing us to write more interesting and interactive programs. We address three main topics:

To introduce these topics, we consider a practical problem: writing an interactive program that computes the area of various geometric shapes.

Goal: an Interactive Program that Computes Areas#

Our goal is to write a program that runs on the console (a.k.a. terminal) and behaves as follows:

  1. Asks the user to write their name;

  2. Greets the user by using their name;

  3. Asks the user to write the name of a geometric shape;

  4. Checks what shape has been specified by the user:

    1. if the shape is "rectangle", the program asks the user to specify its width and height (both having type double), and tells whether the rectangle is a square (when width and height are equal). Then, the program computes the area, and prints the result;

    2. if the shape is "circle", the program asks the user to specify its radius (having type double), computes the area, and prints the result;

    3. otherwise (i.e. if the shape is something else), the program displays an error message.

  5. Finally, the program says goodbye and ends.

This flowchart visualises the intended program behaviour:

Area calculator flowchart

Based on our first taste of Java, we know how to address some parts of this program: displaying a greeting, computing an area. However, we have not yet seen how to:

  • receive user inputs from the console (points 1 and 3), and

  • execute either point 4.1, 4.2, or 4.3, depending on a condition (i.e. which shape has been specified by the user). In other words, we do not yet know how to write Java code to perform the rhombus-shaped steps in the flowchart.

Example 12 (An area calculator program)

The following Java program behaves according to the description above, and uses some Java constructs that we have not yet seen. In the rest of this module we discuss all these new constructs.

Note

You can run the program below by copy&pasting it in a text editor (e.g. VS Code), saving it in a file called AreaCalculator.java, and then running on a terminal (in the same folder where you saved the file):

java AreaCalculator.java
 1class AreaCalculator {
 2    public static void main(String[] args) {
 3        var scanner = new java.util.Scanner(System.in);
 4        scanner.useLocale(java.util.Locale.ENGLISH);
 5
 6        System.out.println("Hello, human.  What is your name?");
 7
 8        var name = scanner.nextLine();
 9        System.out.println("Nice to meet you, " + name + "!");
10        System.out.println("Please write the name of a geometric shape:");
11
12        var shape = scanner.nextLine();
13        if (shape.equals("rectangle")) {
14            System.out.println("What is the width?");
15            var width = scanner.nextDouble();
16            System.out.println("What is the height?");
17            var height = scanner.nextDouble();
18
19            if (width == height) {
20                System.out.println("The specified rectangle is a square");
21            } else {
22                System.out.println("The specified rectangle is not a square");
23            }
24
25            var area = width * height;
26            System.out.println("The area is: " + area);
27        } else if (shape.equals("circle")) {
28            System.out.println("What is the radius?");
29            var radius = scanner.nextDouble();
30
31            var area = radius * radius * Math.PI;
32            System.out.println("The area is: " + area);
33        } else {
34            System.out.println("I don't know the shape '" + shape + "' :-(");
35        }
36
37        scanner.close();
38        System.out.println("Goodbye!");
39    }
40}

Console I/O#

In our first Java program we displayed some console outputs, by using System.out.println(...). We now discuss the topic of console input and output in more detail — and this will let us begin an exploration of the Java Application Programmer Interface (API), which is a library of ready-to-use software components that we can utilise to develop our programs.

The Standard Input and Output Streams#

Java programs can read inputs and display outputs by using two predefined objects provided by the Java API:

  • System.out can be used to display outputs on the console. To produce an output, we need to call (or invoke) one of the methods provided by System.out: for example, the method println takes one argument, displays its value on the console, and moves to the beginning of the next line on the console. We have used this method in our first Java program: we wrote System.out.println(message) to display the value of the argument message.

  • System.in can be used to receive inputs from the console. This object has a limited set of methods: it only supports reading sequences of bytes, which makes the handling of inputs quite cumbersome. For this reason, we create and use instead a more convenient Scanner object.

The objects System.in and System.out are called standard input stream and standard output stream, respectively: you can imagine that they carry “streams” of text characters — one flowing from the console keyboard to the Java application (input), and another flowing from the Java application to the console display (output), as shown in Fig. 13 below. This design descends from the teleprinter and video terminals from the 1960s and 1970s.

Standard input and output

Fig. 13 A diagram of the standard input and output streams in Java. The standard input (accessible via the object System.in) carries data from the console keyboard to the running program; the standard output (accessible via the object System.out) carries data from the program to the console display.#

Creating and Using a Scanner for Reading Inputs#

On the Java shell, we can create a scanner object that reads inputs from the keyboard, as follows:

jshell> var s = new java.util.Scanner(System.in)
scanner ==> java.util.Scanner[...]
|  created variable s : Scanner

Let us examine this in detail:

  • On the first line, we use the keyword new to construct (i.e., create) a new object of a desired class;

  • In this example, we write new java.util.Scanner(System.in), where:

    • java.util.Scanner is the class name, which is provided by the Java API; (more specifically, java.util is the name of a Java package that, among other things, contains a class called Scanner);

    • to construct the desired object of the class java.util.Scanner, we provide an argument — and in this case, we provide System.in.

  • We use the result of new ... to declare a variable called s. Correspondingly, on the 2nd line above, the Java shell tells us that variable s now gives us access to the object constructed by new ... (such an object is represented as java.util.Scanner[...]).

  • The last line printed by the Java shell tells us that the variable s has type Scanner (which is abbreviated from java.util.Scanner).

We can now use the variable s to call the methods provided by the object we have just created; moreover, since we constructed this Scanner object by “attaching” it to System.in (which we provided as argument when using new ...), this Scanner object will allow us to receive inputs from the console.

Remark 3 (Configuring the localisation of the Scanner object)

Before continuing, it is recommended to configure the localisation of the Scanner object, i.e. the conventions used by the scanner e.g. to recognise numbers with fractional parts. To do that, you can call the method useLocale, and (for the lectures and exercises of this course) specify the English localisation:

jshell> s.useLocale(java.util.Locale.ENGLISH)

This will ensure that the scanner will apply the English language standards and recognise e.g. the input 3.14 as a number with fractional digits. If you don’t configure the localisation, then the Scanner will apply your system defaults: for instance, if your system language is set to Danish, the Scanner object will not recognise 3.14 as a number, but it will recognise 3,14 instead.

For instance, we can use the Scanner object to read the next line of input text and use it to declare a variable:

jshell> var inputLine = s.nextLine()

The method s.nextLine() is now waiting for us to write something and press (return key). For example, if we write This is just a test and press , the Java shell will then display:

inputLine ==> "This is just a test"
|  created variable inputLine : String

Therefore, the method s.nextLine() has read a whole line of console input and has produced a corresponding String, which has been used to declare the variable inputLine. From now on, we can use the variable inputLine to access the text just received from the console. In fact, we can try:

jshell> System.out.println("The input was: '" + inputLine + "'")
The input was: 'This is just a test'

The Scanner object also provides other convenience methods for reading numbers. For example, you can try:

jshell> var width = s.nextDouble()

Again, the method waits for us to provide a number (possibly with decimal digits). If we write e.g. 4.25 and press , we will see:

width ==> 4.25
|  created variable width : double

Therefore, the method s.nextDouble() has read the console input and returned the corresponding value 4.25 (of type double), which has been used to declare the variable width.

Objects of class Scanner provide other similar convenience methods to read the console input and return various types of values. For instance, we could use:

  • s.nextInt(), which produces a value of type int;

  • s.nextBoolean(), which produces a value of type boolean.

Remark 4 (Closing a scanner)

When we are done using a scanner s, we should close it, by calling the method s.close() (the parentheses () mean that the method does not take any argument). In Example 12 you can observe that a Scanner object is created at the beginning of the program, and closed at the end.

After being closed, a scanner instance object will report an error (as an exception) if it is used to read further inputs.

Remark 5 (Errors when using Scanner objects)

If a Scanner object receives an unexpected input, it will report an error (as an exception) and stop working. For example, if you use s.nextInt() on the Java shell, then type Hello! and press , you will see:

jshell> s.nextInt()
Hello!
|  Exception java.util.InputMismatchException
|        at Scanner.throwFor (Scanner.java:939)
|        at Scanner.next (Scanner.java:1594)
|        at Scanner.nextInt (Scanner.java:2258)
|        at Scanner.nextInt (Scanner.java:2212)
|        at (#8:1)

After this, error, the Scanner object becomes unusable: any attempt to read inputs using the variable s will cause an error. (We will see how to handle errors later in the course)

Note

The class java.util.Scanner is part of the Java API; if you are curious, you can have a look at its official documentation.

Remark 6 (Terminology: “value” or “object”? “Type” or “class”?)

In the explanation above, we have said that java.util.Scanner is the class of the object constructed with new .... Instead, in previous examples, we have talked about the type of values (e.g. value 42 has type int).

So, what is the difference between “class” and “type,” and between “object” and “value”?

  • The term “type” can refer to both primitive data types (int, boolean, double, …) and classes (such as java.util.Scanner). In other words, a class is a specific kind of type with advanced features that we will explore later in the course. Therefore, it is correct to say either:

    • the variable s has type Scanner; or

    • the variable s has class Scanner.

  • In Java, the term “value” is often generically used to refer to two things:

    • a primitive value, which is instance of a primitive data types (e.g. 42 is an instance of int, false is an instance of boolean, 3.14 is an instance of double, …); or

    • an object, which is an instance of a class (such as java.util.Scanner).

We will see later in the course that primitive values and objects have significantly different behaviours (and we will see the first signs of their difference in this very Module, when we will discuss how to compare strings).

Remark 7 (Terminology: statements vs. expressions)

When talking about Java code constructs, we often use the terms “statement” and “expression.” You can follow the intuition presented below:

  • Expressions compute and produce a result value (e.g. a number, or an object). The act of computing the result of an expression is called evaluation, and we often say that an expression “evaluates to” a result. For instance:

    • 2 is an expression, because it produces the value 2 itself;

    • x (i.e. the name of a variable) is an expression, because it produces the value previously given to x;

    • 2 + 3 is an expression that uses the operator + to compute and produce the value 5; (or in other words, 2 + 3 evaluates to the result 5)

    • s.nextLine() is an expression, because it produces a result (a String with the input received from the console);

    • new java.util.Scanner(System.in) is an expression, because it produces a result: a newly-created object of the class Scanner. (We will reprise the exact behaviour of new later in the course.)

    Therefore, an expression can be written in any point of Java program where a value is required: when the program runs, the required value is obtained by computing the expression. For instance:

    • the operator + requires two numerical values to compute a result (e.g. 2 + 3 evaluates to the result 5). Therefore, we can use as arguments for + any expression that produces a number. E.g. we can write (2 * 3) + 5, and the result will be computed by first computing the expression 2 * 3, and then adding its result to 5;

    • to declare a variable, we write var x = ..., and we need to provide a value instead of .... Therefore, we can provide an expression like 2 + 3 or s.nextLine() or new java.util.Scanner(...) — and the result of the expression will become the value of x.

    Note

    For more details, see Section 2.4 (“Expressions”) of the reference book.

  • Statements do not produce a result value: their purpose is to have an effect on the program and its execution. For instance:

    • var x = 2 + 3 is a statement that declares the variable x, which can be used later in the program (but the statement itself does not produce any result value);

    • if (x) { ... } (which we will discuss shortly) is a statement that checks whether the expression x produces the value true, and if so, executes the code in ... (but the statement itself does not produce any result value).

    A Java program is essentially a sequence of statements delimited by semicolons ; and/or surrounded by curly brackets {}. When the program runs, the statements are executed one after the other — and executing a statement may require computing one or more expressions written as part of the statement (e.g. to execute the statement var x = 2 + 3, the expression 2 + 3 is computed, and its result value 5 is finally used to declare x).

The distinction between expressions and statements will become clearer with practice: the suggestion is to pay attention to how the terms “statement” and “expression” are used in these lecture notes, and refer back to this remark in case of doubts.

Conditional Statements and Expressions#

The Java programming language provides various ways to regulate the control flow of a program, i.e. the order in which different part of a program are executed, depending whether a certain condition is true or false. In this Module we address:

In this Module we also address some other ways to control computations depending on conditions: conditional expressions and switch constructs.

Important

Conditional expressions and “switch” constructs are not strictly necessary for writing beginner Java programs: every task that can be solved using them can also be solved using if-then-else statements. Therefore, make sure you understand how to use if-then-else before going further. With practice, you will see that can contitional expressions and “switch” constructs can help you write Java code that is simpler and easier to read and maintain.

If-Then-Else Statement#

The if-then-else statement has the following syntax:

1if (bool_expr) {
2    // Statements that are executed when the result of 'bool_expr' is 'true'
3} else {
4    // Statements that are executed when the result of 'bool_expr' is 'false'
5}
6// Other statements can follow (optionally)

where bool_expr can be any expression of type boolean. When an if-then-else statement is executed, the following happens:

  • first, the expression bool_expr is evaluated, and its result is checked. Subsequently,

    • if the result of bool_expr is the value true, the statements inside the first pair of curly brackets {} (lines 1–3) are executed;

    • otherwise (i.e. if the result of bool_expr is the value false), the statements inside the pair of curly brackets {} after else (lines 3–5) are executed;

  • finally, the execution continues with the statements (if any) that follow the whole if-then-else statement (line 6).

When represented as a flowchart, the if-then-else statement looks as follows — and whenever we spot the following pattern in a flowchart, we should consider using an if-then-else statement when writing the corresponding Java program.

Flowchart for if-then-else

Note

Conceptually, the if-then-else statement serves the same purpose of conditional jumps in BlinkenBits: it checks whether a condition (written as a boolean expression) produces the result true or false, and depending on this, it makes the execution “jump” to a certain part of the code. Indeed, behind the scenes, Java translates if-then-else into “jump” machine instructions.

The key difference is that if-then-else statements in Java are considerably easier to write and read than assembly jumps, because we do not need to worry about registers for computing the condition, nor memory addresses for jumping: we just need to structure our code depending on what we need to be executed when the condition is true or false.

Example 13 (Using if-then-else on the Java shell)

We can experiment with the if-then-else statement on the Java shell. Let us first declare two variables (a and b):

jshell> var a = 10; var b = 42
a ==> 10
|  created variable a : int
b ==> 42
|  created variable b : int

Now, let us create a variable that contains the result of a comparison between a and b: let us call this variable isAGreaterThanB (notice below that its type is boolean and its value is false).

jshell> var isAGreaterThanB = a > b;
isAGreaterThanB ==> false
|  created variable isAGreaterThanB : boolean

Then, copy&paste the following Java code on the Java shell, and press :

1if (isAGreaterThanB) {
2    System.out.println("'" + a + " > " + b + "' is TRUE :-)");
3} else {
4    System.out.println("'" + a + " > " + b + "' is FALSE :-(");
5}
6System.out.println("We got past the if-then-else statement");

What will happen is:

  • on line 1, the if-then-else statement checks whether the conditional expression (in this case, the variable isAGreaterThanB) has the value true or false. Since its value is false, the execution “jump” to the block of code under the else (line 4) — which displays the string "... is FALSE :-(";

  • finally, the execution continues with the code that follows the whole if-then-else statement, which displays the string "We got past...".

Therefore, we will see the following output on the Java shell:

'10 > 42' is false :-(
We got past the if-then-else statement

We can revise the example above by writing the condition expression directly in the if-then-else statement, so we do not have to create the variable isAGreaterThanB. In fact, we can write:

1if (a > b) {
2    System.out.println("'" + a + " > " + b + "' is TRUE :-)");
3} else {
4    System.out.println("'" + a + " > " + b + "' is FALSE :-(");
5}
6System.out.println("We got past the if-then-else statement");

Java executes this if-then-else by first computing the expression a > b (line 1); since its result is false, the execution “jumps” to the block of code under the else, and produces the same output we have seen before. (Try it on the Java shell!)

To see a different outcome, you can now try the following:

  1. assign to variable a a different value that is bigger than b, e.g.:

    jshell> a = b * 2
    a ==> 84
    |  assigned to a : int
    
  2. Then, copy&paste again the second if-then-else example above (the one containing the condition expression a > b) on the Java shell, and execute it. Observe that now the output is:

    '84 > 42' is TRUE :-)
    We got past the if-then-else statement
    

    Tip

    On the Java shell, instead of copy&pasting code snippets that have been already used, you can press the upwards arrow key to navigate the history of previous code snippets, and press to execute the one you select.

Remark 8 (Changing (re-assigning) a variable)

As you can see in the Example 13 above, in Java it is possible to declare a variable (e.g. var a = 10) and later change that variable with a new assignment (e.g. by writing a = b * 2). You can even assign a new value to a variable by using its own previous value: for instance, if a has value 10, and you write a = a + 3, the following happens:

  • first, the expression a + 3 is computed using the current value of a, that is 10. Therefore, a + 3 evaluates to 13;

  • then, the value 13 is assigned to a. Therefore, from now on, a has value 13; if you execute a = a + 3 again, then a + 3 will produce the result 16, hence a will get the value 16.

This may appear odd if you are used to mathematical terminology, where variables are declared once and cannot be changed afterwards. However, we have seen something very similar happen in BlinkenBits, e.g. when we write r0 <- r0 + r1 to change the value contained in the register r0 with the result of the sum of the current value in r0 plus the value in r1.

Indeed, the intuition here is that in Java, a “variable” works like a register (or a memory location): it contains a value that can be changed while the program runs. To do that in Java, we just write an assignment; therefore, when e.g. a = b * 2 is executed, the old value that was contained in a is overwritten with the result of the expression b * 2 (so, if b has value 42, the variable a gets the new value 84).

Note that, when a variable is reassigned, then its type must be respected: e.g. if the variable has type int, then we can only reassign a new value of type int.

If-Then Statement#

We can also write an if-then statement without the “else” part:

1if (bool_expr) {
2    // Statements that are executed when the result of 'bool_expr' is 'true'
3}
4// Other statements can follow (optionally)

which is just shorthand for:

1if (bool_expr) {
2    // Statements that are executed when the result of 'bool_expr' is 'true'
3} else {
4}
5// Other statements can follow (optionally)

In other words, when bool_expr gives the result false, then the else code block is executed (lines 3–4) — but since that block is empty, the program just continues its execution with the statements that follow on line 5.

When represented in a flowchart, the if-then statement looks as follows — and whenever we spot the following pattern in a flowchart, we should consider using an if-then statement when writing the corresponding Java program.

Flowchart for if-then

Example 14 (Using if-then on the Java shell)

Using the “if-then” statement, we can write an example similar to the one in Example 13, as follows:

1var message = "FALSE :-(";
2if (a > b) {
3    message = "TRUE :-)";
4}
5System.out.println("'" + a + " > " + b + "' is " + message);

When this code runs, the following happens:

  • line 1 declares a variable message with the string "FALSE :-(";

  • line 2 checks the condition a > b:

    • if the condition is true, then the block of code on lines 2–4 is executed. Consequently, the value of the variable message is changed with the string "TRUE :-)". Then, the execution continues on line 5;

    • otherwise (i.e. if the condition on line 2 is false), the execution just continues on line 5 — and consequently, the value of the variable message is not changed and remains the string "FALSE :-(";

  • finally, on line 5, a string with the current values of the variables a, b, and message is displayed.

You can experiment by running the code snippet above on the Java shell, using different values of a and b. For example, if a is 10 and b is 42, then the System.out.println(...) on line 5 displays:

'10 > 42' is FALSE :-(

Otherwise, if e.g. a is 84 and b is 42, then the System.out.println(...) on line 5 displays:

'84 > 42' is TRUE :-)

Note

While experimenting with the code snippet above, the Java shell may display additional messages like:

|  modified variable message : String
|    update overwrote variable message : String

The Java shell is just letting us know that a variable (in this case, message) has been redeclared. This is not a problem for this examples.

Boolean Expressions#

We have seen that, when writing if-then-else or if-then statements, or conditional expressions, we need to provide an expression of type boolean that determines what is computed and executed next (depending on whether the boolean expression evaluates to true or false).

We have already seen some expressions that produce boolean results by comparing two numbers using the relational operators < (“less than”), > (“greater than”) or == (“equal”); we have also used some of these operators in the examples above. Java also provides the relational operators <= (“less than or equal to”), >= (“greater than or equal to”), and != (“not equal to”): they also compare numbers and produce true or false as a result.

Java also allows to write expressions of type boolean using 3 logical operators listed in Table 7 below.

Table 7 Logical operators in Java#

Operator

Description

Example

Result

&&

Logical “and”

a && b

true if both a and b are true; false otherwise

||

Logical “or”

a || b

false if both a and b are false; true otherwise

!

Logical “not”

! a

true if a is false; false if a is true

Example 15 (Logical operators)

Our task is to write a program that checks the value of the variable speed (of type int), and:

  • if the value of speed is between 10 and 50 (included), then the program prints “Your speed is OK”;

  • otherwise (i.e. if the value of speed is less than 10 or more than 50), the program prints “Your speed is outside the range 10-50!”;

We can solve this problem in different ways, by using different relational and logical operators to compute whether the speed is within the limits.

  • We could write a boolean expression that produces true if speed is between 10 and 50 (included), and produces false otherwise. Using the logical operator &&, we can write:

    (speed >= 10) && (speed <= 50)
    

    Then, we can use the expression above as the condition of the following if-then-else statement:

    if ((speed >= 10) && (speed <= 50)) {
        System.out.println("Your speed is OK");
    } else {
        System.out.println("Your speed is outside the range 10-50!");
    }
    
  • As an alternative approach, we could write a boolean expression that evaluates to true if speed is outside the limits (i.e. either smaller than 10 or greater than 50), and evaluates to false otherwise. Using the logical operator ||, we can write:

    (speed < 10) || (speed > 50)
    

    Then, we can use the expression above as the condition of the following if-then-else statement (notice that, compared to the previous solution, here we need to flip the two branches of if-then-else, because the expression is true when the speed is outside the limits):

    if ((speed < 10) || (speed > 50)) {
        System.out.println("Your speed is outside the range 10-50!");
    } else {
        System.out.println("Your speed is OK");
    }
    
  • As a further alternative approach, we could write a boolean expression that evaluates to true if speed is not outside the limits (i.e. speed is not smaller than 10 and not greater than 50), and evaluates to false otherwise. Using the logical operators ! and &&, we can write:

    !(speed < 10) && !(speed > 50)
    

    Then, we can use the expression above as the condition of the following if-then-else statement:

    if (!(speed < 10) && !(speed > 50)) {
        System.out.println("Your speed is OK");
    } else {
        System.out.println("Your speed is outside the range 10-50!");
    }
    

You can try the expressions and if-then-else statements above on the Java shell by first declaring a variable called speed (of type int); you can try assigning different values to speed (e.g. 2, 33, 60, …) and see how different values alter the result of the boolean expressions, and the output displayed by the if-then-else statements. You can also try other variations of the boolean expressions and if-then-else statements.

Example 16 (Nesting if-then-else and if-then statements)

An if-then-else or if-then statement may execute any sequence of Java statements depending on the result of the boolean condition. This means that we can nest other if-then-else or if-then statements. For instance, consider again this case from Example 15:

if (!(speed < 10) && !(speed > 50)) {
    System.out.println("Your speed is OK");
} else {
    System.out.println("Your speed is outside the range 10-50!");
}

Now, suppose that we want our program to explicitly tell whether the speed is too fast (above the 10-50 range) or too slow (below the 10-50 range). We can do it as follows:

 1if (!(speed < 10) && !(speed > 50)) {
 2    System.out.println("Your speed is OK");
 3} else {
 4    // If the program execution is here, then either speed < 10 or speed > 50
 5    if (speed < 10) {
 6        System.out.println("You are going too slow!");
 7    } else {
 8        // If the program execution is here, then speed > 50
 9        System.out.println("You are going too fast!");
10    }
11}

You can experiment with the program above on the Java shell, by changing the values assigned to the variable speed.

Tip

We can make this last program a bit nicer-looking by leveraging the fact that, in Java, curly brackets are optional if they only enclose a single statement. Since the if-then-else on lines 5–10 is a single statement, we can remove the comment on line 4 and the surrounding curly brackets {} (lines 3 and 11), and reformat the program as follows:

if (!(speed < 10) && !(speed > 50)) {
    System.out.println("Your speed is OK");
} else if (speed < 10) {
    System.out.println("You are going too slow!");
} else {
    // If the program execution is here, then speed > 50
    System.out.println("You are going too fast!");
}

Conditional Expression#

Note

This section of the lecture notes is not strictly necessary for writing beginners’ Java programs. However, learning when and how to use the conditional expressions may make you Java programs easier to write and read.

Example 14 above shows a pattern that may often emerge when writing a program: the value of a variable (in this case, message) depends on a condition (in this case, a > b). For situations like this, Java offers a very handy tool that can make our programs simpler: conditional expressions, which have the following syntax:

bool_expr ? expr_if_cond_true : expr_if_cond_false

Where bool_expr must be an expression of type boolean, while expr_if_cond_true and expr_if_cond_false are two expressions of the same type (e.g. both int, both String, etc.).

The conditional expression is computed as follows:

  • first, bool_expr is computed, and its result is checked:

    • if the result of bool_expr is true, then expr_if_cond_true is computed, and its result becomes the result of the whole conditional expression;

    • otherwise (i.e. if the result of bool_expr is false), then expr_if_cond_false is computed, and its result becomes the result of the whole conditional expression.

Example 17 (Using conditional expressions on the Java shell)

Consider this code snippet:

1var message = (a > b) ? "TRUE :-)" : "FALSE :-(";
2System.out.println("'" + a + " > " + b + "' is " + message);

You can experiment with this code by copy&pasting it on the Java shell, and using different values for the variables a and b.

For example, when a is 10 and b is 42, the condition a > b (line 1) is false; consequently, the conditional expression result is the string "FALSE :-(" — and that string is used in the declaration of the variable message. Therefore, the System.out.println(...) (line 2) displays:

'10 > 42' is FALSE :-(

Instead, when a is 84 and b is 42, the condition a > b (line 1) is true; consequently, the conditional expression result is the string "TRUE :-)" — and that string is used in the declaration of the variable message. Therefore, the System.out.println(...) (line 2) displays:

'84 > 42' is TRUE :-)

Example 18 (Area calculator using conditional expressions)

The following Java program is very similar to the one in Example 12, and (from the user perspective) it behaves in the same way. The difference is that the highlighted lines perform their task (i.e., printing whether the rectangle is a square or not) with a conditional expression (instead of an if-then-else statement):

  • on line 19, the result of the conditional expression is used to declare the variable squareOrNot (which is a string). After that line is executed, the variable squareOrNot may contain either "a square" or "not a square", depending on whether the expression width == height is true or false;

  • then, on line 20, the value of the variable squareOrNot is printed, following the string "The specified rectangle is ". Therefore, the user will see a different output depending on the value of the variable squareOrNot.

 1class AreaCalculator {
 2    public static void main(String[] args) {
 3        var scanner = new java.util.Scanner(System.in);
 4        scanner.useLocale(java.util.Locale.ENGLISH);
 5
 6        System.out.println("Hello, human.  What is your name?");
 7
 8        var name = scanner.nextLine();
 9        System.out.println("Nice to meet you, " + name + "!");
10        System.out.println("Please write the name of a geometric shape:");
11
12        var shape = scanner.nextLine();
13        if (shape.equals("rectangle")) {
14            System.out.println("What is the width?");
15            var width = scanner.nextDouble();
16            System.out.println("What is the height?");
17            var height = scanner.nextDouble();
18
19            var squareOrNot = (width == height) ? "a square" : "not a square";
20            System.out.println("The specified rectangle is " + squareOrNot);
21
22            var area = width * height;
23            System.out.println("The area is: " + area);
24        } else if (shape.equals("circle")) {
25            System.out.println("What is the radius?");
26            var radius = scanner.nextDouble();
27
28            var area = radius * radius * Math.PI;
29            System.out.println("The area is: " + area);
30        } else {
31            System.out.println("I don't know the shape '" + shape + "' :-(");
32        }
33
34        scanner.close();
35        System.out.println("Goodbye!");
36    }
37}

Switch Statement#

Note

This section of the lecture notes is not strictly necessary for writing beginners’ Java programs. However, learning when and how to use the switch statement instead of if-then-else may make your Java programs easier to write and read.

Suppose we need to write a program that, given a number n, works as follows:

  • if n is between 0 and 2, the program prints the corresponding word (i.e. “zero”, “one”, or “two”)

  • otherwise, the program prints “WAT!”

We can do this by nesting several if-then-else and/or if-then statements, for instance as follows:

 1if (n == 0) {
 2    System.out.println("zero");
 3} else {
 4    if (n == 1) {
 5        System.out.println("one");
 6    } else {
 7        if (n == 2) {
 8            System.out.println("two");
 9        } else {
10            System.out.println("WAT!");
11        }
12    }
13}

This deep nesting can be hard to read — but we can leverage the fact that, in Java, curly brackets are optional if they only enclose a single statement. Therefore, we can remove some curly brackets and reformat the code above as:

1if (n == 0) {
2    System.out.println("zero");
3} else if (n == 1) {
4    System.out.println("one");
5} else if (n == 2) {
6    System.out.println("two");
7} else {
8    System.out.println("WAT!");
9}

This pattern is quite common, and contains a bit of redundancy: we specify multiple times that we are checking whether a same expression (n) is equal to different constant values (0, 1, …). Luckily, Java provides a handy tool to make this kind of code more straightforward: the switch statement, which allows us to rewrite the code above as follows:

 1switch (n) {
 2    case 0 -> {
 3        System.out.println("zero");
 4    }
 5    case 1 -> {
 6        System.out.println("one");
 7    }
 8    case 2 -> {
 9        System.out.println("two");
10    }
11    default -> {
12        System.out.println("WAT!");
13    }
14}

The meaning of the code above is:

  1. compute the result of the expression given as argument to switch (line 1)

  2. check whether that value is equal to one of the values provided in the various cases (lines 2, 5, 8) — and if so, execute the code that follows the corresponding arrow ->;

  3. otherwise, execute the code that follows default -> ....

Remark 9 (Omitting curly brackets in if-then-else, if-then, and switch statements)

The last few code examples could be made shorter. Since curly brackets are optional if they only enclose a single statement, we could have written:

1if (n == 0)
2    System.out.println("zero");
3else if (n == 1)
4    System.out.println("one");
5else if (n == 2)
6    System.out.println("two");
7else
8    System.out.println("WAT!");

This is, however, a bit risky and not recommended, because it makes it easy to introduce mistakes. For instance, suppose that we want to update the program to make it print “I don’t know this number” after “WAT!”. We may write a program like:

1if (n == 0)
2    System.out.println("zero");
3else if (n == 1)
4    System.out.println("one");
5else if (n == 2)
6    System.out.println("two");
7else
8    System.out.println("WAT!");
9    System.out.println("I don't know this number");

This program is syntactically valid and Java can run it. By looking at it, it may seem that the System.out.println(...) on line 9 belongs to the else on line 7, and is only executed when n is not 0, 1, nor 2 — and indeed, the program may seem to work correctly, e.g. when n has value 42 or 1000.

However this program has a bug and does not always behave as we want! In fact, if you try the program on the Java shell, you will see that it always prints I don't know this number, even when e.g. n has value 1. This is because the last else (line 7) only captures the first statement that follows it (line 8). Therefore, this program is equivalent to:

 1if (n == 0) {
 2    System.out.println("zero");
 3} else if (n == 1) {
 4    System.out.println("one");
 5} else if (n == 2) {
 6    System.out.println("two");
 7} else {
 8    System.out.println("WAT!");
 9}
10System.out.println("I don't know this number");

Instead, what we want is is:

 1if (n == 0) {
 2    System.out.println("zero");
 3} else if (n == 1) {
 4    System.out.println("one");
 5} else if (n == 2) {
 6    System.out.println("two");
 7} else {
 8    System.out.println("WAT!");
 9    System.out.println("I don't know this number");
10}

Summing up, the advice is: be careful when you omit curly brackets in if-then and if-then-else statements. It is better to use a few more curly brackets (even if they are not strictly needed) and avoid bugs that may be difficult to find and fix.

Instead, omitting brackets when using the switch statement is safer: if you make a mistake and omit curly brackets in the “wrong way,” Java will immediately report syntax errors and will not even run your program. The “switch” example above can be simplified as:

1switch (n) {
2    case 0 -> System.out.println("zero");
3    case 1 -> System.out.println("one");
4    case 2 -> System.out.println("two");
5    default -> System.out.println("WAT!");
6}

If we try to add a new System.out.println(...) right after the one on line 5, as follows…

1switch (n) {
2    case 0 -> System.out.println("zero");
3    case 1 -> System.out.println("one");
4    case 2 -> System.out.println("two");
5    default -> System.out.println("WAT!");
6               System.out.println("I don't know this number"); // Invalid syntax
7}

…then this last program will not run: the Java shell will produce a bunch of syntax errors. Therefore, we immediately know that something is wrong.

To add a new statement to the default case (or in other cases) of the switch without causing syntax errors, we must use curly brackets:

1switch (n) {
2    case 0 -> System.out.println("zero");
3    case 1 -> System.out.println("one");
4    case 2 -> System.out.println("two");
5    default -> {
6        System.out.println("WAT!");
7        System.out.println("I don't know this number");
8    }
9}

Switch Expression#

Note

This section of the lecture notes is not strictly necessary. However, learning when and how to use the switch expression instead of if-then-else or conditional expressions may make your Java programs easier to write and read.

Java also provides a switch expression, that is intuitively similar to a conditional expression, and allows us to rewrite the code we used to illustrate the switch statement in a more concise way:

1var number = switch (n) {
2    case 0 -> "zero";
3    case 1 -> "one";
4    case 2 -> "two";
5    default -> "WAT!";
6};
7System.out.println(number);

The meaning of the switch expression above is:

  1. compute the result of the expression given as argument to switch (line 1);

  2. check whether that result is equal to one of the constant values provided in the various cases (lines 2, 3, 4) — and if so:

    • compute the result of the expression that follows the corresponding arrow ->, and

    • that result becomes the result of the whole switch expression;

  3. otherwise (i.e. if none of the cases is equal to the result of the argument of switch):

    • compute the result of the expression that follows default -> ... (line 5);

    • that result becomes the result of the whole switch expression.

In the code snippet above, the result of the switch expression (which has type String) is used to declare the variable number (line 1), that is displayed (line 7).

More on Strings in Java#

We now reprise the topic of Strings in Java, to highlight their features and the possible pitfalls when comparing two strings.

Some Useful String Methods#

A very important fact is that in Java, String is not a primitive data type: instead, String is a class, and when we write a string literal like "Hello", we are creating an object of class String.

The consequence of this fact is that String objects have methods that we can invoke (just like we can invoke methods of Scanner objects).

We can try such methods on the Java shell. First, let’s create a String object to declare a variable str that we will use for our experiments:

jshell> var str = "Hello!"
|  created variable str : String

We can compute the length of str by calling the method str.length(), which takes no arguments and produces a result of type int (in this case, 6):

jshell> var len = str.length()
len ==> 6
|  created variable len : int

We can retrieve the character at a certain position of str by calling str.charAt(pos): this method takes an argument (pos, which must be an int) that is the desired character position (counting from 0); then, the method produces a result of type char.

jshell> var char0 = str.charAt(0)
char0 ==> 'H'
|  created variable char0 : char
jshell> var char3 = str.charAt(3)
char3 ==> 'l'
|  created variable char3 : char

Important

If we call str.charAt(...) with a character position beyond the length of str, we get an error (as an exception), as shown below. (Remember that, since character positions are counted from 0, the last character of str is at position str.length() - 1)

jshell> str.charAt(str.length())
|  Exception java.lang.StringIndexOutOfBoundsException: String index out of range: 6
|        at StringLatin1.charAt (StringLatin1.java:48)
|        at String.charAt (String.java:1517)
|        at (#36:1)

We can compute the uppercase version of str by calling the method str.toUpperCase(), which takes no arguments and produces a new String:

jshell> var strUp = str.toUpperCase()
strUp ==> "HELLO!"
|  created variable strUp : String

Similarly, we can compute the lowercase version of str by calling the method str.toLowerCase(), which takes no arguments and produces a new String:

jshell> var strLow = str.toLowerCase()
strLow ==> "hello!"
|  created variable strLow : String

We can also compute a string obtained by repeating str, by calling str.repeat(n) (where n is an int specifying the number of repetitions).

jshell> var str3 = str.repeat(3)
str2 ==> "Hello!Hello!Hello!"
|  created variable str3 : String

Note

String objects provide many more methods: you don’t need to learn them by heart, but they will become familiar with frequent usage. For details, you can always refer to the Java API documentation for the String class.

Comparing Strings for Equality or Alphabetically#

In previous examples we have seen that we can compare numbers for equality using the operator ==, which returns a boolean value (true or false). For example:

jshell> 5 == 2 * 6 - 7
$6 ==> true
|  created scratch variable $6 : boolean

This may lead us to believe that we can also use == to compare String objects. For instance, the string "BlahBlah" should be equal to the result of "Blah".repeat(2)". But if we try, we get the result false!

jshell> "BlahBlah" == "Blah".repeat(2)
$7 ==> false
|  created scratch variable $7 : boolean

This happens because Java Strings are objects — and when used on objects, the operator == does not behave as one might expect.

We will see the precise reason later in the course. For now, it is important to remember that to check whether two String objects are equal, we must use the method .equals(...) instead of ==. When we call e.g. str1.equals(str2), the method produces true if the two strings str1 and str2 are equal character-by-character; otherwise, the method produces false. For instance:

jshell> "BlahBlah".equals("Blah")
$8 ==> false
|  created scratch variable $8 : boolean
jshell> "BlahBlah".equals("Blah".repeat(2))
$9 ==> true
|  created scratch variable $9 : boolean

Example 19 (Switch statement on Strings)

The “switch” statement works as one might expected when used on Strings: i.e. it compares the string contents against the constant strings provided in the various cases, using the method .equals(...).

Consider the following snippet of Java code that, given a String variable num containing a number written in English, prints the corresponding numerical value:

1switch (num) {
2    case "zero" -> System.out.println("0");
3    case "one" -> System.out.println("1");
4    case "two" -> System.out.println("2");
5    default -> System.out.println("WAT!");
6}

When the switch statement above checks the various cases, it uses num.equals("zero") on line 2, num.equals("one") on line 3, etc.

(As usual, you can try the code above on the Java shell: declare a variable num of type String, and experiment by copy&pasting the code above and running it after assigning different values to num.)

We conclude this overview with another handy method: str1.compareTo(str2) produces an int value, that can be either:

  • negative, if str1 comes alphabetically before str2;

  • 0 (zero), if str1 and str2 are equal character-by-character; or

  • positive, if str1 comes alphabetically after str2.

For instance:

jshell> "AAA".compareTo("AAB")
$10 ==> -1
|  created scratch variable $10 : int
jshell> "AAA".compareTo("AAA")
$11 ==> 0
|  created scratch variable $11 : int
jshell> "AAB".compareTo("AAA")
$12 ==> 1
|  created scratch variable $12 : int

Concluding Remarks#

In this module we have seen how to perform input/output on the console, and how to control execution flow of a program by using the if-then-else and if-then statements. We have also studied in more details how to write boolean expressions, and more details and features of Strings.

Along the way, we have also introduced some important concepts: using Java classes and objects (using the Scanner class as a practical application), and the difference between values and objects, and types and classes, and between expressions and statements.

With this knowledge, you should be able to understand each line of the program in Example 12.

We have also addressed some more advanced tools: conditional expressions, the Switch Statement and Switch Expression: you do not need to master these more advanced tools yet — but in some situations, they can be used instead of if-then-else statements and conditional expressions to write simpler and more maintainable programs.

References and Further Readings#

You are encouraged to read the following sections of the reference book: (Note: they sometimes mention Java features that we will address later in the course)

  • Section 2.1 - “Character Strings”

  • Section 2.6 - “Interactive Programs”

  • Section 5.1 - “Boolean Expressions”

  • Section 5.2 - “The if Statement”

  • Section 5.3 - “Comparing Data”

  • Section 6.2 - “The Conditional Operator”

You can also have a look at Section 6.1 of the reference book (“The switch Statement”) — but notice that the book uses a different and “more traditional” syntax for switch statements. You should be aware of that “traditional” syntax, because you may see it used in other programs. These lecture notes adopt a more modern syntax for switch (often called “enhanced switch”) that is easier to write and read, and less prone to introducing bugs. If you are curious, you can learn why the modern syntax for switch was introduced by reading the (now implemented) Java Enhancement Proposal 361.

You can find the complete documentation of the Java API here: https://docs.oracle.com/en/java/javase/21/docs/api/index.html.

Exercises#

Note

These exercises are not part of the course assessment. They are provided to help you self-test your understanding.

Exercise 21 (Experimenting with comparisons)

Read the following Java expressions, and figure out whether each one is a valid comparison, and if so, what is the result. To verify whether your answers are correct, you can execute each expression on the Java shell.

  • 2 = 2

  • 2 == 2

  • 2 == 1 + 1

  • 2 == "2"

  • "AAA" == "A".repeat(3)

  • "AAA".equals("A".repeat(3))

Exercise 22 (Boolean expressions)

Assume that variable x has value 1, and y has value 2.

Read the following Java expressions: each one has type boolean, and your task is to figure out what is its result. To verify whether your answers are correct, you can execute each expression on the Java shell (after declaring the two variables x and y with values 1 and 2, respectively).

Tip

  • Keep in mind that the logical operators && and || have very low precedence with respect to other operators: for example, the expression 2 > 3 || x == y is equivalent to (2 > 3) || (x == y) (i.e. the sub-expressions 2 > 3 and x == y are evaluated first, and then their results are used to compute ||).

  • If an expression looks complex, or its evaluation seems unclear, try to manually add parentheses around its sub-expressions, and check whether the original expression and the version with added parentheses evaluate to the same result.

  • If you are in doubt about operator precedence when writing your Java programs, just add parentheses to make sure that sub-expressions are evaluated in the order you expect. Writing unnecessary parentheses may not be very elegant — but forgetting necessary parentheses is much worse, because it would introduce bugs in your programs!

  • false

  • true

  • true == false

  • false == false

  • x != y

  • x < 3 + y

  • y < x + 3

  • (x + y > 3) == false

  • false != x < 3

  • (x == y) == false

  • !false

  • !true

  • !true == false

  • !(true == false)

  • true && false

  • false || true

  • x + y > 3 && x < y

  • x + y == 3 || x < 4

  • x < y && (3 * 4 == 2 * 6 - 1) == !(3 < x)

Lab and Weekly Assessments#

During the lab you can try the exercises above or begin your work on the weekly assessments below.

Note

These weekly assessment follow the ones in part 1 of this Module.

Important

04 - Conversion from Seconds#

Edit the file Conversion.java provided in the handout, and write a program that reads from the console a number of seconds (an int value), and displays how many days, hours, minutes and seconds the specified number of seconds corresponds to. Then, submit the modified file Conversion.java on DTU Autolab.

For example, if the console input is the number 238577, the program should print:

238577 seconds equals 2 days, 18 hours, 16 minutes and 17 seconds.

Hint

  • To read the input value from the console, you should create and use a Scanner object.

  • If you have n seconds and n has type int, then:

    • to know how many minutes fit in n seconds, you can compute n / 60 (which is an integer division);

    • to know how many seconds remain after removing from n all the minutes, you can compute n % 60 — where % is the remainder operator in Java.

  • The program must only print on the console the result of the conversion (according to the example above) and nothing else.

05 - Password Check#

Edit the file Password.java provided in the handout, and write a program that:

  1. reads a String from the console, representing a password; and

  2. checks the length of the password:

    • if the length is between 5 and 8 characters, the program prints Password length OK;

    • if the password is shorter than 5 characters, the program prints Password too short;

    • if the password is longer than 8 characters, the program prints Password too long.

Then, submit the modified file Password.java on DTU Autolab.

06 - Line-Point Distance#

Edit the file Distance.java provided in the handout, and write a program that reads from the console the values of 4 variables (of type double), in the following order:

  • \(a\) and \(b\) — coefficients of the equation of a line \(y = ax + b\);

  • \(x_0\) and \(y_0\) — the coordinates of a point.

Then, the program must compute and display on the console the distance between the point and the line.

The distance is computed with the following formula:

\[ \frac{|a x_0 - y_0 + b|}{\sqrt{1 + a^2}} \]

If the computed distance is (say) 0.52, the program must print on the console:

The distance is 0.52

When you are done, submit the modified file Distance.java on DTU Autolab.

Hint

  • To compute the absolute value and the square root in the formula, you can use Math.abs(...) and Math.sqrt(...), respectively. For instance:

    jshell> Math.abs(-2 * 3.0)
    $1 ==> 6.0
    |  created scratch variable $1 : double
    
    jshell> Math.sqrt(25.0)
    $2 ==> 5.0
    |  created scratch variable $2 : double
    
  • The program must only print on the console the computed distance, and nothing else.