Search code examples
javaintellij-idea

never ending loop for user populated arraylist


I need to write a method to take user input car specs and add them into an ArrayList. This needs to accept any amount of specs the user wants to input, including none. This is my first post here, so apologies for any poor syntax.

public static ArrayList getTrim() {

Scanner t = new Scanner(System.in);
System.out.println("Enter car trim");
ArrayList<String> trim = new ArrayList<>();

while (t.hasNext()) {
trim.add(t.next());
}

return trim;

}

I thought this condition would return false if whitespace was entered. This continues iterating and can only be exited manually.

I also tried

if (t.hasNext()) {
trim.add(t.next());
}

else {
t.close();
}

This iterates once and then returns the ArrayList, but I need to be able to input more. Changing my if or while condition to hasNextLine() gives the same results stated, here I used hasNext() because car trim levels have certain format expectations. I do not understand why hasNext() will not return as false when no input is given.


Solution

  • Thank you! I understand why this works, but I suppose the root of my question is how hasNext() is operating in my loop if it does not detect when there is no input.

    It.. doesn't.

    Think about it. How can the computer tell the difference between a user who is thinking about what to type and perhaps leaving it for after lunch, and a user who is 'done'? Fire up the webcam and do some AI analysis of whether the user looks like they feel they are done with typing?

    System.in is not the keyboard. It's the 'standard input for the JVM process' which is by default, at least, if you fire up a java app from the command line, set up to read from the keyboard. It does not have to be:

    java -jar myapp.jar </some/path/to/some/file.txt
    

    Now System.in reads from that file. There is no way to read from the keyboard for that process.

    java -jar myapp.jar </dev/barcodeScanner1
    

    Now presumably anytime you scan a barcode, the java app receives that as if you typed the numbers of that barcode in and hit 'enter'1.

    It is up to the 'source of this input' to 'end'. A file ends when you get to the end of the file.

    A keyboard cannot possibly 'end' - therefore, .hasNext() cannot possibly return false.

    Whitespace

    You talk in your comments about 'entering whitespace'.

    Then you don't understand Scanner. You're in good company; it's probably the most misunderstood thing. Certifiably so if we go by the sheer amount of questions StackOverflow is inundated with.

    Scanner has nothing to do with keyboards.

    Scanner just takes any source of textual input and splits it up into chunks. A chunk is called a 'token', and a 'token' is defined by 'all the text that is in between delimiters'. Delimiters are in turn defined by a regular expression, and by default the regex used is \s+. As in, any amount of whitespace.

    Hence:

    public static void main(String[] args) {
      Scanner s = new Scanner(System.in);
      while (true) {
        System.out.println("Token: ≥" + s.next()) + "≤");
      }
    }
    

    If you ran that, and entered on the command line, say: hello world!, then pressed enter, and typed my name is Lajos, you would see:

    Token: ≥hello≤
    Token: ≥world!≤
    Token: ≥my≤
    Token: ≥name≤
    Token: ≥is≤
    Token: ≥Lajos≤
    

    The thing about scanner: It is not possible to register or otherwise get a bead on what the content of anything that falls under 'delimiter' is. You can't ask a scanner: How much whitespace was there?

    You also can't ask: Please 'stop' when you encounter whitespace. No, whitespace simply separates one token from the next. There is, to a scanner, no difference at all between hitting enter, and hitting space and then enter. That's both '1 or more whitespace characters', so, interchangible, and as this is about the delimiter, undetectable.

    You probably don't want to use Scanner at all. System.in itself certainly can differentiate it.

    Solution 1

    The usual strategy is to mention in a prompt some magic word that indicates 'done'. For example:

    static void main(String[] args) throws Exception {
      var s = new Scanner(System.in);
      System.out.println("Welcome to the fruit stand! Enter the fruit you'd like to buy, one at a time:");
    
      var basket = new ArrayList<String>();
      while (true) {
        System.out.print("Fruit (type 'DONE' when done): ");
        String fruit = s.next();
        if (fruit.equalsIgnoreCase("DONE")) break;
        basket.add(fruit);
      }
    
      System.out.println("Here's your basket: " + basket);
    }
    

    Solution 2

    Ditch scanner. Or at least, ditch all it is about and use only nextLine, which interacts very badly with all the other methods it has - choose one and only one (nextLine, or everything but nextLine):

    static void main(String[] args) throws Exception {
      var s = new Scanner(System.in);
      System.out.println("Welcome to the fruit stand! Enter the fruit you'd like to buy, one at a time:");
    
      var basket = new ArrayList<String>();
      while (true) {
        System.out.print("Fruit (enter when done): ");
        String fruit = s.nextLine();
        if (fruit.isEmpty()) break;
        basket.add(fruit);
      }
    
      System.out.println("Here's your basket: " + basket);
    }
    

    [1] In practice most barcode scanners look and act like keyboards, and don't have a device in the sense that it does not exist in /dev / cannot be piped into a process like this. But, as an example, it works.