Java程序辅导

C C++ Java Python Processing编程在线培训 程序编写 软件开发 视频讲解

客服在线QQ:2653320439 微信:ittutor Email:itutor@qq.com
wx: cjtutor
QQ: 2653320439
Java - Classes and methods Java - Classes and methods [ The rational number class | Adding rational numbers | Adding an integer to a rational number | Simplification | Arrays of rational numbers | Sorting arrays of rational numbers | Square root of a rational number ] The rational number class For some types of numerical calculation it is useful to work with rational numbers. These are numbers whose value is expressed as the ratio or quotient of two integers. This means that many fractional values (such as 1/7 and 2/3) can be stored exactly, something that isn't possible using normal floating point numbers. Java doesn't provide such a data type. However, it is quite straightforward to define one for yourself. Once you've written code that will manipulate such rational numbers, it could well be useful in other contexts and possibly to other people. Java provides a rich set of mechanisms for making code available. The basic mechanism is to 'encapsulate' all the code and related material in something called a class. Of course, we have already been using Java standard classes extensively and all the programming examples so far have actually consisted of a simple class. To support our rational numbers we are going to create a class called RatNumber We will represent rational numbers as pairs of integers. For example ½ will be represented as (1,2) and ¾ will be represented as (3,4). The rules of arithmetic for rational numbers should be stated at this stage. Operation Definition Addition/Subtraction (x1,y1) ± (x2,y2) = (x1 × y2 ± x2 × y1, y1 × y2) Multiplication (x1,y1) × (x2,y2) = (x1 × x2,y1 × y2) Division (x1,y1) ÷ (x2,y2) = (x1 × y2, x2 × y1) Some examples might be in order. Operation Examples In decimal notation Addition (3,4)+(2,7) = (29,28) 0.75 + 0.285714... = 1.035714... Subtraction (2,3)-(1,8) = (13,24) 0.666666... - 0.125 = 0.541666... Multiplication (5,11)×(2,7) = (10,77) 0.454545... × 0.285714... = 0.12987... Division (9,13)÷(3,12) = (108,39) 0.692307... ÷ 0.25 = 2.76923... Ideally after calculation of a new rational number it should be reduced to its lowest terms, i.e. (50,100) should be converted to (1,2). To demonstrate the basic ability of a program to work with rational numbers it is necessary to provide input and output functions. Here's the first 'test harness' program that exercises the rational number class. It should be saved in a file called RatDemo1.java public class RatDemo1 { static public void main(String args[]) { RatNumber k = new RatNumber(3,4); System.out.println(k); } } And here's the rational number class source code. It should be saved in a second file called RatNumber.java public class RatNumber { int num; // The numerator int den; // The denominator public RatNumber(int x, int y) { num = x; den = y; } public String toString() { return "(" + num + "/" + den + ")"; } } Before discussing issues of syntax, let's see how the test harness program is compiled and run. There are two source code files, RatDemo1.java and RatNumber.java, to be compiled. They can be separately compiled and then the RatDemo1.class file can be executed. Here's the Command Prompt dialogue. C:\>javac RatNumber.java C:\>javac RatDemo1.java C:\>java RatDemo1 (3/4) Notice that the two source (.java) files are compiled separately producing two class (.class ) files. Once this has been done the RatDemo1.class file is executed. This file can be executed because it contains a method called main. In attempting to execute RatDemo1.class the Java interpreter encounters references to the class RatNumber, and it will then try to load the class. In order to load a class, the Java interpreter needs to find a class file whose name matches the name of the class. When the Java compiler and interpreter require either class information (in the case of the compiler) or class loading (in the case of the interpreter) where they look for classes is determined by something called the CLASSPATH. This specifies a list of directories. If CLASSPATH is not explicitly set it will default to the current directory and that is very convenient for the simple example shown above. In this case it is only necessary to explicitly compile RatDemo1.java, the compiler will check for the presence of RatNumber.class and if it is not present or it is present and appears to be older than RatNumber.java then it will also be compiled. If CLASSPATH is set then it must include the current directory or you can use the java/javac command line argument -classpath to supply a specific value. Problems with the correct setting of CLASSPATH are very common in Java development. On Unix and other systems environment variables are sets of strings that define interesting parameters that are available to processes. They are normally manipulated using command line tools and are commonly set to "useful" values as part of the login sequence. On Unix/Linux systems you can get a list of all the environment variables by using the command env. You can also display the value of a particular environment variable using a command such as "echo $CLASSPATH". For more information see any Unix text or manual. If you want to change the test harness program, there is no need to change or recompile the class code. Similarly, if you wish to change the class code, you only need to recompile the class code; the test harness does not require recompilation. In this simple example there are only two methods defined in the RatNumber class. These are a simple method called toString which returns a string representation of the rational number object, and a method called RatNumber. This second method is called a constructor. The RatNumber.java file also includes the declaration of two integers (instance variables) called num and den. The constructor A rational number is an object that encapsulates two integers. When a rational number is required, code such as the following is used to create an instance of the RatNumber class (i.e. create a RatNumber object). RatNumber k = new RatNumber(3,4); This is a typical Java code snippet, of which several examples have been seen earlier in these notes. It is actually a shorthand way of writing RatNumber k; k = new RatNumber(3,4); The effect of the first line of code is to declare a reference variable named k. This is intended to hold a reference to (i.e. the address of or a pointer to) a RatNumber object. The second line of code, in the jargon, invokes the constructor for the class RatNumber. The constructor will return a reference (i.e. the address) of a new instance of the RatNumber class and initialise it in accordance with the parameters supplied for the constructor (3,4 in this case). The Java operator new when followed by a class name, invokes the class constructor. The internal structure of the newly created RatNumber object is simply a copy of the instance variables defined within the RatNumber class definition. In this case the internal structure consists of the two integers called den and num. The names of these integer variables are local to the RatNumber object and cannot be used anywhere else. This invisibility of the internal structure is an important aspect of good program design. The definition of the RatNumber constructor method can now be examined more carefully. public RatNumber(int x, int y) { num = x; den = y; } This is actually a simple method definition. The method name, RatNumber, is the same as the class name and, unlike other methods, does not specify a return type. This identifies the method as the class constructor. The constructor may or may not have parameters; in this case it has two integer parameters, x and y. These two values are assigned to the object's instance variables, num and den, thus initialising the object. The toString() method The RatNumber class also includes a method called toString() which returns a string representation of the object. This allows the println() method to print the RatNumber object, thus: System.out.println( k.toString() ); which produces the output: (3/4) This illustrates the syntax of method references which is typical of object oriented programming languages. k is, as discussed earlier, a reference to a RatNumber object and this piece of code simply invokes the toString() method of the RatNumber class. (This is implied by the fact that the variable k is a reference to an instance of the class RatNumber). Here's the code of the toString() method. public String toString() { return "(" + num + "/" + den + ")"; } Note that the instance variables num and den are accessible since the method toString() is within the RatNumber class. There is one further point to note. Whenever println() is used to print an object, it will automatically call the object's toString() method to obtain a string to print, even when the method is not specified. This is possible because every object inherits a default toString() method. However, the string returned by the default method is usually not very meaningful for the user, so it is usual to redefine toString() whenever you write you own class. This default behaviour of println() means you can print any object with this simplified statement (as used in out version of RatDemo1): System.out.println( k ); Adding up rational numbers Of course, just creating and displaying rational numbers isn't very exciting. The next example extends both the class and the test harness to perform addition on rational numbers. First check the information above to see how rational numbers are added and then examine the code below for the new version of the rational number class. public class RatNumber { int num; int den; public RatNumber(int x, int y) { num = x; // The numerator den = y; // The denominator } public String toString() { return "(" + num + "/" + den + ")"; } public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber(0,0); result.num = num * a.den + a.num * den; result.den = den * a.den; return result; } } And here's the code of a new version of the test harness. public class RatDemo2 { static public void main(String args[]) { RatNumber k, l, m; k = new RatNumber(3,4); l = new RatNumber(5,6); m = k.add(l); System.out.println(k+" + "+l+" = "+m); } } And here's the output from the test harness. C:\>java RatDemo2 (3/4) + (5/6) = (38/24) Of course the answer really should have been (19,12) but the RatNumber class doesn't yet include code to reduce a rational number to it's lowest terms. The add() method in the RatNumber class is fairly straightforward. Like many methods, add() has a parameter. This is called a formal parameter in the definition of the method and an actual parameter when the method is actually called. When the method is executed, the formal parameter takes on the value (here a reference) of the actual parameter. Nearly all programming languages support the use of functions although they may be called procedures or subroutines. In most modern programming languages, arguments are transferred in the same way as they are in Java. I.e. the values of the actual parameters are calculated (they may be expressions) and the values passed to the function. In some languages, if the parameter is the name of a variable, the address of a variable is passed to the function and the function operates "indirectly" on the variable. This is known as call by reference to distinguish it from the commoner method known as call by value. A further method known as call by name extends the idea. If one of the actual parameters is an expression, then rather than passing the value of the expression, a pointer to code that will evaluate the expression is passed. As a consequence, repeated references to such a parameter will cause repeated expression evaluations, possibly with different results if the function has changed the values of some of the variables referenced in the expression. This can get very confusing. Java uses call by value, although the values passed are going to be addresses (references) if they are not primitive data types. In this respect, as in many others, Java is quite similar to C. Note how the method calculates the sum of the two rational numbers. A third RatNumber instance is created and referenced by the variable result. The instance variables of the local object, num and den, are used with the instance variables of the parameter, a.num and a.den, to calculate the instance variables of result, i.e. result.num and result.den. The method then returns the reference result. The code is shown below. public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber(0,0); result.num = num * a.den + a.num * den; result.den = den * a.den; return result; } There is no way to create a rational number without initialising it via the constructor. In this case the values stored by the constructor, (0, 0), are immediately over-written. If you think this is rather clumsy, you may prefer the following code: public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber( num * a.den + a.num * den, den * a.den ); return result; } Or even, if you are enthusiast for C-like brevity of coding: public RatNumber add(RatNumber a) { return new RatNumber( num * a.den + a.num * den, den * a.den ); } Notice that, in the final example, there is no need for the variable result. The test harness code is also quite straightforward. There are three rational number reference variables, k, l and m. k and l are initialised in the test harness via the RatNumber constructor. m acquires a value by assignment of the value returned by the add() method. Adding an integer to a rational number Suppose the user wanted to add a simple integer, such as 3, to a rational number using the RatNumber class. The user could, of course, do something like: RatNumber k, l, m; k = new RatNumber(2,3); l = new RatNumber(3,1); // i.e. 3 m = k.add(l); But there is a better, simpler way. It is straightforward to write a function that adds an integer to a rational number and include it in the RatNumber class code. More surprisingly, it can share the name add with the method already defined. Here's the code: public class RatNumber { int num; // The numerator int den; // The denominator public RatNumber(int x, int y) { num = x; den = y; } public String toString() { return "(" + num + "/" + den + ")"; } public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber(0,0); result.num = num * a.den + a.num * den; result.den = den * a.den; return result; } public RatNumber add(int a) { return new RatNumber( num + a * den, den ); } } And here's a version of the test harness that exercises this new method: public class RatDemo2 { static public void main(String args[]) { RatNumber k, l, m, n; k = new RatNumber(3,4); l = new RatNumber(5,6); m = k.add(l); System.out.println(k+" + "+l+" = "+m); n = m.add(2); System.out.println(m+" + 2 = "+n); } } And here's the output: C:\>java RatDemo2 (3/4) + (5/6) = (38/24) (38/24) + 2 = (86/24) Tidying it up Before going on to more ambitious things, it will be useful to reduce our rational numbers to their simplest terms, i.e. rather than storing a rational number as (38, 24) as shown in the previous example, it should be stored as (19, 12). This involves determining the largest integer that will divide both of the components. This is known as the greatest common divisor and a method for determining it was devised by Euclid in classical antiquity. (You don't need to understand Euclid's method to follow what is happening in the code below). It will also be useful to display the rational number's value as a floating point number. A new method, toDouble(), has been added to do this conversion. The class code has been significantly changed and the new version appears below. As in previous examples the code changes are highlighted. public class RatNumber { int num; // The numerator int den; // The denominator public RatNumber(int x, int y) { num = x; den = y; } public String toString() { return "(" + num + "/" + den + ")"; } public double toDouble() { return (double) num / den; } public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber(0,0); result.num = num * a.den + a.num * den; result.den = den * a.den; return simplify(result); } public RatNumber add(int a) { return simplify(new RatNumber( num + a * den, den )); } private RatNumber simplify(RatNumber a) { int gcd; gcd = euclid(a.num,a.den); a.num /= gcd; a.den /= gcd; return a; } /* Euclid's algorithm, see http://www.nist.gov/dads/HTML/euclidalgo.html */ private int euclid(int a,int b) { if(b==0) return a; else return euclid(b, a % b); } } Here is a suitable test harness, RatDemo3: public class RatDemo3 { static public void main(String args[]) { RatNumber k, l, m, n; k = new RatNumber(3,4); l = new RatNumber(5,6); m = k.add(l); n = m.add(2); System.out.println(m+"\t"+m.toDouble()); System.out.println(n+"\t"+n.toDouble()); } } and here's the result of running the test harness: (19/12) 1.5833333333333333 (43/12) 3.5833333333333335 The changes to the class file are not too difficult to follow. Reducing the rational number to its lowest terms is achieved by two new methods, simplify() and euclid(). Notice that both these methods have the modifier private. This means the methods can only be used within the object, whereas an object's public methods can also be called from outside the object. The simplify() method extracts the two instance variables of the rational number and passes them to the method euclid. Note that this method modifies the rational number whose reference was supplied as a parameter; it returns this reference as a functional value. It could have been coded as a method with void value since all the changes have been made via the reference and the function has not created a new rational number. However this would have made the revised coding of the add() methods more clumsy. The operator /= is the divide and assign operator that would be familiar to C programmers. The code x /= y is equivalent to x = x/y. The euclid() method, apart from the private modifier, would be exactly the same if coded in C. Notice that this function calls itself, i.e. this is a recursive method. Also note how the code of the add() methods have been modified. The reference to the new rational number created by the RatNumber class constructor is passed to simplify as a parameter without being stored anywhere. This reference is returned by simplify and, in turn, returned by the add method. Finally, note that the toDouble() method includes an example of explicit casting. When the expression num / den is evaluated, it will produce an integer result. This is because the division operator is operating on two integer values. To force floating point division, the programmer must explicitly change the data type of one of the values to floating point by preceding the variable with the changed data type in brackets, thus (double)num. A double divided by an integer now produces a floating point result, as previously seen. An array of rational numbers Here's a simple test harness that creates an array of rational numbers and displays them. public class RatDemo4 { static public void main(String args[]) { RatNumber[] nums; nums = new RatNumber[10]; for(int i=0;i<10;i++) { int j = i % 3; nums[i] = new RatNumber(j+3, i+6); } for(int i=0;i<10;i++) System.out.println( nums[i]+"\t"+nums[i].toDouble() ); } } And here's the output. (3/6) 0.5 (4/7) 0.5714285714285714 (5/8) 0.625 (3/9) 0.3333333333333333 (4/10) 0.4 (5/11) 0.45454545454545453 (3/12) 0.25 (4/13) 0.3076923076923077 (5/14) 0.35714285714285715 (3/15) 0.2 The code is deliberately a bit convoluted so that the rational numbers are not in order because in our next example we're going to sort this array. Note the syntax for creating an array of rational numbers is simply to use the class constructor name with an array size specification appended. Sorting an array of rational numbers In the previous example we created and initialised an array of rational numbers. They were deliberately out of sequence. Java, as you might expect, provides a standard sort method. This is carefully designed for efficiency and reliability. If you've ever tried to write your own sort routine you'll appreciate this. A quick reference to the documentation shows that the sort method is part of the Arrays class and the documentation shows that the class's sort method is overloaded so it can sort arrays of various primitive data types into ascending order. The sort method can also sort an array of objects, providing the objects have a natural sort order defined. This will be the case if the class of the objects implements the Comparable interface. For example, if you have an array of strings called names, say, you can sort the array with this statement: Arrays.sort( names ); since String is a Java standard class which implements the Comparable interface, and therefore string objects have a natural sort order. However our rational numbers are not one of Java's standard classes. To sort the array, Java needs to know how two rational numbers are to be ordered. We must supply a method that will compare two rational numbers. This method will be called repeatedly by the sort method to sort the array elements. The implication is that sort will sort arrays of objects of any class provided it knows how to do the comparison. There is also a second, more general version of the sort method which takes two parameters. This version is useful if, during the course of a program, you need to sort the array in a number of different ways. The second parameter is a reference to an object which defines the sort order. Each time you call the sort method, you can supply a different object for this parameter to sort the array in any required order. We will use this version to display the array of RatNumber objects first in ascending order, then in descending order of value. The Java documentation shows this version of the sort method as: public static void sort(T[] a, Comparator c) This is a generic definition. The T character is a placeholder for the class name of the array elements being sorted, in this case RatNumber. The first parameter is a reference to the array of RatNumber objects; the second parameter is a Comparator reference to the object that determines the sort order. Comparator is not actually a class but a closely related concept called an interface defined in the java.util package. An interface specifies, in an abstract way, certain required methods that must be defined by any class that implements the interface. A class will implement an interface by defining the abstract methods listed in the interface. Here's the actual definition of the Comparator interface: public interface Comparator { public abstract int compare(T o1, T o2); public abstract boolean equals(Object o); } Again, this is a generic definition. The T must be replaced with the class name of the the two objects to be compared, i.e. RatNumber. A class that implements this interface must define the two methods listed, compare() and equals(). The following class implements the Comparator interface and should be saved in a file called AscOrder.java: import java.util.Comparator; public class AscOrder implements Comparator { // Method to compare RatNumber objects. Order is ascending public int compare(RatNumber a, RatNumber b) { return (a.num * b.den)-(a.den * b.num); } } First the Comparator interface is imported. Then the class header declares that this class, AscOrder, will implement the Comparator interface. The compare() method is defined as required. This method receives references to two RatNumber objects to be compared. If a is greater than b, a positive integer must be returned, if a is less than b, a negative integer must be returned, and if a equals b, zero must be returned. The calculation achieves this. The sort method will call this method repeatedly as it sorts the array into ascending order. Since the sort method only uses compare(), we will not bother defining the equals() method; a default version will be inherited instead. A similar class must be defined for sorting into descending order. The only difference is the calculation performed by compare() method. This code should be saved as DescOrder.java. import java.util.Comparator; public class DescOrder implements Comparator { public int compare(RatNumber a, RatNumber b) { return (b.num * a.den)-(b.den * a.num); } } The test harness has also been modified to include the following lines of code: import java.util.Arrays; public class RatDemo4 { static public void main(String args[]) { RatNumber[] nums; nums = new RatNumber[10]; for(int i=0;i<10;i++) { int j = i % 3; nums[i] = new RatNumber(j+3, i+6); } System.out.println("Unsorted numbers:"); for(int i=0;i<10;i++) System.out.println( nums[i]+"\t"+nums[i].toDouble() ); Arrays.sort( nums, new AscOrder()); System.out.println("\nAscending order:"); for(int i=0;i<10;i++) System.out.println( nums[i]+"\t"+nums[i].toDouble() ); Arrays.sort( nums, new DescOrder()); System.out.println("\nDescending order:"); for(int i=0;i<10;i++) System.out.println( nums[i]+"\t"+nums[i].toDouble() ); } } Looking at the test harness code, the call to the Arrays.sort() method has the reference of the array of rational numbers as its first parameter. An object of class AscOrder is created and passed as the second parameter. The array is then sorted according to the sort order defined by the compare() method in the AscOrder class and the array displayed on the screen. This is repeated a second time, but with an object of class DescOrder passed to Arrays.sort(). The program produces the following output: Unsorted numbers: (3/6) 0.5 (4/7) 0.5714285714285714 (5/8) 0.625 (3/9) 0.3333333333333333 (4/10) 0.4 (5/11) 0.45454545454545453 (3/12) 0.25 (4/13) 0.3076923076923077 (5/14) 0.35714285714285715 (3/15) 0.2 Ascending order: (3/15) 0.2 (3/12) 0.25 (4/13) 0.3076923076923077 (3/9) 0.3333333333333333 (5/14) 0.35714285714285715 (4/10) 0.4 (5/11) 0.45454545454545453 (3/6) 0.5 (4/7) 0.5714285714285714 (5/8) 0.625 Descending order: (5/8) 0.625 (4/7) 0.5714285714285714 (3/6) 0.5 (5/11) 0.45454545454545453 (4/10) 0.4 (5/14) 0.35714285714285715 (3/9) 0.3333333333333333 (4/13) 0.3076923076923077 (3/12) 0.25 (3/15) 0.2 The rational numbers have indeed been sorted as intended. Note that no changes were required to the RatNumber class. It is only necessary to define a class that implements the Comparator interface and define the compare() method to produce the sort order required.   Calculating the square root of a rational number A common method of calculating the square root of any number is to use a particular example of the Newton-Raphson method. In order to calculate the square root of a, make an initial approximation x and then evaluate the expression (x+a/x)/2 to obtain a better approximation. Repeat until the result is "good enough". For example to calculate the square root of 5 using 2.5 as an initial approximation, proceed thus: Approximation 5/x x+5/x (x+5/x)/2 2.5 2 4.5 2.25 2.25 2.2222... 4.47222... 2.236111.... 2.236111... 2.236024845.. 4.472135956 2.236067978 2.236067978 2.236067977 4.4721355954 2.236067977 The accuracy is limited by the accuracy of my calculator. (If you want better accuracy you can use the Unix command line tool bc, this provides arbitrary precision arithmetic). To implement the calculation shown above using rational numbers, we need to include some extra methods in the RatNumber class. These provide for subtraction, multiplication and division by rational numbers and integers. The gcd method has also been tweaked to prevent it generating a negative greatest common divisor. The revised class is shown below. public class RatNumber implements Comparable { int num; // The numerator int den; // The denominator public RatNumber(int x, int y) { num = x; den = y; } public String toString() { return "(" + num + "/" + den + ")"; } public double toDouble() { return (double) num / den; } public RatNumber add(RatNumber a) { RatNumber result; result = new RatNumber(0,0); result.num = num * a.den + a.num * den; result.den = den * a.den; return simplify(result); } public RatNumber add(int a) { return simplify(new RatNumber( num + a * den, den )); } public RatNumber sub(RatNumber a) { return simplify(new RatNumber(num * a.den - a.num * den, den * a.den)); } public RatNumber sub(int a) { return simplify(new RatNumber(num - a * den, den)); } public RatNumber mult(RatNumber a) { return simplify(new RatNumber(num * a.num, den * a.den)); } public RatNumber mult(int n) { return simplify(new RatNumber(num * n, den)); } public RatNumber div(RatNumber a) { return simplify(new RatNumber(num * a.den, den * a.num)); } public RatNumber div(int n) { return simplify(new RatNumber(num, den * n)); } private RatNumber simplify(RatNumber a) { int gcd; gcd = euclid(Math.abs(a.num), Math.abs(a.den)); a.num /= gcd; a.den /= gcd; return a; } /* Euclid's algorithm, see http://www.nist.gov/dads/HTML/euclidalgo.html */ private int euclid(int a, int b) { if(b==0) return a; else return euclid(b, a % b); } public int compareTo(RatNumber n) { return (num * n.den)-(den * n.num) ; } } The new sections of code are highlighted. Here is the code of a program that calculates the square root of a rational number: public class RatSqrt { static public void main(String args[]) { RatNumber a = new RatNumber(5,1); RatNumber x, xp; x = new RatNumber(1,1); x = a.div(2); double err; do { xp = a.div(x); xp = xp.add(x); xp = xp.div(2); // i.e. xp = (a/x + x)/2 err = Math.abs(xp.toDouble() - x.toDouble()); x = xp; System.out.println( x+"\t"+x.toDouble() ); } while(err > 0.0000001); } } This program will only calculate the square root of 5. Modifying it to read values from user input is left as an exercise for the reader. The repetitive calculation of values is terminated when the (absolute) difference between two successive approximations to the square root differ by less than 0.0000001. Here's the output produced by running the program. (9/4) 2.25 (161/72) 2.236111111111111 (51841/23184) 2.236067977915804 (360003755/801254496) 0.4493001372188244 (-1798375879/277956672) -6.469986368954655 (-397019077/-187057792) 2.1224407321134207 (-1091330663/-490503936) 2.2249172389923495 . . There are several more lines of the same general appearance. The square root of 5 is approximately 2.23606797749978969640 so, clearly, by line 3 of the output the program has produced a good approximation but after that things seem to go badly wrong. Looking at the two parts of the rational number on the 4th line of output, it looks very much as if the integer multiplications involved in rational number arithmetic have exceeded the capabilities of int arithmetic. One possible solution is to modify the RatNumber class to use long (64 bit) data internally, but this is only a palliative. A more sensible approach is to make the RatNumber class throw an exception when results are likely to be out of range. Problems can be detected by the, apparently, clumsy procedure of calculating all the products etc., using floating point arithmetic and flagging an error if these exceed the floating point equivalent of the largest int value. Doing this properly is tedious. Generating exceptions We have already seen how to catch exceptions. This section deals with how to generate them. The RatNumber class and the square root calculation program will be modified to address this issue. The first important point is to remember that an exception is an object. This means that all the various sorts of exception must be defined in a class. In this particular case we're going to define RatRangeException. This means we are going to need a class called RatRangeException and since this class needs to be used in both the square root calculating program and in the RatNumber class, it needs to be public and hence there needs to be a separate file called RatRangeException.java which will be separately compiled. Before we look at the RatRangeException class, let's see how the RatNumber code throws an exception. Here is a part of the revised class, showing the add(RatNumber) method modified to throw an exception on possible range error. public RatNumber add(RatNumber a) throws RatRangeException { if(Math.abs((double)num * (double)a.den) > Integer.MAX_VALUE) throw new RatRangeException("Oops"); if(Math.abs((double)den * (double)a.num) > Integer.MAX_VALUE) throw new RatRangeException("Ouch"); if(Math.abs((double)den * (double)a.den) > Integer.MAX_VALUE) throw new RatRangeException("Damn"); return simplify(new RatNumber(num * a.den + a.num * den, den * a.den)); } This is not too difficult to follow. The first syntactical point is that the first line of the method includes a clause that says throws RatRangeException. This simply indicates that this method could throw a RatRangeException. The compiler will check this, hence it is known as a checked exception, and the compiler will include code to check the handling of the exception. (Some standard exceptions such as NullPointerException are unchecked and there is no need for throws clauses). The remaining new code tests for a fault condition and if one is found the exception is thrown. Typically, the syntax is: if(.....) throw new ....Exception("message"); Note the occurrence of the operator new here. Since exceptions are objects, what is happening is that a new exception object is created using the constructor method of the Exception class. It's now time to have a look at the RatRangeException class. Here's the code. public class RatRangeException extends Exception { static String s; public RatRangeException(String msg) { s = msg; } public RatRangeException() { } public String getMessage() { return s; } } There are no real surprises here. There are two constructors, one of which takes a String parameter. Note that the RatRangeException class extends the Exception class which in turn extends the Throwable class. If a class extends another class it means that the extending class inherits all the methods etc., of the parent class, unless they are specifically replaced. In this particular case it means that our RatRangeException class inherits, amongst other things, a getMessage() method, ultimately, from the class Throwable. However the inherited getMessage() method doesn't do quite what we want. It is replaced by a local method in our RatRangeException class. If the RatRangeException class constructor is invoked with a string parameter, then the string is saved in the class "local" storage s. This value is returned by the RatRangeException method getMessage(). Actually there was no need to call this getMessage(); it could just as well have been called WhyError() etc. The modifier static associated with the declaration of s means that it is not publically visible (it's not public) and, more importantly, that it retains the value between class method calls. The square root calculation program also requires modification to include suitable try/catch statements. Here it is: public class RatSqrt { static public void main(String args[]) { RatNumber a = new RatNumber(5,1); RatNumber x, xp; x = new RatNumber(1,1); x = a.div(2); double err; try { do { xp = a.div(x); xp = xp.add(x); xp = xp.div(2); // i.e. xp = (a/x + x)/2 err = Math.abs(xp.toDouble() - x.toDouble()); x = xp; System.out.println( x+"\t"+x.toDouble() ); } while(err > 0.0000001); } catch(RatRangeException e) { System.out.println("Error - " + e.getMessage()); } } } And finally here is the output from the program: [3/1] (3.0) [7/3] (2.3333333333333335) [47/21] (2.238095238095238) [2207/987] (2.236068895643364) [4870847/2178309] (2.236067977499978) Error - Oops This page is part of a set of notes about the Java programming language prepared by Peter Burden for the module CP4044. A disclaimer applies to this page.