Module 3: Elementary Java, Part II


Methods (functions): a first look

 

Consider a simple example: (source file)


public class UniformRandom {

    // "Global" variable - the seed, set to some 
    // arbitrary non-zero value.
    static long r_seed = 12345678L; 

    //---- No need to understand the technical details between HERE -----

    static final long m = 2147483647L;
    static final long a = 48271L;
    static final long q = 44488L;
    static final long r = 3399L;

    // Basic Lehmer generator - uniform[0,1] For more information see Knuth, Vol. II.
    public static double uniform ()
    {
        long hi = r_seed / q;
        long lo = r_seed - q * hi;
        long t = a * lo - r * hi;
        if (t > 0) {
            r_seed = t;
        }
        else {
            r_seed = t + m;
        }
        return ( (double) r_seed / (double) m );
    }

    //---- and HERE -----------------------------------------------------

    public static void main (String[] argv)
    {
        // Let's test the generator. The mean should be 0.5.
        double sum = 0;
        for (int i=1; i<=10000; i++) {
            sum += uniform ();
        }

        System.out.println ("Average of 10000 samples: " + sum/10000);
    }

}

Note:

 

Let's modify the code so that the seed can be set via a method: (source file)


public class UniformRandom2 {
    
    // Same constants
    static long r_seed = 12345678L; 
    static final long m = 2147483647L;
    static final long a = 48271L;
    static final long q = 44488L;
    static final long r = 3399L;

    public static double uniform ()
    {
        // ... same as before ...
    }

     // Set the seed to any given value.
    public static void setSeed (long value)
    {
        r_seed = value;
    }

    public static void main (String[] argv)
    {
        // Let's test the generator again. The mean should be 0.5.
        setSeed (13579);
        double sum = 0;
        for (int i=1; i<=10000; i++) {
            sum += uniform ();
        }

        System.out.println ("Average of 10000 samples: " + sum/10000);
    }

}

Note:

 

Exercise 3.1: Modify the above code to add a method that computes the area of a circle of given (parameter) radius. Then use the method to compute the mean area of a circle whose radius is uniformly distributed in the range (0,1). Thus, generate a number of circles randomly (according to the above distribution), compute the area of each and take the average. What is the theoretical answer?
 


Methods: passing parameters

 

We have already seen an example of passing parameters by value. What about call-by-reference?

All parameters in Java methods are passed by-value.
Thus, no "swap" method is possible in Java.

What happens if we try to implement "swap"? Let's see: (source file)


public class Swap {

    public static void swap (int a, int b)
    {
        int temp;
        temp = a;
        a = b;
        b = temp;
    }

    public static void main (String[] argv)
    {
        int i=5, j=6;
        swap (i,j);
        System.out.println ("i=" + i + "  j=" + j);
        // What gets printed out?
    }
}

Why didn't they provide call-by-reference in Java?



What do you do when you want a method to modify a variable?

 


Methods: overloading

 

Suppose we want to add the following methods to our uniform_random generator:

One option is to define methods as follows:


public class UniformRandom3 {

    static long r_seed = 12345678L; 
    static final long m = 2147483647L;
    static final long a = 48271L;
    static final long q = 44488L;
    static final long r = 3399L;

    public static double uniform ()
    {
        // ... same as before ... 
    }

    // U[a,b] generator
    public static double uniformRange (double a, double b)
    {
        // ... need code in here ... 
    }

    // Discrete Uniform random generator -
    returns an integer between a and b
    public static long discreteUniform (long a, long b)
    {
        // ... need code in here ... 
    }

    public static void main (String[] argv)
    {
        // ... use the methods ...
    }

}

Java allows you to overload method names as long as the methods' signatures are unique.

Thus, instead of defining uniformRange and discreteUniform above, we could simply use uniform: (source file)


public class UniformRandom4 {

    static long r_seed = 12345678L; 
    static final long m = 2147483647L;
    static final long a = 48271L;
    static final long q = 44488L;
    static final long r = 3399L;

    public static double uniform ()
    {
        // ... 
    }

    // U[a,b] generator 
    public static double uniform (double a, double b)
    {
        if (b > a) {
           // Can you figure out why this works?
            return ( a + (b-a) * uniform() );
        }
        else { 
            System.out.println ("ERR in uniform(double,double):a="+a+"b="+b); 
            return 0;
        }
    }

    // Discrete Uniform random generator - returns an
    // integer between a and b
    public static long uniform (long a, long b)
    {
        if (b > a) {
            double x = uniform ();
            long c = ( a + (long) Math.floor((b-a+1)*x) );
            return c;
        }
        else if (a == b) {
            return a;     
        }
        else { 
            System.out.println ("ERR: in uniform(long,long):a="+a+"b="+b); 
            return 0;
        }
    }


    public static void main (String[] argv)
    {
        // Use the functions ...
        // Note: they have the same name.
        double x = uniform ();
        double y = uniform (2.718, 3.141);
        long i = uniform (7, 16);

        System.out.println ("x=" + x + " y=" + y + " i=" + i);
    }

}

Note:

 


Screen I/O

 

We have seen how to print stuff to the screen using System.out.print() or System.out.println().

We'll start by reading a single line from the screen. There are two ways to do this:


Again, for comparison, we'll look at two different ways to read a single integer from the screen:

Note:

 


Screen I/O: formatting

 

 


File I/O: reading from a file

 

The Java approach to I/O in general is similar to that of C++:

Since a file is just an input stream, reading from a file is similar to reading from the screen, provided the file is identified as an input stream.

The following example shows how to read a text file line-by-line: (source file )

import java.io.*;

public class FileIO1 {

    public static void main (String[] argv)
    {
        try {
            // These are the key steps in setting up the read operation.
            FileReader fr = new FileReader ("testdata");
            LineNumberReader lr = new LineNumberReader (fr);

            // Now read the input lines
            boolean over = false;         
            int i = 1;
            do {
                // Get a line from the file.
                String inputLine = lr.readLine ();
                if (inputLine != null) {
                    System.out.println ("Line " + i + ": " + inputLine);
                    i++;
                }
                else {
                    over = true;
                }
            } while (! over);

            // Done.
            lr.close();
        }
        catch (IOException e) {
            // If there was a problem...
            System.out.println (e);
        }
    
    }
}

Note:

Here's the same example with a (simpler) while-loop:


public static void main (String[] argv)
{
    try {
        FileReader fr = new FileReader ("testdata");
        LineNumberReader lr = new LineNumberReader (fr);

        // Using a while loop ...
        String inputLine = lr.readLine ();
        int i = 0;
        while (inputLine != null) {
            i ++;
            System.out.println ("Line " + i + ": " + input_line);
	    inputLine = lr.readLine ();
        }

        // Done.
        lr.close();
    }
    catch (IOException e) {
        // If there was a problem...
        System.out.println (e);
    }
    
}
 


File I/O: writing to a file

 

Similar to reading, writing is an output stream activity. By defining a file as an output stream, you can write to a file: ( source file )


import java.io.*;

public class FileIO2 {

    public static void main (String[] argv)
    {
        try {
            // Need to associate a file with a PrintWriter.
            FileWriter fr = new FileWriter ("testdata2");
            PrintWriter pw = new PrintWriter (fr);

            // Now we're ready for writing.
            pw.println ("Hello");
            pw.println ("Hello again");

            // Done.
            pw.close();
         }
         catch (IOException e) {
             System.out.println (e);
         }
     }
}

Note:

 


File I/O: reading and writing to the same file

 

Reading and writing to the same file can be done in many ways. We will consider the following simple task:

Example: ( source file )


import java.io.*;

public class FileIO3 {

    public static void main (String[] argv)
    {
	String filename = "testdata3";
	int numLines = 0;
	String[] lineBuffer = null;
	
	// First, we read in the file to get the size.
	try {
	    FileReader fr = new FileReader (filename);
	    LineNumberReader lr = new LineNumberReader (fr);
	    
	    boolean over = false;         
	    numLines = 0;
	    do {
		String input_line = lr.readLine ();
		if (input_line != null)
		    numLines ++;
		else
		    over = true;
	    } while (! over);
	    
	    lr.close();
	}
	catch (IOException e) {
	    System.out.println (e);
	}

	// We have now counted the number of lines and
	// we will read the file into a buffer.
	try {
	    FileReader fr = new FileReader (filename);
	    LineNumberReader lr = new LineNumberReader (fr);
	    
	    // Allocate necessary space.
	    lineBuffer = new String[numLines];
	    
	    int i = -1;
	    boolean over = false;
	    do {
		String s = lr.readLine ();
		if (s == null) 
		    over = true;
		else 
		    lineBuffer [++i] = s;
	    } while (! over);
	    
	    lr.close();
	}
	catch (IOException e) {
	    System.out.println (e);
	}
	
	// Next, write the buffer out to the same file
	// with line numbers added.
	try {
	    // Open the file for writing
	    FileWriter fr = new FileWriter (filename);
	    PrintWriter pw = new PrintWriter (fr);
	    
	    // Write from buffer with line numbers:
	    for (int i=0; i<numLines; i++) 
		pw.println ("Line " + (i+1) + ": " + lineBuffer[i]);
	    
	    pw.close();
	}
	catch (IOException e) {
	    System.out.println (e);
	}
	
    }

}
 


File I/O: Appending to a file

 

To append to a file, simply use the appropriate arguments to FileWriter: include "true" to indicate appending.

Example: ( source file )


import java.io.*;

public class FileIOAppend {

    public static void main (String[] argv)
    {
	try {
	    // Using "true" as a second argument indicates "append"
	    // in FileWriter.
	    FileWriter fr = new FileWriter ("testdata_append", true);
	    PrintWriter pw = new PrintWriter (fr);

	    // Now we're ready for writing.
	    pw.println ("Hello");
	    pw.println ("Hello again");

	    // Done.
	    pw.close();
	}
	catch (IOException e) {
	    System.out.println (e);
	}
    }

}
 


Simple input parsing

 

Consider an input file that looks like this:

Circle:
  center.x=50
  center.y=60
  radius=20
Circle:
  center.x=320
  center.y=180
  radius=40
Rectangle: 
  topleft.x=50
  topleft.y=400
  bottomright.x=500
  bottomright.y=40

What we would like to do is to parse the input to extract the numbers.

Here is how it can be done ( source file ):



// We need to import the StringTokenizer object in
// the java.util package:
import java.util.*;

public class FileIO4 {

    // Parameters:
    //   inString: the property string (e.g. "radius=0.9")
    //   property:  the property name (e.g., "radius")
    // The value is assumed to be a real number.

    public static double readProperty (String inString, String property)
    {
        // Obtain a copy of the StringTokenizer object.
	StringTokenizer st = new StringTokenizer (inString);
	
	// Get the first token, specifying the delimiter.
	String firstPart = st.nextToken ("=");
	
	// Trim whitespace on either side - a String function.
	firstPart = firstPart.trim ();
	
	System.out.println ("First part: " + firstPart);
	
	if (! firstPart.equals (property)){
	    System.out.println ("Improper string: " + inString);
	    System.exit (0);
	}
	
	// Get the next token, now allowing for end-of-line.
	String secondPart = st.nextToken (" =\t\n\r");
	
	// Trim whitespace.
	secondPart = secondPart.trim();
	
	System.out.println ("Second part: " + secondPart);
	
	// Read the number in the second part using the library's
	// Double object.
	try {
	    double d = Double.parseDouble (secondPart);
	    return d;
	}
	catch (NumberFormatException e) {
	    System.out.println ("Second part not a number: " + secondPart);
	    System.exit (0);
	}
	return 0;
    }

    public static void main (String[] argv)
    {
	// Test
	double d = readProperty ("x=5", "x");
	System.out.println (d);
	
	d = readProperty ("center.x=5", "center.x");
	System.out.println (d);
	
	d = readProperty ("center.x=5   ", "center.x");
	System.out.println (d);
	
	d = readProperty (" center.x = 5   ", "center.x");
	System.out.println (d);
	
    }

}

Note:

Exercise 3.3: Use the above method, and the methods for reading to a file for the following task. Suppose we want to extract information from a file containing a description of a circle. For example (source file):

Circle:
  center.x=50
  center.y=60
  radius=20
You are to prompt for the file name, read the file, extract the information and print out the parameters of the circle, and its area. Hints:
 


Encapsulation: single file

 

Since the above methods of reading an integer etc are useful, let's a create a program unit or module to contain these methods.

We will rewrite these methods in the file ReadGeodata.java


import java.io.*;
import java.util.*;

class UsefulIO {

    // Read a string from the screen.
    public static String readString (String prompt)
    {
        // .... 
    }

    // Parse a string for a property.
    public static double readProperty (String inString, String property)
    {
        // .... 
    }


} // End of class UsefulIO


public class ReadGeoData {

    public static void main (String[] argv)
    {
	// Get file name
	String filename = UsefulIO.readString ("Enter filename: ");
	
	try {
	    // ... Now read the input lines and process them ... 
	}
	catch (IOException e) {
	    System.out.println (e);
	}
	
    }
    
} // End of ReadGeoData

Note:

 
Why is this form of encapsulation a good thing?
 


Encapsulation: multiple files

 

To make a collection of methods truly useful, the encapsulation should be placed in a separate file.

We will place our I/O methods in the file UsefulIO.java :


import java.io.*;
import java.util.*;

public class UsefulIO {

    // Read a string from the screen.

    public static String readString (String prompt)
    {
        // ... 
    }

    // Parse a string for a property.
    
    public static double readProperty (String inString, String property)
    {
        // ... 
    }

    // ... Other useful methods ...

}

Next, these methods will be used in the file ReadGeodata2.java:


import java.io.*;

public class ReadGeoData2 {

    public static void main (String[] argv)
    {
	// Get file name:
	String filename = UsefulIO.readString ("Enter filename: ");

        // ... rest of code not shown ...
    }	
    
}

Note:

 


Packages

 

To help programmers and programming teams manage files, Java provides the ability to package related code.

Consider the following example:

What is going on?




© 1998, Rahul Simha (revised 2017)