How to re-write an input loop to not contain code repetition?

Viewed 397

I have the following code, which continues to ask the user to enter a letter as long as the letter is either "a" or "b":

import java.util.Scanner;

public class Main
{   
    public static void main(String[] args)
    {
        Scanner scan = new Scanner(System.in);
        
        String letter;
        
        System.out.print("Enter a letter: ");
        letter = scan.nextLine();
        
        while(letter.equals("a") || letter.equals("b"))
        {
            System.out.println("You entered: " + letter);
            
            System.out.print("Enter a letter: ");
            letter = scan.nextLine();
        }
    }
}

But the following code is repeated twice:

System.out.print("Enter a letter: ");
letter = scan.nextLine();

Is there a way to make the above code only appear one time?

11 Answers
    while (true) {
        System.out.print("Enter a letter: ");
        String letter = scan.nextLine();
        if (!letter.equals("a") && !letter.equals("b"))
            break;
        System.out.println("You entered: " + letter);
    }

This is the classic example of a loop that is neither naturally while-do nor do-while — it needs to exit from the middle, if you want the same behavior and also to reduce code duplication.

(Notice also that the variable declaration letter has been moved to an inner scope since it is no longer needed in the outer scope.  This is a small positive indication.)

As an alternative to while (true) some languages allow degenerate for-loop as in for(;;).


The below reverses the logic of the conditional loop exit test, at the expense of more control flow logic.

    while (true) {
        System.out.print("Enter a letter: ");
        String letter = scan.nextLine();
        if (letter.equals("a") || letter.equals("b")) {
            System.out.println("You entered: " + letter);
            continue;
        }
        break;
    }

(There is no difference between these in efficiency terms — these are equivalent at the level of machine code.)

do while loop

import java.util.Scanner;

public class Main
{   
    public static void main(String[] args)
    {
        Scanner scan = new Scanner(System.in);

        String letter;
        
        do{   
            System.out.print("Enter a letter: ");
            letter = scan.nextLine();
            System.out.println("You entered: " + letter);

        }while(letter.equals("a") || letter.equals("b"));
    }
}

It will loop once first, and then continue again if the statment is true.

You need to perform three sequential actions in a loop:

  • read the input entered by the user;
  • validate the input;
  • print the input, but only if it is valid.

That means that conditional logic must reside inside the loop before the statement that prints the input. And that makes the condition of the loop redundant. Instead, we can create an infinite loop with a break statement wrapped by a condition, that validates the input.

While loop

That's how it can be done using a while loop:

public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    
    String letter;
    
    while (true) {
        System.out.print("Enter a letter: ");
        letter = scan.nextLine();
        if (!letter.matches("[ab]")) break;
        System.out.println("You entered: " + letter);
    }
}

Method matches() that expects a regular expression as argument is used in the code above to simplify the termination condition.

For more information on regular expressions, take a look at this tutorial


For loop

Regarding the advice of utilizing a for loop - that's doable, but by its nature the task of reading the user input fits better in the concept of while loop because we don't know the amount of data in advance, and the iteration can terminate at any point in time.

Also note syntax of the for statement consists of three parts separated with a semicolon:

  • initialization expression - allow to define and initialize variables that would be used in the loop;
  • termination expression - condition which terminates the execution of the loop;
  • increment expression - defines how variables would change at each iteration step.

All these parts are optional, but the two semicolons ; inside the parentheses always have to be present.

Because as I've said earlier, the input needs to be validated inside the loop, we can't take advantage from the termination expression and increment expression.

For the sake of completeness, below I've provided the version of how it can be achieved using a for loop:

public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    
    for (String letter = "a"; ;) {
        System.out.print("Enter a letter: ");
        letter = scan.nextLine();
        if (!letter.matches("[ab]")) break;
        System.out.println("You entered: " + letter);
    }
}

The only advantage is that the scope of the variable letter was reduced. But approach of utilizing while loop is more readable and expressive.

Alternative approach

Another option is to preserve your initial structure of the code:

  • initialize the variable letter before the loop, at the same line where it is defined;
  • enter the loop if letter holds a valid input;
  • print the input and reassign the variable.

But in order to avoid duplication of the code line responsible for printing the prompt and reading the input will be extracted into a separate method.

public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);

    String letter = readInput(scan);

    while (letter.matches("[ab]")) {
        System.out.println("You entered: " + letter);            
        letter = readInput(scan);
    }
}

public static String readInput(Scanner scan) {
    System.out.print("Enter a letter: ");
    return scan.nextLine();
}

As Bobulous mentioned, a do-while loop is another simple solution. If duplicating the conditional is still a deal-breaker for you, though, you can also create a function that returns a boolean, and, when true, prints the extra text.

public static void main(String[] args)
{
    Scanner scan = new Scanner(System.in);
    
    String letter;
    
    do
    {
        System.out.print("Enter a letter: ");
        letter = scan.nextLine();
    } while(inputIsAOrB(letter));
}

public static boolean inputIsAOrB(String input) {
    if (input.equals("a") || input.equals("b"))
    {
        System.out.println("You entered: " + input);
        return true;
    }
    else
    {
        return false;
    }
}

You can also view the problem as generating and processing a stream of strings. The side effects in the generator may trigger some philosophical discussions, otherwise I think it is quite clean:

Stream.generate(() -> {
          System.out.print("Enter a letter: ");
          return scan.nextLine();
      })
      .takeWhile(str -> str.equals("a") || str.equals("b"))
      .forEach(str -> System.out.println("You entered: " + str));

... which will run like this:

Enter a letter: a
You entered: a
Enter a letter: b
You entered: b
Enter a letter: c

Process finished with exit code 0

A while loop with a break after executing your second print command will limit the code to only asking for a single input.

    Scanner ct = new Scanner(System.in);

    String input;

    System.out.print("Please enter either 'a' or 'b': ");

    input = ct.nextLine();

    while(input.equals("a") || input.equals("b")){
        System.out.println("You entered: " + input);
        break;
    }

Simply

List<Character> expectedChars = new ArrayList<>();
expectedChars.add('a');
expectedChars.add('b');
while(!expectedChars.contains(line = scan.nextLine())) {
System.out.println("Not expected");
}

// Now has a expected char. Proceed.

I without using any break or if-else externally (I am using ternary operator though) within control loop, you can also use below :

Scanner scanner = new Scanner(System.in);
boolean flag=true;
        while (flag) {
            System.out.println("enter letter");
            String bv = scanner.nextLine();
            flag=bv.matches("a|b");
            System.out.println(flag?"you entered"+bv:' ');
}

with for loop, it can be even simpler :

Scanner scanner = new Scanner(System.in);
        for (boolean flag = true; flag;) {
            System.out.println("enter letter");
            String bv = scanner.nextLine();
            flag = bv.matches("a|b");
            System.out.println(flag ? "you entered" + bv : ' ');
        }

Or if you ok for having whitspace for first run:

   Scanner scanner = new Scanner(System.in);
   String aa=" ";
   String bv=null;
    for (boolean flag = true; flag;aa=(flag?"you entered: "+bv:" ")) {
            System.out.println(aa);
            System.out.println("enter letter");
            bv = scanner.nextLine();
            flag = bv.matches("a|b");
        }

You can easily run a while loop until letter = "a" or letter = "b". Code will start the while loop with intial "" value of the letter and get the new value by scanner. Then it check the new value of the letter before starting the next round of the loop.

package com.company;

import java.util.Scanner;

public class Main
{
    public static void main(String[] args)
    {
        Scanner scan = new Scanner(System.in);

        String letter = "";

        while(!(letter.equals("a") || letter.equals("b")))
        {
            System.out.print("Enter a letter: ");
            letter = scan.nextLine();
            System.out.println("You entered: " + letter);

        }
    }
}

Similar as previous answer, but from a code readability perspective i would create an array of valid characters instead and use in condition. That results in the more "text like" condition below reading "if not validCharacters contains letter":

  public static void main(String[] args) {
    Scanner scan = new Scanner(System.in);
    List<String> validCharacters = List.of("a", "b");

    while (true) {
      System.out.print("Enter a letter: ");
      String letter = scan.nextLine();
      if (!validCharacters.contains(letter)) {
        break;
      }
      System.out.println("You entered: " + letter);
    }
  }

Your code just needed a little tweak to make it work, although you can use many more efficient approaches but here it is:

import java.util.Scanner;

public class Main
{   
    public static void main(String[] args)
    {
        Scanner scan = new Scanner(System.in);
        
        String letter = "a";
        
        while(letter.equals("a") || letter.equals("b"))
        {
            
            System.out.print("Enter a letter: ");
            letter = scan.nextLine();
            System.out.println("You entered: " + letter);
            
        }
    }
}
Related