Wednesday, December 10, 2014

I Came Here for a Good Argument

I was inspired this morning by another post at /r/javahelp.  A guy taking a programming class got a note from his teacher indicating the teacher wasn't happy with a hard coded path to a data file.  I am not saying I think the professor's tone was helpful, but I do see the point of that.

So how do we get around hard coded paths in main?

I think you can probably guess by now that if you have a method that opens a file for reading you don't have to embed the file name into the method itself.  It's simple enough to create a String parameter to your file and open up whatever is passed in:

    void readFile(String fileToRead) {
        File forReading = new File(fileToRead);

Well we can do the same sort of thing in main, too.  Let's go back to how main is defined:

    static void main(String[] args) {

I haven't mentioned that args parameter before, but it can come in really handy.  That's where your command line arguments can be found.

By the way, you will see the term arguments and the term parameters applied to method calls time and again.  They're the same thing, so don't get too hung up on which one is which.

When you run a java program, you can do all sorts of things beyond just calling the main method.  I won't discuss JVM Parameters for now, but you can tell java itself to modify its behavior, request extra memory, etc.  More importantly for us right now, you can give your program additional information.  For now I'll discuss running from a command line prompt, but the same concepts apply if you're running inside an IDE.  You just get to them a bit differently.

 Let's write a small program:

    public class CommandLineDemo {
        public static void main(String [] args) {
            for(int i = 0; i < args.length; i++) {
                System.out.println(args[i]);
            }
        }
    }


See?  I told you it was small.  But it does something which will prove useful once you've absorbed this concept.

If we just run the program it looks like it's doing nothing:

    $java CommandLineDemo
    $

But if we provide arguments things change

    $java CommandLineDemo Doe a deer
    Doe
    a
    deer
    $

Well now, that changes things, doesn't it?  Many programs become a lot more useful if you can tell them exactly what you want done.  Let's face it, this workflow:

1) Update file name in program
2) Compile program
3) Run program

Is rather less efficient than this workflow:

1) Run program with file name as an argument

Of course, file names just scratch the surface of this new capability.  You can analyze your command line for multiple values.  You can pass command line switches that control your program's behavior.  If you need numbers instead of String values, never fear, because it's always possible to convert Strings to numbers via readily available parsing methods.  In short, command line arguments give you and your users a great deal of flexibility that would be at the least inconvenient if they weren't available.  Sure, you could force a user of a program that searches for text in a file to update another file containing the file name and the value to search...  But that's when they go looking for someone else's program to use.

So let's combine the two bits of code we had above into one.  Note that because args is an array, we are free to deal with MANY input file names instead of just one as in the original hard coded demonstration:

    public class CommandLineFileReader {

        public static void main(String [] args) {
            for(int i = 0; i < args.length; i++) {
                readFile(args[i]);
            }
        }
   
        public static void readFile(String fileName) {
        ...
        }
}

Of course, in a real world program you'd probably want to do some exception handling, as outlined in a recent post.  That might be embedded within readFile, or maybe main might look more like this:

        public static void main(String [] args) {
            int successCount = 0;
            int failCount = 0;
            for(int i = 0; i < args.length; i++) {
                try {
                    readFile(args[i]);
                    successCount ++;
                    System.our.println("I was able to read " + args[i]);
                }
                catch(IOException e) {
                    failCount ++;
                    System.out.println("Unable to read " + args[i] + ": " + e);
            }
            System.out.println("Successfully read " + successCount + " files.");
            System.out.println("Failed to read " + failCount + " files.");
        }

Note that because I catch this exception inside of the for loop, I can notify the user that a file failed and keep track of how many have done so but continue on to process the rest.  It should take something much more catastrophic than a misspelling to break your system.

I hope this makes some of the capabilities of command line arguments clear, with a side order of more robust programming practices.  As always, comments and questions are welcome.