1

I have a console application with a main method and some methods that I call from main. There, I want to ask the user for some input, for which I use the Scanner class.

Here's my problem:

I find there is no way to use Scanner when reading inputs from outside main without random exceptions or unexpected behaviour. I have tried two approaches:

  1. Having a Scanner global variable in the class containing main. Then I use this same Scanner in all functions in that same class.
  2. In every function I need to ask for input, I declare a new Scanner variable, use it, and close it before exiting the function.

1. makes Scanner try to read twice. I mean, I have a sc.readLine in a function and, when I exit that function, I have another sc.readLine in main. I input once and the two readLine lines get executed, the second one reading an empty String.

2. throws Exception (base class Exception) when I call any sc.readLine for a second time during the execution of the program.

I have also noticed that any other method other than readLine is going to read various items on the same line. For example, line "10 20 30 40" would execute 4 sc.nextInt calls.

TL;DR: how do you use Scanner in a console application?

Arvind Kumar Avinash
  • 50,121
  • 5
  • 26
  • 72
CR0N0S.LXIII
  • 353
  • 1
  • 11
  • Java doesn't support the concept of global variables. basically,you're just looking about how to provide input by console? – Stultuske Feb 28 '20 at 15:06
  • Use way 2. If you use it correctly and there is valid input to read, there won’t be an exception. – Konrad Rudolph Feb 28 '20 at 15:06
  • I'm using number 2 and I'm getting a NoSuchElementException. I can provide some basic code if you want – CR0N0S.LXIII Feb 28 '20 at 15:08
  • 1
    @Konrad Rudolph 2 would not work if you close the `Scanner`, as he specifically mentions asking for user input, meaning he is using `System.in` to open the `Scanner`. If you close the `Scanner` that is on `System.in` you will be unable to open `System.in` again and the code will fail when you try to create and use another `Scanner`. You will have to _not_ close the `Scanner` irregardless of the warning if it uses `System.in`. – Nexevis Feb 28 '20 at 15:15
  • "I declare a new Scanner variable, use it, and close it before exiting function" closing Scanner will also close resource from which it reads its data, so if you have function like `void foo(){Scanner sc = new Scanner(System.in); /*do some stuff with scanner*/ sc.close()}` then after `foo` will be called first time it will close `System.in` which will prevent you from reading from it again (like when you would like to use `foo()` again). Instead create one scanner and *pass it as parameter* like `void foo(Scanner sc){/*use scanner*/}` and use it with already existing scanner like `foo(scan);`. – Pshemo Feb 28 '20 at 15:16
  • @Pshemo You don't need to pass it as a parameter, depending on the functionality it is completely valid to open the `Scanner` from within the method, you just need to not close it if it uses `System.in`. – Nexevis Feb 28 '20 at 15:17
  • Ok, so not closing Scanner when using 2 or declaring Scanner in Main and passing it as parameter. Thank you very much. You can provide the answer so I accept it if you want – CR0N0S.LXIII Feb 28 '20 at 15:18
  • @Nexevis I mean, yeah, obviously don’t `close` a scanner attached to the standard input stream. – Konrad Rudolph Feb 28 '20 at 15:18
  • @Nexevis Well you are right, we don't *need* to pass it as parameter, but creating new Scanner each time method is called feels like wasting resource (memory and time). Passing already existing scanner as parameter feels more natural to me. But that is just my opinion. – Pshemo Feb 28 '20 at 15:20
  • @KonradRudolph OK, thanks for pointing that out. – Pshemo Feb 28 '20 at 15:35
  • Beware of another problem with having multiple Scanners which handle same resource which is: they read entire (or some big chunk - need to check) data available in resource and cache it. For instance when we have `int getNumber(){return new Scanner(System.in).nextInt();}` and we ask user to provide N numbers like `print("give me 4 numbers);for (/*N times/*){ list.add(getNumber()); }` but user will provide input in form `1 2 3 4(enter)` then Scanner from first call of `getNumber()` will consume all available data leaving nothing for other scanners created in next calls of `getNumber()`. – Pshemo Feb 28 '20 at 15:38
  • @Pshemo Actually I double-checked the implementation, and hence removed my comment: the `Scanner` class is *horrifically* wasteful. It *should* be cheap to construct, but it simply isn’t. Your instinct was right. Most of this is a quality-of-implementation issue: it could be fixed. – Konrad Rudolph Feb 28 '20 at 15:38

3 Answers3

1

One way:

import java.util.Scanner;

public class Main {
    private Scanner scanner = new Scanner(System.in);

    public Scanner getScanner() {
        return scanner;
    }

    void fun1() {
        Scanner in = getScanner();
        System.out.print("Enter a string: ");
        System.out.println("You entered: " + in.nextLine());
    }

    void fun2() {
        Scanner in = getScanner();
        System.out.print("Enter an integer: ");
        int n = 0;
        try {
            n = Integer.parseInt(in.nextLine());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println(n + " + 10 = " + (n + 10));
    }

    public static void main(String[] args) {
        Main m = new Main();
        m.fun1();
        m.fun2();
    }
}

A sample run:

Enter a string: Hello world!
You entered: Hello world!
Enter an integer: 25
25 + 10 = 35

Another way:

import java.util.Scanner;

public class Main {
    static void fun1(Scanner in) {
        System.out.print("Enter a string: ");
        System.out.println("You entered: " + in.nextLine());
    }

    static void fun2(Scanner in) {
        System.out.print("Enter an integer: ");
        int n = 0;
        try {
            n = Integer.parseInt(in.nextLine());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        System.out.println(n + " + 10 = " + (n + 10));
    }

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

A sample run:

Enter a string: Hello world!
You entered: Hello world!
Enter an integer: 25
25 + 10 = 35

Regarding your problem with next() or nextInt(): Given below is the recommended way for multiple inputs in one go.

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        boolean valid = true;
        System.out.print("Enter some intgers: ");
        String strNum = in.nextLine();
        String[] strNumArr = strNum.split("\\s+");
        int[] numArr = new int[strNumArr.length];
        for (int i = 0; i < strNumArr.length; i++) {
            try {
                numArr[i] = Integer.parseInt(strNumArr[i]);
            } catch (NumberFormatException e) {
                numArr[i] = Integer.MIN_VALUE;
                valid = false;
            }
        }
        System.out.println(Arrays.toString(numArr));
        if (!valid) {
            System.out.println("Note: invalid inputs have been reset to " + Integer.MIN_VALUE);
        }
    }
}

A sample run:

Enter some intgers: 10 5 20 15
[10, 5, 20, 15]

Another sample run:

Enter some intgers: 4     40 a    20   b   15
[4, 40, -2147483648, 20, -2147483648, 15]
Note: invalid inputs have been reset to -2147483648

Check Scanner is skipping nextLine() after using next() or nextFoo()? for more information about console input using Scanner.

Arvind Kumar Avinash
  • 50,121
  • 5
  • 26
  • 72
0

to use a single instance of Scanner (or any other datatype or class), you can use the design pattern "Singleton" which consist to instantiate a single instance of your Object for the whole project.

The Singleton definition (a new class) :

import java.util.Scanner;

public class ScannerSingleton {
    private static Scanner sc = null;

    private ScannerSingleton() {
        sc = new Scanner(System.in);

    }

    public static Scanner getInstance() {
        if (sc == null) {
            synchronized(ScannerSingleton.class) {
                if (sc == null)
                    sc = new ScannerSingleton();
            }
        }

    }
}

and each time you want to use your scanner anywhere you only have to call ScannerSingleton.getInstance() which will return your single instance of scanner

example of use:

String test = ScannerSingleton.getInstance().nextLine();
Bashir
  • 1,964
  • 5
  • 15
  • 34
  • 1
    Beware that your “singleton” isn’t thread safe, and it feels conceptually iffy (besides the general problems with singletons): you *don’t* want a single scanner. You want a single *standard input scanner*. Yes, that’s what your code does, but the class name reflects this poorly. – Konrad Rudolph Feb 28 '20 at 15:26
  • how it isn't safe? – Bashir Feb 28 '20 at 15:30
  • 1
    If two threads call `getInstance` simultaneously, `sc` potentially gets initialised twice, and you’ve therefore created two `ScannerSingleton`s, and two `Scanner`s over `System.in`. – Konrad Rudolph Feb 28 '20 at 15:33
  • I understand, after editing my answer, you won't have this problem which statistically, its risk is low, since it can only happen when the Singleton is called for the first time. – Bashir Feb 28 '20 at 15:42
  • 1
    A Microsoft engineer famously noted that “‘one in a million’ is next Tuesday”. Meaning, when code is run frequently, even occurrences with a *very* small chance are statistically guaranteed to occur in the long run. **Never** trust low chances (unless you can exactly quantify them, as in the case of GUID collisions, which are indeed so rare that they will *never* happen). – Konrad Rudolph Feb 28 '20 at 15:45
  • that's right, I assume that now we have no risk, I mean after the edit of the answer, no? since the risk can happens only on the first call, we synchronize after testing if it is null(only on the first call will be null), so that we don't synchronize on each call. – Bashir Feb 28 '20 at 16:05
  • Your initial edit fixed the problem (though it made calling it expensive). But your second edit *reintroduced the problem!* You need to employ double-checked synchronisation; in other words, inside your `synchronized` block you need to perform the null check *again*. – Konrad Rudolph Feb 28 '20 at 16:07
  • I re-edit it to avoid the coast of the synchronization at each time. we don't need to null check again, since the risk may happens only on the first call. So only at the first call all the threads will find it null, so at this time we synchronize. but in later calls we won't have any problem, since it will never be null – Bashir Feb 28 '20 at 16:12
  • well well, you're right, I have to double check .. thanks for this interesting discussion – Bashir Feb 28 '20 at 16:15
0

I suggest to pass your Scanner object as another parameter for your functions.

public static void main(String[] args)
{
  Scanner scanner=new Scanner(System.in);
  String answer = ask(scanner, "what is your name?");
  System.out.println("Oh! Hello dear " + answer + "!");
  scanner.close();
}

private static String ask(Scanner scanner, String question)
{
  System.out.println(question);
  return scanner.nextLine();
}
lue
  • 399
  • 4
  • 16