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?
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?
x = func (a,b,c); // x gets modified
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:
class UniformRandom { public static double uniform (double a, double b) { // ... } // This has the same signature. public static long uniform (double a, double b) { // ... } }
Exercise 3.2: Why does it make sense to bar the above from the language?
public static double uniform (double a, b)
import java.lang.*;
even though not needed here, imports all classes
in java.lang.
(We will see what this means later).
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:
import java.io.*; public class ScreenIO1 { public static void main (String[] argv) { // Put out a prompt. System.out.print ("Enter string: "); // We have to have a try clause because // the method readLine throws an exception. try { // These are the key steps in setting up the read operation. InputStreamReader isr = new InputStreamReader (System.in); LineNumberReader lr = new LineNumberReader (isr); // Now read the input line. String inputLine = lr.readLine (); // Echo it. System.out.println ("Echo: " + inputLine); } catch (IOException e) { // If there was a problem... System.out.println (e); } } }
import java.util.*; public class ScannerExample { public static void main (String[] argv) { Scanner scanner = new Scanner (System.in); System.out.print ("Enter string: "); String inputLine = scanner.nextLine (); System.out.println ("Echo: " + inputLine); } }
Again, for comparison, we'll look at two different ways to read
a single integer from the screen:
import java.io.*; public class ScreenIO2 { public static void main (String[] argv) { System.out.print ("Enter an integer: "); try { InputStreamReader isr = new InputStreamReader (System.in); LineNumberReader lr = new LineNumberReader (isr); String inputLine = lr.readLine (); // Parse for an integer. int i = 0; try { i = Integer.parseInt (inputLine); } catch (NumberFormatException e) { System.out.println ("Error in input"); System.exit (0); } // Echo it. System.out.println ("The integer you entered: " + i); } catch (IOException e) { System.out.println (e); } } }
import java.util.*; public class ScannerExample2 { public static void main (String[] argv) { Scanner scanner = new Scanner (System.in); System.out.print ("Enter an integer: "); int i = scanner.nextInt (); System.out.println ("The integer you entered: " + i); } }
Note:
try { //... some code } catch () { //... some other code }statement. In these cases, we are forced to by the compiler since the methods we call throw exceptions.
import java.io.*;
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); } }
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:
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); } } }
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); } } }
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:
import java.util.*;
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:
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:
filename = UsefulIO.readString ("Enter filename: ");
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:
To help programmers and programming teams manage files, Java provides the ability to package related code.
Consider the following example:
// Notice the import: import useful_io_pack.*; public class ReadGeoData3 { public static void main (String[] argv) { // Get file name: String filename = UsefulIOSubunit.readString ("Enter filename: "); // ... rest of code not shown ... } }
mkdir useful_io_pack
package useful_io_pack; // Must be the first statement in the file. import java.io.*; import java.util.*; public class UsefulIOSubunit { // Read a string from the screen. public static String read_string (String prompt) { // ... code not shown ... } // Parse a string for a property. public static double read_property (String inString, String property) { // ... code not shown ... } }
What is going on?