Search code examples
javaio

Java: Using BufferedReader and PrintWriter in Conjunction Causes Issues


I'm trying to learn about Java's io package through experimentation and from the book "Java2: the Complete Reference (5th ed.)". Below, I have tried to make a very simple program to take some input using a BufferedReader instance and deliver some simple console output with PrintWriter instances.

package io;

import java.io.*;

public class UsingPrinterWriter {
    public static void main(String argv[]) {
        PrintWriter output = new PrintWriter(System.out, true);
        Profile hypro = Profile.promptedProfile();
        output.print(hypro);
        output.close();
        return;
    }
}

class Profile {
    private String first_name;
    private String last_name;
    private int age;

    Profile() {}

    Profile(String first_name, String last_name, int age) {
        this.first_name = first_name;
        this.last_name = last_name;
        this.age = age;
    }

    void setFirstName(String fn) {
        this.first_name = fn;
        return;
    }

    void setLastName(String ln) {
        this.last_name = ln;
        return;
    }

    void setAge(int a) {
        this.age = a;
        return;
    }

    static Profile promptedProfile() {
        Profile new_profile = new Profile();

        // create the writer and reader
        PrintWriter output = new PrintWriter(System.out, true);
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));

        try {
            // prompt for first name
            output.print("Enter your first name: ");
            new_profile.setFirstName(input.readLine());

            // prompt for last name
            output.print("Enter your last name: ");
            new_profile.setLastName(input.readLine());

            // prompt for age
            output.print("Enter your age: ");
            while(true) {
                try {
                    new_profile.setAge(Integer.parseInt(input.readLine()));
                    if(new_profile.age > 0 && new_profile.age < 110) {
                        break;
                    }
                    throw new NumberFormatException("Out of bounds.");
                } catch(NumberFormatException nfe) {
                    output.print("Invalid response, try again: ");
                }
            }
            input.close();          
        } catch (IOException ioe) {
            output.println(ioe);
        } finally {
            output.close();
        }

        return new_profile;
    }

    @Override
    public String toString() {
        return String.format("Profile:\n  Name: %s %s\n  Age: %d\n", this.first_name, this.last_name, this.age);
    }
}

Issue 1: when I run this (Using the Eclipse IDE) the output I get is a blank terminal, where, once I enter a string, another string, and a valid integer, all one after the other in newlines, (then)the prompt lines where I do output.print(...) get printed. Here's an example to show what I mean:

first
last
20
Enter your first name: Enter your last name: Enter your age:

Issue 2: The line: output.print(hypro); won't get printed. Anything after the line Profile hypro = Profile.promptedProfile();.

So I'd like to ask:

  1. what's going on here (the conceptual knowledge about these io classes that I seem to be missing) ?
  2. what can I do to resolve these issues?

In advance, thank you.

P.S. I'm already aware of the java.util.Scanner class but I want to solve this issue without using it. Also, I'm open to suggestions about any better programming practices that I should have implemented here.


Solution

  • Let's start with a few basics:

    First of all what does flushing of a stream means?, well, it simply means that when you flush a buffered stream, all the data from it is cleared off and sent to the target destination. In your code, flushing output would make sure that all the data in it is sent to the console (System.out) for printing.

    Secondly the stream System.out is a STATIC stream, that is you get only one copy of that, and on closing it you can not use it again for printing things.

    Issue 1:Now although you have enabled autoflushing (PrintWriter output = new PrintWriter(System.out, true); // the second argument is true), the autoflush is called only when \r\n (Carriage return line feed, CRLF) or a \n is encountered, and since your output statements miss those characters, none of the prompts get printed as the stream is not flushed at the correct times. The data keeps on appending to the stream until the end of the program when the autoflush finally runs for the first and the last time and, as a result, all the statements are printed in a line when the program terminates.

    To fix: As explained in a previous answer, you can either use println() instead of print() or you can add the missing characters to the print() call like:

    output.print(new_profile.getName() + "\r\n")

    Or you can manually call flush() after each print() call (edit suggested by @EJP) if you'd like to get the outputs in the same line 😊

    Issue 2: As I just said, the System.out stream is a static one and since you close the stream when you call promptedProfile(), you can not use it again in your UsePrinterWriter class' main method. You can try printing something else before the function call like this:

    output.println("foo bar"); //print something here Profile hypro = Profile.promptedProfile();

    You will see that foo bar is printed on the console.

    To fix: As in for fixing this issue, I'd say print everything in a single function and print everything before calling close() on a writer object that uses System.out, or you can simply use System.out.println(); to avoid all this altogether ;)