How about a readln for numbers
Kenneth Fogel
kfogel at dawsoncollege.qc.ca
Thu Nov 7 19:16:40 UTC 2024
The readln method introduced as part java.base is great because it includes the prompt for the input. The only shortcoming is that if the input must be a number then you still need to employ a static class member such as Double.parseDouble() to make it a number:
// Currently
var loan = Double.parseDouble(readln("Loan: "));
// My delusional idea
var loan = readDbl("Loan: ");
I can think of reasons why this could be a bad idea. I taught my students to use a construct using Scanner to create a console input that passed my Beethoven test (while humming Beethoven’s 5th symphony randomly strike keys on your keyboard as if you were playing the symphony and your code should just report invalid input and not an exception). If we had a readInt and/or readDbl they would not pass this test. That Python would happily accept anything and then fail when the value was used in a calculation is no better. But, for learning Java could we have a readInt or readDbl alongside readln? I know that DataInputStream has such methods, but without a prompt and it must be attached to an input stream such as a file.
As always, just thinking out loud. Feel free to use my name in vain.
Ken
This message and a back and forth with Ethan McCue began in the OpenJDK Discus list and we were asked to move it here. Here is the summary of messages in the order they were sent to the list. The message above is the last one.
void main() {
int number;
do {
String input = readln("Enter a number between 1 and 10: ");
try {
number = Integer.parseInt(input);
if (number < 1 || number > 10) {
println("Number out of range");
}
// All is well
break;
} catch (NumberFormatException _) {
println("You have not entered a number");
}
} while (true);
println(number);
}
For color on how that might affect the pedagogy:
"First we need to get input from the user, this is a String which represents text"
void main() {
String input = readln("Enter a number between 1 and 10: ");
}
"We can turn a String into an int by writing Integer.parseInt(<text here>). If what they wrote isn't a number it will crash"
void main() {
String input = readln("Enter a number between 1 and 10: ");
int number = Integer.parseInt(input);
}
"To handle crashes we can put the code that might crash inside of try { }, then write the way it might crash in a catch clause"
void main() {
String input = readln("Enter a number between 1 and 10: ");
try {
int number = Integer.parseInt(input);
println("You gave: " + number);
} catch (NumberFormatException e) {
println("Bad number: " + input);
}
}
"If they gave a number that wasn't between 1 and 10 ..."
void main() {
String input = readln("Enter a number between 1 and 10: ");
try {
int number = Integer.parseInt(input);
if (number < 1 || number > 10) {
println("Number is not between 1 and 10: " + number);
}
else {
println("You gave: " + number);
}
} catch (NumberFormatException e) {
println("Bad number: " + input);
}
}
"Now we want to keep asking them until they give a valid input. To do this, first delay the assignment of number"
void main() {
String input = readln("Enter a number between 1 and 10: ");
int number;
try {
number = Integer.parseInt(input);
if (number < 1 || number > 10) {
println("Number is not between 1 and 10: " + number);
}
else {
println("You gave: " + number);
}
} catch (NumberFormatException e) {
println("Bad number: " + input);
}
}
... and so on until you reach
void main() {
String input = readln("Enter a number between 1 and 10: ");
int number;
do {
try {
number = Integer.parseInt(input);
if (number < 1 || number > 10) {
println("Number is not between 1 and 10: " + number);
}
else {
break;
}
} catch (NumberFormatException e) {
println("Bad number: " + input);
}
} while (true);
println("You gave: " + number);
}
On Thu, Nov 7, 2024 at 12:50 PM Ethan McCue <ethan at mccue.dev<mailto:ethan at mccue.dev>> wrote:
So with all that context, do you see why accepting Double.parseDouble(readln()); might be worth it?
"Paving the onramp" shouldn't mean only "paving the first 48 hours." The convenience afforded by a dedicated readDouble, at least to me, feels outweighed
by
* The loss of a perfectly good opportunity to explain the fundamentals of parsing (strings represent text, you can interpret that text by....)
* The loss of a perfectly good opportunity to teach basic exception handling
* The divergence with the behavior of Scanner
* The combinatorial explosion of "why not readByte?"
* The privileged position it puts primitives in as the end-point of interpreting user input
* Guns, Feet
(I've separately voiced my concerns about readln in this regard.)
On Thu, Nov 7, 2024 at 12:40 PM Kenneth Fogel <kfogel at dawsoncollege.qc.ca<mailto:kfogel at dawsoncollege.qc.ca>> wrote:
Here is an example of a Beethoven test passing routine that expects a number for 1 to 10. The Scanner, sc, has already been initialized. I also taught how to use regular expressions to fine tune what is acceptable input and the acceptable range such that you only need to use readLine so that subsequent input is not messed up when a user enters 23 45 instead of 23.45. Notice that this routine cleans out the buffer with a nextLine at the end.
int number;
do {
System.out.println("Enter a number between 1 and 10: ");
if (sc.hasNextInt()) { // Check that there is an integer in the keyboard buffer
number = sc.nextInt(); //
// Check if the number is in range
if (number < 1 || number > 10) {
System.out.println("Number out of range.");
}
} else { // There was not an integer in the keyboard buffer
number = -1; // a value that will keep execution in the loop
System.out.println("You have not entered a number");
}
sc.nextLine(); // Clean out the buffer
} while (number < 1 || number > 10);
This tells me that in the paving the onramp universe a readInt or readDbl must clear the keyboard buffer when it encounters a terminating character such as the space, tab, and \n. Already I can hear the roar over how this breaks the expected behaviour of Scanner such as allowing a list of primitives in the keyboard buffer.
Ken
From: Ethan McCue <ethan at mccue.dev<mailto:ethan at mccue.dev>>
Sent: November 7, 2024 11:43 AM
To: Kenneth Fogel <kfogel at dawsoncollege.qc.ca<mailto:kfogel at dawsoncollege.qc.ca>>
Cc: discuss at openjdk.org<mailto:discuss at openjdk.org>
Subject: Re: How about a readln for numbers?
Currently, "don't mix nextLine with next/next int/etc" is an extremely common footgun.
In one of the coding help discords, this is the auto message we send when people run into trouble with that.
Mixing any nextXXX method with nextLine from the Scanner class for user input, will not ask you for input again but instead result in an empty line read by nextLine. To prevent this, when reading user input, always only use nextLine. If you need an int, do
int value = Integer.parseInt(scanner.nextLine());
instead of using nextInt. Assume the following:
Scanner scanner = new Scanner(System.in);
System.out.println("Enter your age:");
int age = scanner.nextInt();
System.out.println("Enter your name:");
String name = scanner.nextLine();
System.out.println("Hello " + name + ", you are " + age + " years old");
When executing this code, you will be asked to enter an age, suppose you enter 20. However, the code will not ask you to actually input a name and the output will be:
Hello , you are 20 years old.
The reason why is that when you hit the enter button, your actual input is
20\n
and not just 20. A call to nextInt will now consume the 20 and leave the newline symbol \n in the internal input buffer of System.in. The call to nextLine will now not lead to a new input, since there is still unread input left in System.in. So it will read the \n, leading to an empty input. So every user input is not only a number, but a full line. As such, it makes much more sense to also use nextLine(), even if reading just an age. The corrected code which works as intended is:
Scanner scanner = new Scanner(System.in);
System.out.println("Enter your age:");
// Now nextLine, not nextInt anymore
int age = Integer.parseInt(scanner.nextLine());
System.out.println("Enter your name:");
String name = scanner.nextLine();
System.out.println("Hello " + name + ", you are " + age + " years old");
The nextXXX methods, such as nextInt can be useful when reading multi-input from a single line. For example when you enter 20 John in a single line.
On Thu, Nov 7, 2024, 11:36 AM Kenneth Fogel <kfogel at dawsoncollege.qc.ca<mailto:kfogel at dawsoncollege.qc.ca>> wrote:
The readln method introduced as part java.base is great because it includes the prompt for the input. The only shortcoming is that if the input must be a number then you still need to employ a static class member such as Double.parseDouble() to make it a number:
// Currently
var loan = Double.parseDouble(readln("Loan: "));
// My delusional idea
var loan = readDbl("Loan: ");
I can think of reasons why this could be a bad idea. I taught my students to use a construct using Scanner to create a console input that passed my Beethoven test (while humming Beethoven’s 5th symphony randomly strike keys on your keyboard as if you were playing the symphony and your code should just report invalid input and not an exception). If we had a readInt and/or readDbl they would not pass this test. That Python would happily accept anything and then fail when the value was used in a calculation is no better. But, for learning Java could we have a readInt or readDbl alongside readln? I know that DataInputStream has such methods, but without a prompt and it must be attached to an input stream such as a file.
As always, just thinking out loud. Feel free to use my name in vain.
Ken
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20241107/835d7c1c/attachment-0001.htm>
More information about the amber-dev
mailing list