1

So my IDE is complaining if I don't enclose the Scanner in a try with block, but if I do it this way instead of closing it when it's supposed to close (once win = true), it closes the System.in stream, how do I prevent that?

public final void turn() {
    System.out.println("Enter your next move!");
    try (Scanner keyboard = new Scanner(System.in)) {
        final String move = keyboard.nextLine();
        if (move.isEmpty()) {
            won = true;
            return;
        }
        if (!validateFormat(move)) {
            System.out.println("Invalid format, try again.");
            return;
        }
        String[] moveAr;
        try {
            moveAr = move.split(",");
        } catch (PatternSyntaxException e) {
            System.out.println(e.getMessage());
            return;
        }
        try {
            validFields(moveAr);
        } catch (InvalidTurnException e) {
            System.out.println(e.getMessage());
            return;
        }
        final char colour = spielFeld.getField(getColumn(moveAr[0].charAt(0)),Character.getNumericValue(moveAr[0].charAt(1)) - 1).getColour();
        for (final String string : moveAr) {
            final int line = Character.getNumericValue(string.charAt(1)) - 1;
            final int column = getColumn(string.charAt(0));
            spielFeld.cross(column,line);
            final int columni = getColumn(string.charAt(0));
            if (spielFeld.columnCrossed(columni)) {
                points += crossedValues(string.charAt(0));
            }
        }
        if (spielFeld.colourComplete(colour)) {
            points += COLOUR_POINTS;
            coloursCrossed++;
        }
        if (coloursCrossed >= 2) {
            won = true;
        }
    }
    System.out.println("Momentane Punkte: " + points);
}
a.deshpande012
  • 715
  • 7
  • 18
  • 1
    Does this answer your question? [Close Scanner without closing System.in](https://stackoverflow.com/questions/14962082/close-scanner-without-closing-system-in) – Reizo Dec 11 '20 at 20:09
  • I saw that in my google search, but figured maybe there would be a better solution? Surely making your own class for what I'd assume is a common problem can't be the only solution. –  Dec 11 '20 at 20:11
  • I agree that this is not ideal, but it appears to be the only solution (considering you want to cleanly close your resources). You can of course keep your code cleaner by moving the close-preventing wrapper class to a separate file or use a library solution as done [here](https://stackoverflow.com/a/14143048/7353216). Imo its a design flaw in JDK, that `close`'ing a stream generally calls `close`on its wrapped stream. – Reizo Dec 11 '20 at 20:17
  • 1
    @Reizo *"considering you want to cleanly close your resources"*. Except that `System.in` doesn't belong to you, so you shouldn't "cleanly close" it, not even if you wrap it in something, like a `Scanner`. – Andreas Dec 11 '20 at 20:27
  • @Reizo The fact that wrapping streams close the underlying stream is certainly not a design flaw. If they didn't do that, you couldn't created chained streams like `new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))))`, manually like that or with the convenience helpers provided by some of those classes. `close()` must call through in all cases, except when wrapping `System.in`. Let that be the exception, not all the gazillion other times wrappers are used. – Andreas Dec 11 '20 at 20:28
  • @Reizo Why is that a flaw in Java? If closing a stream didn't close the underlying resources, it would be functionally equivalent to letting it go out of scope and getting garbage collected. The whole point of closing streams is to free up resources by freeing the underling IO streams. (whether that be to the console, the disk, the internet, etc.) As @Andreas said, you shouldn't close resources you don't own, which means you shouldn't close `System.in`. If you could somehow "close" `Scanner` without closing `System.in` you might as well no-op. – a.deshpande012 Dec 11 '20 at 20:29
  • @Andreas Indeed, I was referring to the `Scanner` to be cleanly `close`d. Closing the `System.in` is causing the exact problem, we want to prevent here. – Reizo Dec 11 '20 at 20:32
  • I think closing the wrapped stream would be valid, if there is a clear ownership relation of the wrapping stream to the wrapped one. But since there is no such thing as "ownership" in Java, the act of closing the wrapped stream appears to me more like a side-effect. I agree that this is in fact more _practical_ in the vast majority of situations. But the _idealistic_ approach would be to explicitly `close` resources which I explicitly `new`ed. – Reizo Dec 11 '20 at 20:40
  • And since we have try-with-resources, it would be particularly easy to declare all the opened resources in the resource specifier, to make them all be closed individually but automatically in reverse order. – Reizo Dec 11 '20 at 20:43
  • @Reizo Try-with-resources was invented 15 *years* after streams were, so the design that you called flawed couldn't simply use that non-existing feature. --- Usually, only the original stream allocates a resource, so e.g. in the 4 stream chain I showed earlier, only the `FileOutputStream` is a resource that needs to be closed. The other 3 "wrapping" streams are not themselves resources, but they do (because of the way it's *designed*) take over "ownership" of the underlying resource, so that you only need to close the outermost one. – Andreas Dec 11 '20 at 20:55

2 Answers2

0

how do I prevent that?

Don't close the Scanner object, i.e. don't use try-with-resources on a Scanner that is wrapping System.in.

Instead, accept the warning and hide it, since it's a special exception to the normal "resource" rules:

@SuppressWarnings("resource")
Scanner keyboard = new Scanner(System.in);

FYI: I'm using Eclipse IDE, and it "Surround with try-with-resources" is only the first option for fixing the warning:

enter image description here

Andreas
  • 154,647
  • 11
  • 152
  • 247
0

I would recommend against having multiple Scanner objects wrapping around the same input stream. (in this case, System.in) The reason for this is because Scanners may consume and buffer data from the underlying stream. This means that in some cases data can be lost. You can read more about it in this question.

You might be able to get away with it here, in which case you should just not close the Scanner object by not wrapping it in a try-with-resources. In that case, you can suppress the warning with @SuppressWarnings("resource"). However, this is bad practice.

Instead, I'd recommend creating a single global Scanner object that wraps around System.in, and then passing it to this method as a parameter instead of creating a new scanner in each method that requires input.

a.deshpande012
  • 715
  • 7
  • 18
  • The Scanner is supposed to receive a new Stream each cycle, so I don't think making it global would work right, when would it ask for input then? –  Dec 11 '20 at 20:45
  • @NinjaNinjaNinja you can ask for input whenever you'd like. Your code would look basically the same, but instead of creating a new Scanner object, you'd be accepting it as a parameter. If you create a new Scanner each cycle, it's still wrapping the same underlying input stream. By creating a global scanner object you only wrap around it once, and you can get data from `System.in` whenever you want. – a.deshpande012 Dec 11 '20 at 20:52