In rational.html,we illustrated using already given interfaces. Now we switch to writing them.
In scientific computing you often want a function passed as a parameter. With Python we can easily pass a function as a parameter. There is involved documentation relating Java 8+ lambda expressions to single method interfaces, but we will stick to an approach where just use an interface makes sense, with two methods: We will assume that you will want not only a function, f, but also be able to generate a description string, with a toString. We can package both in an interface that we will name FunctionDoc for the expected signatures in such a class:
public interface FunctionDoc // interface, not class { // NO constructor double f(double x); //semicolon, not body String toString(); // also assumes instance } // methods are public
This is in file FunctionDoc.java The comments indicate most of the restrictions needed for an interface file:
If FD is declared as an instance of a FunctionDoc, then we can make calls like FD.f(2.5) and FD.toString(), but NO other methods.
Here are two possible classes implementing the FunctionDoc interface:. You can skip the logical details if you like: The essential things about these example interfaces are that they both have the two required method signatures, though implemented in very different ways, and that implementing classes like Polynomial can have many further components (instance variables, constructor, other methods):
class TrigSum implements FunctionDoc { public double f(double x) { return Math.sin(x) - Math.cos(x); } public String toString() { return "f(x) = sin x - cos x"; } } class Polynomial implements FunctionDoc { private double[] coef; // decreasing powers /** Polynomial given decreasing coefficient powers */ public Polynomial(double[] coefficients) { coef = shortenArray(coefficients); } /** return array without leading 0's */ public static double[] shortenArray(double[] coefficients) { // not in interface - OK int len = coefficients.length, deg = len - 1; while (deg > 0 && coefficients[deg] == 0) deg--; // leading 0's are redundant double[] nums = new double[deg+1]; for (int i = 0; i <= deg; i++) nums[deg - i] = coefficients[len - 1 - i]; return nums; } public double f(double x) { double sum = 0; for (double c : coef) sum = sum * x + c; return sum; } public String toString() { String s = "f(x) = "; int deg = coef.length - 1; for (int i = 0; i < deg; i++) if (coef[i] != 0) s += String.format("%fx^%s + ", coef[i], deg - i); return s + coef[deg]; } }
We will see a more sophisticated use of a user defined interface in the game of Zuul discussion.
The first example in my Java Companion section Notes on While Loops was on the bisection method for approximating roots of a real function.
The use was rather limited, since the specific function being checked was hard-coded into the algorithm.
With FunctionDoc interface, we can have a much more generally useful method:
As examples of interface use, the important thing in the code the use of a parameter of the FunctionDoc type, that can be passed on to a further method, and also that these methods call only the two expected methods for the FunctionDoc interface:
import java.lang.Math; public class ZeroFind { public static void main(String[] args) { FunctionDoc p = new Polynomial(new double[] {1, 0, -2}); // This should have solution x = +/- square root of 2 showBisection(p, 0, 2); // sqrt(2) showBisection(p, 0, 1); // no root p = new Polynomial(new double[] {1, -3, -3, 1}); // This should have solution x = -1. -1 - 3 + 3 + 1 = 0 showBisection(p, -10.0, 10.0); showBisection(new TrigSum(), 0, 1); // pi / 4 } /** Shows parameters and results of bisection function. */ static void showBisection(FunctionDoc FD, double a, double b) { System.out.println("\nLet " + FD); // use toString implicitly System.out.format("Looking for a root in [%f, %f].%n", a, b); double root = bisection(FD, a, b, true); if (!Double.isNaN(root)) // Nan not equal to itself! System.out.println ("An approximate root is " + root); else System.out.println ("Could not find the root: Endpoints same sign."); } /** This bisection method returns the best double approximation to a root of FD.f. Returns double.NaN if FD.f(a) and FD.f(b) have the same sign. Does not require a < b. Shows steps if showSteps. */ public static double bisection(FunctionDoc FD, double a, double b, boolean showSteps) { if (FD.f(a) == 0) return a; if (FD.f(b) == 0) return b; if (Math.signum(FD.f(a)) == Math.signum(FD.f(b))) return Double.NaN; // can't do bisection double c = (a + b) / 2; // If no f(c) is exactly 0, iterate until the smallest possible // double interval, when there is no distinct double midpoint. while (c != a && c != b) { if (showSteps) System.out.format ("a = %.17g b= %.17g diff = %.17g%n", a, b, b-a); if (FD.f(c) == 0) { return c; } if (Math.signum(FD.f(c)) == Math.signum(FD.f(a))) a = c; else b = c; c = (a + b) / 2; } return c; // double value matches an endpoint here } }
This is in file ZeroFind.java, along with the definitions of TrigSum and Polynomial.
Note again that the implements FunctionDoc at the end of the headings of the Polynomial and TrigSum classes are essential. You could try temporarily removing an implements clause, and see the compilation fail.
In order to keep all three classes in the same file, the latter two cannot be declared public. If I wanted TrigSum and Polynomial to be available to other programs, I would need each to be public and in its own file.
This has been an introduction to interface basics. We are not covering a number of more advanced features in Java 8+: