Lecture from: 01.10.2024 | Video: Videos ETHZ

Handling Program Input (Scanner)

Introduction

Every program needs to interact with its environment to function effectively. This interaction often involves receiving data – the input – which the program then processes and potentially outputs as a result. Input can come from various sources, each posing unique challenges and requiring specific handling techniques.

Diverse Input Sources: The Challenge of Variability

Programs can receive input from a wide range of sources, including:

  • Console: Text-based input directly from the user via the command line interface.
  • Files: Structured or unstructured data stored persistently on disk.
  • Websites: Information retrieved dynamically from web servers through HTTP requests.
  • Databases: Organized collections of data managed by specialized systems (e.g., MySQL, PostgreSQL).

Each input source presents its own set of complexities:

  1. Unpredictability: The behavior of external systems and users is inherently unpredictable. Input formats may vary, timing can be inconsistent, and unexpected errors can occur.
  2. Controllability: Programmers have limited control over the environment generating the input. We cannot dictate the exact content or structure of data received from external sources.
  3. Data Conversion: Raw input often needs to be converted into meaningful data types (e.g., integers, strings, booleans) that the program can understand and process effectively.

Java’s Scanner

In Java, the Scanner class provides a convenient mechanism for reading user input from the console.

Example:

import java.util.Scanner;
 
public class InputDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); // Create a Scanner object connected to System.in (console)
 
        System.out.print("Enter your name: ");
        String name = sc.nextLine(); // Read a complete line of text as a String
 
        System.out.println("Hello, " + name + "!");
    }
}

Key Scanner Methods:

  • next(): Reads the next token (usually a word) from the input stream.
  • nextLine(): Reads an entire line of text from the input stream.
  • nextInt(): Reads the next integer from the input stream.
  • nextDouble(): Reads the next double-precision floating-point number from the input stream.
  • hasNext(): Checks if there is more input available in the stream.

Note: Always close the Scanner object when finished to release system resources using sc.close().

Methods

next():

  • This method reads the next token from the input stream. A token is typically a sequence of characters separated by whitespace (spaces, tabs, or newlines). Think of it like reading words individually.

  • Example: Imagine the user types: “Hello world this is a test”

    Scanner sc = new Scanner(System.in);
    System.out.print("Enter a phrase: ");
    String word1 = sc.next(); // Reads "Hello"
    String word2 = sc.next(); // Reads "world"
    System.out.println("You entered: " + word1 + " and " + word2);
  • Important: sc.next() will stop reading at the first whitespace it encounters. It won’t read the entire line.

nextLine():

  • This method reads the entire line of text from the input stream, including any spaces and up to the newline character (which marks the end of a line).

  • Example: Using the same input phrase “Hello world this is a test”

    Scanner sc = new Scanner(System.in);
    System.out.print("Enter a phrase: ");
    String wholeLine = sc.nextLine(); // Reads "Hello world this is a test"
    System.out.println("You entered: " + wholeLine);

Key Differences:

MethodBehaviorExample InputOutput
next()Reads the next token (word)“Hello world this is a test""Hello” and “world”
nextLine()Reads the entire line of text”Hello world this is a test""Hello world this is a test”

Potential Pitfalls: Data Type Mismatches

When using methods like nextInt(), nextDouble(), etc., the Scanner class expects a specific data type from the user’s input.

If the user enters something that doesn’t match the expected type, you’ll encounter a runtime error. This means your program will halt unexpectedly during execution.

Example:

import java.util.Scanner;
 
public class TypeMismatchDemo {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
 
        System.out.print("Enter an integer: ");
        // User enters "hello" (not a valid integer)
        try {
            int num = sc.nextInt(); // Attempting to read an integer
            System.out.println("You entered: " + num);
        } catch (Exception e) {
            System.out.println("Error: Invalid input. Please enter a valid integer.");
        }
    }
}

Explanation:

  1. The sc.nextInt() method tries to read an integer from the user’s input.
  2. The user enters “hello”, which is not a valid integer representation.
  3. This causes a runtime error, specifically a InputMismatchException.
  4. Our try...catch block gracefully handles this exception, preventing the program from crashing and providing a more informative message to the user.

Key Takeaways:

  • Always validate user input to ensure it matches your program’s expectations.
  • Use try...catch blocks to handle potential runtime errors caused by data type mismatches.

Random

While we often think of randomness as an inherent property of the universe, computers are fundamentally deterministic machines. They follow precise instructions and produce predictable outputs given a specific input.

Therefore, true “randomness” is elusive in the realm of computing. What we have instead are pseudo-random number generators (PRNGs). These sophisticated algorithms create sequences of numbers that appear random but are actually generated based on a starting value called a seed.

Java’s Random Class: A Glimpse into Pseudo-Randomness

Java provides the java.util.Random class to generate pseudo-random numbers. Let’s explore its workings:

Initialization:

  • The Random constructor takes an optional long seed value. If no seed is provided, the system time is used as a default seed, ensuring different starting points for each program run.
Random rand = new Random(); // Uses current system time as the seed

Methods:

  • nextInt(int bound): Generates a pseudo-random integer between 0 (inclusive) and bound (exclusive). For example, rand.nextInt(10) will return a random number from 0 to 9.

    System.out.println("Random number between 0 and 9: " + rand.nextInt(10));
  • nextDouble(): Generates a pseudo-random double-precision floating-point number between 0.0 (inclusive) and 1.0 (exclusive). Useful for generating random decimals.

    System.out.println("Random decimal between 0.0 and 1.0: " + rand.nextDouble());
  • nextBoolean(): Returns a pseudo-random boolean value (either true or false).

Putting Random to Work

Here are some common scenarios where the Java Random class shines:

1. Random Integer within a Range [1, n]

  • Use Case: Simulating dice rolls, selecting random items from a list of size n.
  • Code Example:
int n = 6; // Size of the range (dice roll example)
Random rand = new Random();
int randomNumber = rand.nextInt(n) + 1;  // Generates [1, n]
System.out.println("Random number between 1 and " + n + ": " + randomNumber);

2. Random Integer within a Range [min, max]

  • Use Case: Generating random scores, simulating game events with specific boundaries.
  • Code Example:
int min = 10; // Lower bound of the range
int max = 20; // Upper bound of the range
Random rand = new Random();
int randomNumber = rand.nextInt(max - min + 1) + min;
System.out.println("Random number between " + min + " and " + max + ": " + randomNumber);

3. Random Double within a Range [min, max]

  • Use Case: Simulating real-world phenomena with continuous values (e.g., temperature, distance).
  • Code Example:
double min = 5.0; // Lower bound
double max = 15.0; // Upper bound
Random rand = new Random();
double randomNumber = rand.nextDouble() * (max - min) + min;
System.out.println("Random number between " + min + " and " + max + ": " + randomNumber);

4. Random Selection from a Collection of Objects

  • Use Case: Simulating card draws, choosing random players in a game.
  • Code Example (Java List):
List<String> colors = Arrays.asList("red", "green", "blue", "yellow");
Random rand = new Random();
int randomIndex = rand.nextInt(colors.size());
String chosenColor = colors.get(randomIndex);
System.out.println("Chosen color: " + chosenColor);

Control Flow

Control flow, at its core, dictates the execution sequence of a program – a fundamental concept underpinning every conceivable computational task.

Conditions

Conditional flow introduces a layer of strategic decision-making into programs by enabling them to execute different code paths based on evaluated conditions.

The if Statement

The if statement is the cornerstone of conditional logic in programming. It empowers your code to make decisions and execute different blocks of code based on whether a specific condition is true or false.

Basic Structure:

if (condition) {
    // Code to execute if the condition is true
}
  • condition: A boolean expression that evaluates to either true or false.

Example: Determining Adult Status

int age = 20;
 
if (age >= 18) {
    System.out.println("You are an adult.");
}

In this example, the condition is age >= 18. Since age is 20, which is greater than or equal to 18, the condition evaluates to true, and the code inside the curly braces executes, printing “You are an adult.”

The else Statement:

When you need to handle two distinct outcomes—one if the if condition is true and another if it’s false—the else statement comes into play.

Basic Structure:

if (condition) {
    // Code to execute if the condition is true
} else {
    // Code to execute if the condition is false
}

Example: Age-Based Greetings

int age = 15;
 
if (age >= 18) {
    System.out.println("You are an adult.");
} else {
    System.out.println("You are a minor.");
}

In this example:

  • If age is 18 or greater, the first block executes (“You are an adult.”).
  • If age is less than 18, the second block executes (“You are a minor.”).

Comparison Operators

Comparison operators allow you to compare values and determine if one value is greater than, less than, equal to, or otherwise related to another. They return a boolean result (true or false).

OperatorMeaningExampleResult
==Equal to10 == 10True
!=Not equal to5 != 7True
>Greater than20 > 10True
<Less than5 < 10True
>=Greater than or equal to10 >= 10True
<=Less than or equal to8 <= 10True

Important Note: Be Careful with = vs ==!

  • = (Assignment operator): Assigns a value to a variable. The assignment itself has the type of the variable and the value that is being assigned. (x = 5)
  • == (Equality operator): Compares values for equality. (x == 5)

Here the assignment has the type boolean and is true, therefore the first part will be executed.

Logical (Boolean) Operators

Logical operators allow you to combine multiple conditions to create more complex expressions.

OperatorMeaningExampleResult
&&AND (Both conditions must be true)age >= 18 && hasLicense == trueTrue if both are true
||OR (At least one condition must be true)hasStudentID || isGuestTrue if either is true
!NOT (Reverses the truth value of a condition)!(age < 18)True if age is 18 or older

Note: &&has stronger precedence than ||. These operators are also left associative.

Nested if Statements:

For intricate decision-making, you can nest if statements within each other, creating a hierarchy of conditions.

int grade = 85;
 
if (grade >= 90) {
    System.out.println("A");
} else if (grade >= 80) {
    System.out.println("B");
} else if (grade >= 70) {
    System.out.println("C");
} else {
    System.out.println("D or below");
}

The else if statement:

The else if statement provides a structured way to check multiple conditions sequentially, executing the first true block.

  • Think of it like a series of “if” checkpoints – once one condition is met, the rest are skipped.

Ternary Operator: A Compact Conditional

For simple decisions, Java offers the ternary operator (condition ? expression1 : expression2), which condenses an if-else into a single line.

  • Example: int result = (age >= 18) ? "Adult" : "Minor";

Single Statement No Brackets:

In Java, when the code block following an if, else if, or else statement consists of a single statement, you can omit the curly braces ({}).

Example:

if (age >= 18) System.out.println("You are an adult.");

Short-Circuit Evaluation

Java uses short-circuit evaluation to optimize logical expressions. This means that once the outcome of an expression is known (true or false), the remaining parts are not evaluated.

Example:

if (hasLicense && (age >= 18)) {
    System.out.println("You can drive."); // Only executed if both conditions are true
}

If hasLicense is false, the entire condition becomes false, preventing the need to check age. This saves time and resources, especially in complex scenarios.

This can also be used to prevent divide by 0 errors. You simply check if the denominator is not 0. If it is, the latter part will not be evaluated and you won’t get a runtime exception.

This also means that p && q is not quite the same as q && p. Example: b != 0 && a/b > 1. The same holds for p || q.

Loops: Repeating Actions with Precision

Loops allow you to execute a block of code repeatedly until a certain condition is met. They represent another fundamental control flow mechanism, facilitating the repeated execution of a block of code until a specific condition is met. Java offers several loop constructs.

The for Loop

The for loop is a powerful tool when you know exactly how many times you need to repeat a block of code. It operates on a clear structure: initialization, condition, and increment.

for (int i = 0; i < 5; i++) {
    // Code to be executed repeatedly
}

In this example, int i = 0 initializes a counter variable i to 0. The i < 5 condition checks if i is less than 5; as long as it is true, the loop continues. Finally, i++ increments i by 1 after each iteration.

Infinite Loops

While uncommon, infinite loops occur when the loop condition never becomes false, leading to endless execution. These can quickly consume system resources and require manual intervention to stop.

  • Example: while (true) { ... } – This loop runs continuously until explicitly terminated.

Breaking Out of Loops

The break; statement provides an immediate exit from a loop, regardless of the loop condition.

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break; // Exit the loop when i reaches 5
    }
    System.out.println(i);
}

Continuing to the Next Iteration

The continue; statement skips the remaining code within the current iteration and jumps directly to the next one.

for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) { // Check if i is even
        continue; // Skip even numbers
    }
    System.out.println(i);
}

In this case, the code prints only odd numbers from 0 to 9.

The while Loop

The while loop provides a way to execute code repeatedly as long as a given condition holds true. It’s particularly well-suited when the exact number of iterations is unknown beforehand, and the loop should continue until a specific state or outcome is achieved.

int count = 0;
while (count < 5) {
    System.out.println("Count: " + count);
    count++; // Increment the counter
}

This example demonstrates a while loop that prints numbers from 0 to 4. The loop continues as long as the value of count is less than 5. Inside the loop, we print the current value of count and then increment it by 1 using count++. Once count reaches 5, the condition becomes false, and the loop terminates.

Continue here: 05 Java (Functions, Value vs Reference Semantics, Variable Scope)