Rick

Rick
Rick

Saturday, October 19, 2013

Java Scanner example (JDK auto close, exception handling basics,

I answered another question on StackOverflow. I enjoy answering questions there. It is fun to pull in a code snipped, reproduce an error and fix it. 

The question was about getting a weird message about Scanner.

I have never really use Scanner before. It was added in JDK 1.5. I started programming in Java at JDK 1.0 / JDK 1.1 transition. I am amazed how much cool utilities are int the standard JDK.

Scanner was one that I confused with StringTokenizer.  I have used StringTokenizer back in the day, and as I remember it was a pig which is why I always avoided Scanner (figured it might be piggish), but I have now learned that it is not a pig (Scanner vs. StringTokenizer vs. String.Split). Apparently StringTokenizer is faster (unverified), but a bit piggish (resource intensive, unverified).

If you are from a C backgrond (I am), then Scanner is a bit like sscanf in C (fscanf). 

Scanner has precompiled Regex for common things like ints, floats, double, BigDecimal, etc. Precompiled Regex gives you some performance benefits. (Note to self, see if you can write a faster Scanner than Scanner). Even the Regex, you use (unlike String), it compiles and saves for future use:

LRU Cache Inside of Scanner

Java Scanner Example by Boon Java Developer

Here is the basic structure of the Scanner.
UML Diagram of Java Scanner

Scanner is closeable, and has an iterator.

From the Java docs, Scanner can parse parse primitive types and strings using regular expressions. Scanner breaks its input into tokens using regex delimiter patterns (white text is the default, but you can specify others).


Basic operations:

   Scanner sc = new Scanner(System.in);
   int i = sc.nextInt();
 


You can also check types and have branching logic based on the type.


   Scanner sc = new Scanner(new File(".."));
   while (sc.hasNextLong()) {
            long aLong = sc.nextLong();
    }


From the JavaDocs:

       String input = "1 fish 2 fish red fish blue fish";     
       Scanner s = new Scanner(input).useDelimiter("\\s*fish\\s*");
       System.out.println(s.nextInt());
       System.out.println(s.nextInt());
       System.out.println(s.next());
       System.out.println(s.next());
       s.close();

Output

       1
       2
       red
       blue

Scanner also supports localized scanning as in 10.000,00 instead of 10,000.00 for countries who do their decimals the wrong way. :)

Scanner is a very good example on how to use the Java Regex API.

Java Scanner Example from Boon Java Developer
Scanner has good examples of Java Regex






The above shows an example of parsing lines separators using Regex.



There are always two method for an operation, does it have it, and parse if for me. Let's look at those. First Does it have it:


Java Scanner Example from Boon Java Developer
hasNextLine form Java's Scanner class



"IT" is whatever you are looking for. Lines, Ints, Floats, etc. Is the next thing a "blank"? You can even use your own patterns, and Scanner will cache them for you. Once we know the next thing is a line, we can parries it.


Java Scanner Example from Boon Java Developer
nextLine() from Scanner class




Now that I know it has it, give it to me. (Where it is whatever you want?)


All of the basic types are covered (the reads byte):




Java Scanner Example from Boon Java Developer
Next Line, Next Byte, Next Int, Next BigDecimal, It is all there....


There is nextInt, nextBigDecimal, nextInteger, etc.



If you are new to programming, there are some great examples of defensive programming in the Java API and Java Scanner is not exception. 

Here is a good example of defensive programming in Java Scanner:

Java Scanner Example from Java Boon Developer showing defensive programming


Note the ensureOpen() method. 


Ok... Back to the question at hand. This comes up a lot on StackOverFlow. The basic answer was you can't close the underlying resource while Scanner is doing it things.  The question/problem about "NoSuchElementException with Java.Util.Scanner" shows up 52 times on StackOverflow. 

The two most common causes seem to be that the pattern was not found, and / or the underlying resource was closed. 

The question was as follows:

"When I run this program I get NoSuchElementFoundException when second time s.nextInt() is executed. I tried out every possible methods but with no result. What is the problem over here?"

The example he gave (NOT MY CODE) was:

NOT MY CODE... NOT MY CODE class Main{
    static Employee getData() throws IOException {
        BufferedReader rdr = new BufferedReader(
                            new InputStreamReader(
                            new DataInputStream(System.in)
                            ));
        System.out.printf("Enter Employee ID : ");
        int tmpid = Integer.parseInt(rdr.readLine());
        System.out.printf("Enter Employee Name : ");
        String tmpname = rdr.readLine();
        System.out.printf("Enter Employee Salary : ");
        int tmpsalary = Integer.parseInt(rdr.readLine());
        rdr.close();
        return new Employee(tmpid, tmpname, tmpsalary);//NOT MY CODE
    }   
    public static void main(String []args){ //NOT MY CODE
        boolean b = false;  
        String path = null;
        Scanner s = new Scanner(System.in);
//NOT MY CODE
        File file = null;
        try {
            System.out.printf("Enter path to save your file : ");
            path = s.next();
            file = new File(path);
            if (!(file.createNewFile()))
                System.out.println("Error creating file");
        } catch (Exception ie) {
            System.err.println("Exception : " + ie);
        }       


        do{
            try {
                Employee rec = Main.getData();
                ObjectOutputStream dos = new ObjectOutputStream(new FileOutputStream(file));
                dos.writeObject(rec);
                dos.close();
                System.out.printf("Add more records [true/false]? ");
                s = new Scanner(System.in);
                    int tmp = s.nextInt();
            } catch (Exception ioe) {
                System.err.println("Exception : " + ioe);
            } //NOT MY CODE

        }while(b);
    }
}
He writes: "When I run this program I get NoSuchElementFoundException when second time s.nextInt() is executed. I tried out every possible methods but with no result. What is the problem over here?"

Here is my first pass at an answer:

Never catch an exception unless you are going to do something useful with it.
I got it to work. It was fairly straight forward.
Enter path to save your file : myfile.bin
Enter Employee ID : 99
Enter Employee Name : Rick Hightower
Enter Employee Salary : 99
Add more records [true/false]? true
Enter Employee ID : 77
Enter Employee Name : Dippy Do
Enter Employee Salary : 88
Add more records [true/false]? false
Here is what I have for a first pass at fixing the issues:
public static class Employee implements  Serializable {

    int id;
    String name;
    int salary;

    public Employee(int id, String name, int salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

}

static Employee getData() throws IOException {
    BufferedReader rdr = new BufferedReader(
            new InputStreamReader(
                    new DataInputStream(System.in)
            ));
    System.out.printf("Enter Employee ID : ");
    int tmpid = Integer.parseInt(rdr.readLine());
    System.out.printf("Enter Employee Name : ");
    String tmpname = rdr.readLine();
    System.out.printf("Enter Employee Salary : ");
    int tmpsalary = Integer.parseInt(rdr.readLine());
    //rdr.close(); this is why... you broke it :)
    return new Employee(tmpid, tmpname, tmpsalary);
}

public static void main(String []args) throws Exception {
    boolean moreRecords = true;
    String path = null;
    Scanner scanner = new Scanner(System.in);
    File file = null;
    System.out.printf("Enter path to save your file : ");
    path = scanner.next();
    file = new File(path);


    while (moreRecords) {
        Employee rec = Main.getData();
        ObjectOutputStream dos = new ObjectOutputStream(new FileOutputStream(file));
        dos.writeObject(rec);
        dos.close();
        System.out.printf("Add more records [true/false]? ");
        moreRecords = scanner.nextBoolean();
    }

Then I took another stab at it.....

The problems are mostly in your code. Here is your code with some parts taken away.
The biggest issue you had was you were closing the input stream.
static Employee getData() throws IOException {
    BufferedReader rdr = new BufferedReader(
            new InputStreamReader(
                    new DataInputStream(System.in)
            ));
    System.out.printf("Enter Employee ID : ");
    int tmpid = Integer.parseInt(rdr.readLine());
    System.out.printf("Enter Employee Name : ");
    String tmpname = rdr.readLine();
    System.out.printf("Enter Employee Salary : ");
    int tmpsalary = Integer.parseInt(rdr.readLine());
    //rdr.close(); this is why... you broke it :)       <-------------------SEE
    return new Employee(tmpid, tmpname, tmpsalary);
}
The Java I/O stream uses the decorator pattern so it just keeps delegating the close call into the inner streams. You closed it in getData
That fixes that problem. There are lots of problems with your code.
(I need to work on my manners a bit. :( )




If you are using JDK 1.7 or later, it will close the file for you.
    while (moreRecords) {
        Employee rec = Main.getData();

        try ( ObjectOutputStream dos =
                     new ObjectOutputStream(
                             new FileOutputStream(file) ) ) {

            dos.writeObject(rec);

        }

        System.out.printf("Add more records [true/false]? ");
        moreRecords = scanner.nextBoolean();
    }
If you are using JDK 1.6 or JDK 1.5:
    while (moreRecords) {
        Employee rec = Main.getData();

        ObjectOutputStream dos = null;
        try {
            dos = new ObjectOutputStream(
                            new FileOutputStream(file) );
            dos.writeObject(rec);

        } finally {
           if ( dos!=null  ) {
                dos.close();
           }
        }

        System.out.printf("Add more records [true/false]? ");
        moreRecords = scanner.nextBoolean();
    }

The folks who wrote the Scanner went through all of this trouble to support validating the input before scanning (avoiding the exception before it happens). Use it. So I go on....


Also, your program should do more validation of user input. Scanner can do that as follows:

public static class Employee implements  Serializable {

    private int id;
    private String name;
    private BigDecimal salary;

    public Employee(int id, String name, BigDecimal salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

}

static Employee getData(Scanner scanner) throws IOException {

    System.out.printf("Enter Employee ID : ");
    while ( !scanner.hasNextInt() ) {
        System.out.println("Employee IDs are numbers only");
        scanner.next();
    }

    int employeeId = scanner.nextInt();

    System.out.printf("Enter Employee Name : ");
    String name = scanner.next();


    System.out.printf("Enter Employee Salary : ");

    while ( !scanner.hasNextBigDecimal() ) {
        System.out.println("Employee salaries are decimals " +
                "not random gak");
        scanner.next();
    }
    BigDecimal salary = scanner.nextBigDecimal();

    return new Employee(employeeId, name, salary);
}


public static void main(String []args) throws Exception {
    boolean moreRecords = true;
    String path = null;
    Scanner scanner = new Scanner(System.in);
    File file = null;
    System.out.printf("Enter path to save your file : ");
    path = scanner.next();
    file = new File(path);


    while (moreRecords) {
        Employee rec = Main.getData(scanner);

        try ( ObjectOutputStream dos =
                      new ObjectOutputStream(
                            new FileOutputStream(file) ) ) {
            dos.writeObject(rec);

        }

        System.out.printf("Add more records [true/false]? ");
        moreRecords = scanner.nextBoolean();

Now the input/output is more like this:

Enter path to save your file : asdfasdf
Enter Employee ID : 9a
Employee IDs are numbers only
99
Enter Employee Name : Rick
Enter Employee Salary : aa
Employee salaries are decimals not random gak
99.99
Add more records [true/false]? false
The scanner forces the end user to enter in the right types of data. You can combine it with regex to match patterns for names, etc.

HE WROTE:
"I didn't get the code you have written for the Java 7 version. Whats that new feature in try block introduced in Java 7?"
 I WROTE
I WROTE BACK: There is no finally block. You put the "closeable" (Closable interface) in the try parents try ( ObjectOutputStream dos = ... ) { Then you don't need a finally block. I am new to it as well. I often use the finally block. You have to do one or the other if you are dealing with files since FILE_DESCRIPTORS are a limited resource (10K to 300K and no more and it depends on the OS settings).–     

Ok here is the loop for asking a person for his information over and over until they give you the right type of answer.


System.out.printf("Enter Employee ID : ");
    while ( !scanner.hasNextInt() ) {
        System.out.println("Employee IDs are numbers only");
        scanner.next();
    }


If you don't do the above and the hasNextInt does not match then you get the dreaded "NoSuchElementFoundException", and a confused end user. :)

Conclusion:

I did not know much about Scanner before answering this question. Now I do. Happy to learn new tricks.



No comments:

Post a Comment

Kafka and Cassandra support, training for AWS EC2 Cassandra 3.0 Training