Interfaces

Advanced Programming/Practicum
15-200


Introduction In this lecture, we will take another big step (like our understanding of Object) towards generalizing our understanding of Java classes, through the use of interfaces. This step will ultimately lead to us learning about class inheritance, class hierarchies, abstract classes (and abstract methods), and other advanced topics concerning classes. But, we will start small; we will show how interfaces can be used to generalize the concept of type: at present we know only that primitive types and classes can serve as types for local variables, parameter variables, and fields in classes. We also know that a variable of the type Object can store a reference to any object constructed from a class.

An interface specifies a constraint on a class: any class that implements an interface must satisfy its constraints by defining all the methods specified in the interface. To understand all the material in this lecture about interfaces, we must learn how the four facets of interfaces interconnect.

  1. How to define an interface: each looks like a class defining only method headers (not bodies): no constructors nor fields.
  2. How to write a class that implements an interface.
  3. How to write a method that specifies the type of a parameter using the name of the interface. This includes what arguments -references to objects- can be passed to such a parameter and how the parameter can be used inside the method: to call any methods specified in the interface.
  4. How to call such a method, by constructing an object from some class that implements the interface type.
Technically, interfaces are types that some classes implement.

To illustrate all these points concretely, we will first examine two simple but powerful methods (both are public static) whose parameter types are specified by interfaces: one rejects bad values entered by the user in prompts; the other approximates the definite integral of any univariate function. Along with these methods we discuss the necessary interfaces, a few classes that implement these interfaces, and a driver application that ties all these facets together. Please download, unzip, run and examine the entire Interface Demonstration application.

Next we will examine the Comparator interface, which is defined in the standard Java library. We will use this interface (whose central method specifies Object parameters) along with the sort method defined in the Arrays class (also in the standard Java library) to learn how to sort any array: in the process, we will use lots of casting. Please download, unzip, run and examine the entire Sorting (with Interfaces) Demonstration application.

Each facet of interfaces is simple to undertand by itself: together they provide a powerful programming tool. We will discuss these four facets thrice: once for the prompting method, once for the integration method, and once for the sorting method. Because these facets are so interrelated, it is useful to view everything more than once. Like many advanced Java features, the ability to discuss them technically is critical. Once you can connect up the words, connecting up the concepts (and actually writing the Java code) becomes much simpler.


Facet 1:
Specifying Interfaces
An interface specifies a type by specifying the prototypes of what methods must be defined in any class implementing that interface. For this reason, interfaces look a lot like class definitions; but, they cannot specify any constructors and typically do not specify any fields (fields are allowed, with all sorts of special constraints, so we will delay studying them). In fact, each method specification itself is a just a header followed by a semi-colon (not the body of the header: interfaces specify WHAT methods must be available but not HOW they are implemented). Each is like a prototype (but with parameter names).

Interfaces are often very small; the classes implementing them are also as small, or they may define some other methods (as well as constructors and fields) not specified in the interface. So, for example, the DecisionInt interface (shown below and in the Interface Demonstration) is simply specified as

  public interface DecisionInt {
    public boolean isOK(int x);
  }
First, notice that the word interface appears before the name DecisionInt (we have seen class used similarly). Any class implementing the DecisionInt interface must define a method named isOK with its prototype; one int parameter returning a boolean result. All methods defined in an interface are implicitly public whether or not they include that access modifier; for this reason, some programmers never bother to write public, while I always write it for emphasis.

We will see a variety of classes that each implement this interface, with a different meaning for each isOK method (some allowing the user to enter only positive numbers, even numbers, prime numbers, or numbers in a certain range). Then we will see a method that prompts the user for an integer, and calls isOK (on some object constructed from a class that implements this interface) to decide whether the entered value is to be accepted or rejected; if rejected, the user is reprompted to enter an "OK" values.


Facet 2:
Classes Implementing Interfaces
Classes implement interfaces by (a) explicitly specifying that they do, and by (b) implementing whatever methods are required by the interface. For example, using the isOK interface specified above, we can define the IsEven class as
  public class IsEven implements DecisionInt {
    public boolean isOK(int x)
    {return x%2 == 0;}
  }
Notice the first line in this example: it defines the class name, and then using the keyword implements specifies an interface that it implements (more complicated classes can implement multiple interfaces). Sure enough, this class does supply the required isOK method, having the correct prototype. This isOK method just returns true when its parameter is an even number.

Notice that this class includes no constructor. Recall that in such cases, Java automatically supplies a constructor, written as

  public IsEven ()
  {}
Such a constructor takes no parameters and performs no actions (and obviously initializes no instance variables: IsEven defines none). We could have written exactly this code for the constructor inside the class, but in classes like this one, most programmers let Java supply this constructor automatically. Likewise, it is simple to define classes whose isOK methods allows only positive or prime numbers; their bodies would perform different computations.

For a more interesting second example, here is another class that implements the DecisionInt interface.

  public class IsBetween implements DecisionInt {

    public IsBetween(int low, int high)
    {
      if (low > high)
        throw new IllegalArgumentException
          ("IsBetween Constructor: low("+low+") not <= high("+high+")");
    
      this.low  = low;
      this.high = high;
    }

    public boolean isOK(int x)
    {return low <= x && x <= high;}
 
    private int low;
    private int high; 
}
This class specifies two instance variables, and a constructor that checks for legal values (low must be no bigger than high) before storing its parameters into these instance variables. Instead of throwing an exception when the parameters are out of order, we could just fix them, and write this constructor as
  public IsBetween (int bound1, int bound2)
  {
    low  = Math.min(bound1,bound2);
    high = Math.max(bound1,bound2);
  }
But, I'm not a big fan to automatically fixing such problems: better to throw an exception and let someone know about the problem.

In either case, once an object is constructed and correctly stores these two values, the isOK method determines whether its supplied parameter is between them. Of course, this class correctly implements the DecisionInt interface with its isOK method. What makes this class more interesting than IsEven is that it must store instance variables, and therefore uses a constructor to initialize them correctly. The isOK method in InBetween compares its single parameter to these instance variables; its prototype looks just like all the other isOK methods (it just is more complex on the inside).

Note that if a class specifies that it implements some interface, the Java compiler checks whether all the methods specified in the interface are actually defined in the class (with their correct prototypes). If not, the Java compiler detects and reports an error.

Finally, one class can implement many different interfaces (just as one method can throw many different exceptions), as long as it defines all the methods specified in each interface that it claims to implement. We will see this more advanced feature used later in the course.


Facet 3:
Interfaces as Types in Methods
Interaces are types. We can use the names of interfaces to declare variables: local and parameter variables in methods, and instance variables in classes. This simple statement leads to some extremely interesting and deep ideas in object-oriented programming. What can we do with a variable whose type is declared by the name of an interface?
  • We can store into it a reference to an object constructed from any class that says that it implements the interface.
  • We can use it to call any of the methods specified in the interface. The actual method called is the one defined in the object that such a variable refers to.
So, both of the following declaratins, and their pictures, make sense.

 

This picture illustrates a situation that we have seen only once before, when dealing with the type Object: the type of the variable is COMPATIBLE, but NOT IDENTICAL to the type of the constructed object to which it refers! But unlike the Object type, which can store ANY reference, a variable using an interface type can store REFERENCES ONLY TO OBJECTS CONSTRUCTED FROM A CLASS THAT IMPLEMENTS THAT INTERFACE. Because the IsEven and IsBetween classes each implement the DecisionInt interface, we can make variables declared from the DecisionInt interface/type refer to objects constructed from either of these classes.

Once we have variables like d1 and d2, which refer to objects constructed from classes implementing an interface, we can use them to call any method specified in the interface. So, in these case, we can use each to call only a method named isOK. Which isOK method is called depends entirely on the class of object that the variable refers to.

Given the above variable declarations, calling d1.isOK(3) would return false; it calls the isOK method declared in IsEven, the class of the object that d1 refers to. Likewise calling d2.isOK(3) would return true; it calls the isOK method declared in IsBetween, the class of the object that d2 refers to -and this object stores 1 and 5 for its bounds.

Generally, a variable specifying an interface type knows WHAT method names it can call (those specified in the interface) but not HOW these methods will compute their result (that depends on what object was constructed to store in such a variable. This rule is very echoes what we know about the Object type too.

  • Which methods a variable is allowed (by the compiler) to call depends solely on its type. Any variable declared with the DecisionInt type can call only the isOK method.
  • Which method is actually called depends on the object the variable refers to. Both d1 and d2 are declared to be of the same type: DecisionInt. Which method is called by each depends on the class of the object the variable refers to: IsEven in the first case and IsBetween in the second.
This distinction between the type of the variable and the class of the object that it refers to is a critical first step in our eventual understanding of class inheritance hierarchies.

Now, let's examine another useful prompting method; one that has a DecisionInt parameter. We will define this forInt method in a class named AdvancedPrompt. It actually specifies three parameters: a message to prompt the user, a reference to an object constructed from some class that implements the DecisionType interface, and another message -an "error" message.

  public class AdvancedPrompt {

    public static int forInt (String      message,
                              DecisionInt check,
                              String      errorMessage)
    {
      for(;;) {
          int answer = Prompt.forInt(message);
          if (check.isOK(answer))
            return answer;
          System.out.println(errorMessage);
      }
    }

  }
This method first prompts the user for an int value using the standard Prompt.forInt method, storing its result in answer (all possible problems with the user entering non-integer values are handled by Prompt.forInt). It then uses answer as a parameter to the isOK method of the object to which check refers; if the isOK method called returns true, then this method returns answer; if not, then this method prints an error message and repeats the prompt-check loop.

Notice that the only use of the check parameter inside the method is a call to its isOK method. Because we know that check refers to an object from some class implementing the DecisionInt interface (the compiler doesn't allow any arguments not constructed from such a class), we know that the object it refers to will contain an isOK method.

So, we have written a very general method. It accepts/rejects the information entered by users; its action depends on whatever object is passed as an argument to the check parameter. There might be hundreds of different criteria we want to use for different prompts, but we can alway use this method, along with a class that implements the criteria we want.


Facet 4:
Calling Methods with Interface Parameters
We can now complete the example, by showing how to call the AdvancedPrompt.forInt method. Given the declarations above, we can write
int even = AdvancedPrompt.forInt("Enter even",
                                  d1,
                                  "...not divisible by two");
which could have the following interaction in the console:
Enter even: 3
...not divisible by two
Enter even: 4
at which point the value 4 is stored into the variable even.

Likewise, we can use d2 instead of d1 and write

int selection = AdvancedPrompt.forInt("Enter selection",
                                       d2,
                                       "...not in range [1..5]");
which could have the following interaction in the console:
Enter selection: 7
...not in range [1..5]
Enter selection: 4
at which point the value 4 is stored into the variable selection.

In fact, we do not even need to store an object in a variable before calling AdvancedPrompt.forInt! We could directly write

int selection = AdvancedPrompt.forInt("Enter selection",
                                       new IsBetween(1,5),
                                       "...not in range [1..5]");
Here, the second argument is an expression that evaluates to a reference to an object constructed from the class IsBetween; its value is copied into the check parameter in the method. Here is a call frame illustrating everything (assuming the user enter 4, the method returns 4).

 

What have we accomplished with these four facets of interfaces? Primarily, we have separated the AdvancePrompt.forInt method from the method(s) that determines whether to accept/reject the value entered by the user (the isOK method, in whatever object we pass to this method, controls the semantics of choosing). In the process, we have also specified the DecisionInt interface, as what interconnects these methods.

If we want a new way to filter prompts, all we must do is plug an object from a new class into machinery that we have created: write a class that implements the DecisionInt interface, and construct an instance of that class to pass as a parameter to AdvancedPrompt.forInt. Thus, this mechanism provides a general structure for solving yet-unspeciried problems of differentiating good or bad input values.


All Four Facets:
Integration
Let's solve another completely different and general problem using interfaces. Suppose that we want to be able to write a method that allows us to approximate the area under a curve. We can do this by repeated summing the areas of small rectangles under the curve. Each rectangle's height corresponds to a value of the function F(...); each rectangle's width is the constant h. So, to compute the area under the curve from a to b, we compute F(a)h + F(a+2h)h + F(a+3h)h + ... F(b-h)h, as illustrated in the following picture. (This is not the best way to approximate this area, but it is the simplest.)

  Let's examine the four facets of interfaces to solve this problem. First, we must specify the interface, which supplies a method to compute the value of a function of one variable: a univariate function. We don't know how this will be done (just as we didn't know how a method would compute whether to accept/reject a value), but we know what the prototype of the method must be. So we can write this interface simply (as simply as DecisionInt)
  public interface Univariate {
    public double evaluate (double x);
  }

Second, we must write a class that implements this interface. As an example, let's write a class that can easily represent all quadratic forms: ax2+bx+c.

  public class Quadratic implements Univariate {
  
    public Quadratic (double a, double b, double c)
    {
      this.a = a;
      this.b = b;
      this.c = c;
    }
  
    public double evaluate (double x)
    {return a*x*x + b*x + c;}
  
    private double a,b,c;
  }
Third, we must write a method that approximates the integral for an arbitrary univariate function. We will call such a method integrate and define it in the AdvancedMath class as follows
  public class AdvancedMath {

    public static double integrate (Univariate f,
                                    double low, double high,
                                    double step)
    {
      double sum = 0.;
      for(double x = low; x < high; x+=step) 
        sum += f.evaluate(x) * step;
      return sum;
    }
  }
Note that we use the parameter f just once in this code, calling its evaluate method for all the different values of x generated by the for loop (so it does get called many times when the method executes: once for each rectangle). Note too that this for loop uses double values: a legal, but not frequent choice.

Finally, let's combine all this information to write a call that approximates the area under the curve 2x2-3x+5 between 1 and 3.5 using a step-width/h of .01. We can do this in one line.

double area = 
  AdvancedMath.integrate(new Quadratic(2.,-3.,5.), 1., 3.5, .01);

We can also define many other methods in the AdvancedMath class that operate on univariate functions: finding 0s, computing maxima/minima, approximating derivatives, etc. We can also define many other classes that implement the Univariate interface: cubics, exponentials, trigononmetric functions, and combinations of all these. Then, we can mix and match these as necessary: say, use the findZero method on an object constructed from the Cubic class. The more we build these libraries, the more likely we are to find debugged classes that we can use directly from them in our new applications.


The Comparator Interface and the sort method in Arrays The standard Java library contains an interface named Comparator, defined in the java.util package. Find the Javadoc for this interface and scan its documentation. Notice that in the All Classes pane -interfaces appear here too- its name appears italicized as Comparator. In fact, the names of all interfaces appear here italicized, so you can quickly tell whether an identifier names a class or an interface (these are the only two possibilities). The Comparator interface is defined by
  public interface Comparator {
    public int     compare (Object o1, Object o2);
    public boolean equals  (Object obj);
  }
Of these two methods, compare is the most important by far. Primarily, classes implementing this interface use compare to perform a trichotomous comparison of two arguments: telling whether the first parameter is (a) less than (returning any negative number) the second parameter, (b) equal to (returning 0) the second parameter, or (c) greater than (returning any posive numbers) the second parameter.

Interfaces are general to being with, allowing lots of different class to implement them. In addition, this interface uses the type Object for parameters in the compare methods. This is a tipoff that this interface is very general and very useful!

Let's first write a class implementing the Comparator interface for objects constructed from the Integer wrapper class. Then we will see how to use objects constructed from this class in a sorting method in the standard Java library.

  public class IntegerComparator implements Comparator {
    public int compare (Object o1, Object o2)
    {
      Integer i1 = (Integer)o1;
      Integer i2 = (Integer)o2;
      return i1.compareTo(i2);
    }

    public boolean equals(Object obj)
    {return (obj instanceof IntegerComparator);}
  }
Notice that the compare method first casts both parameters and stores their references into Integer local variables; if either cast fails, Java will automatically throw a ClassCastException. Then it calls the compareTo method on the first Integer, using the second as an argument. This method conveniently is defined in the Integer wrapper class (look it up) to compute the trichotomous result easily: it returns exactly the value that we want compare to return! In fact, because the non-static compareTo method has the prototype int compareTo(Object) we could simplify the body of compare to just return ((Integer)o1).compareTo(i2); because compareTo will by itself cast its argument to be an Integer. (Why can't we write just return o1.compareTo(o2);?)

The equals method just returns whether obj is also an instance of this IntegerComparator class; any two instance of this class have the same state, because the class stores no instance variables that can be different! Thus any two IntegerComparator objects will return the same results when their compare methods are called.

Finally, Java will write automatically write the constructor

  public IntegerComparator()
  {}
Now, let's see how to use an object constructed from the IntegerComparator class. Examine the sort method in the Arrays class, which is defined in the java.lang package. It is a highly overloaded method. The prototype that we are most interested in is
  public static void sort(Object[] a, Comparator c)
First, note that this method is static; as in the Math and Prompt classes, all the methods here are static. When called, it will efficiently sort any Object[] (Object array) using the ordering specified by a class implementing Comparator. It actually permutes the values in the parameter array a such that it has the postcondition
  • a stores all its original values, as many times as they originally occurred (e.g., there might be duplicates, etc.)
  • these values are ordered in the array such that
      c.compare(a[0],a[1]) <= 0 and
      c.compare(a[1],a[2]) <= 0 and
      ....
      c.compare(a[a.length-2],a[a.length-1]) <= 0
That is, it sorts Object[] a according to Comparator c.

So, if Java executed the code

  //Declare arrays
  int[]     temp = new int[]{3,7,4,1,10,2,8,5,9,6};
  Integer[] x    = new Integer[temp.length];

  //Fill wrapper array x with values from temp
  for (int i=0; i<temp.length; i++)
    x[i] = new Integer(temp[i]);

  //Sort wrapper array
  Arrays.sort(x,new IntegerComparator());

  //Print wrapper array
  for (int i=0; i<x.length; i++)
    System.out.println(x[i]+" ");
Then it would print 1 2 3 4 5 6 7 8 9 10: sorting the array in ascending order (technically, non-descending order because a value can be followed by another one that is equal to it).

How hard would it be to sort the array in the other direction: from biggest to smallest. We could just replace the call

  Arrays.sort(x,new IntegerComparator());
by
  Arrays.sort(x,new ReverseIntegerComparator());
and define
  public class ReverseIntegerComparator implements Comparator {
    public int compare (Object o1, Object o2)
    {
      Integer i1 = (Integer)o1;
      Integer i2 = (Integer)o2;
      return -i1.compareTo(i2);   //Notice negation!
    }

    public boolean equals(Object obj)
    {return (obj instanceof ReverseIntegerComparator);}
  }
By negating the original returned result
  • if i1 normally compares less than i2 (returns a negative value, meaning i1 should occur in the array before i2) the result this method returns will be positive (indicating that i1 is considered greater than i2 and belong after it in the array)
  • if i1 compares equal to i2 (returns 0, -0 is returned, the same as 0
  • if i1 normally compares greater than i2 (returns a positive value, meaning i1 should occur in the array after i2) the result this method returns will be negative (indicating that i1 is considered less than i2 and belong after it in the array)
Remember that the smaller values, according to the comparator, appear towards the front of a sorted array.

In fact, we can write a class with the following amazing property: its constructor will take any Comparator and produce an object that is also a Comparator, but one that returns the opposite result.. With this class in our library (ReverseAComparator is in fact in the course library), we can instead of the above write

  Arrays.sort(x,new ReverseAComparator(new IntegerComparator()));
We can use an object constructed from ReverseAComparator whenever we want to reverse any comparator that we have already written, no matter what types the comparator is comparing! It stores the object it is constructed with and calls its compare method, negatinge the result, whenever its compare method is called. We can define this class as follows: it isn't long and therefore it should be easy to understand. But, I'll admit, understanding is tortuous; it is much easier to understand how to use this class than to understand how it works; or put another way, we can easily use it without caring how it works: the pinnacle for useful tool. It is also beautiful.
  public class ReverseAComparator implements Comparator {

    public ReverseAComparator(Comparator c)
    {realComparator = c;}

    public int compare (Object o1, Object o2)
    {return -realComparator.compare(o1,o2);}

    public boolean equals(Object obj)
    {
      return (obj instanceof ReverseAComparator) && 
              realComparator.equals(
                ((ReverseAComparator)obj).realComparator);
    }

    private final Comparator realComparator;
  }
This class uses a decorator pattern: it produces an object on which we can call the same methods as the object that it was constructed with: it decorates the original object. I vastly prefer to write and debug a general class and use it multiple times than write multiple classes. Decorators capture exactly this desire.

Notice what equals checks: obj is an instance of ReverseAComparator, and that both realComparators are equals. Here there is state in the class, so we need to check it for equality.

As a final example of a class that implements Comparator, imagine that we have declared DiceEnsemble[] ds; and stored into it a reference to an array object filled with references to DiceEnsemble) objects. Now, suppose that we wanted to sort these ensembles, so that the fewer times they were rolled, the early they appear in the sorted array. Here is the class needed by Arrays.sort:

  public class RolledComparator implements Comparator {
    public int compare (Object o1, Object o2)
    {
      DiceEnsemble d1 = (DiceEnsemble)o1;
      DiceEnsemble d2 = (DiceEnsemble)o2;
      return d1.getRollCount() - d2.getRollCount();
    }

    public boolean equals(Object obj)
    {return (obj instanceof RolledComparator);}
  }
Notice how simple this class is to write; it looks just like the other classes we wrote implementing the Comparator interface. Thus, we should consider it simple to use Arrays.sort to sort any array, using any ordering criteria; all we need to do is to write a simple class implementing the Comparator interface that establishes the ordering, and call Arrays.sort with it and the array to sort. So, it would also be simple to sort a DiceEnsemble[], either in increasing or decreasing order, the number of times each ensemble was rolled, by the number of dice in each ensemble, by the sum of the pips showing in each ensemble, etc.


Loose Ends In this section we will discuss a few loose ends about interfaces. First, what happens if a class does not specify that it implements an interface (doesn't use that keyword in its definition) but actually defines all the methods that the interface specifies? Can we construct an object from this class and store it in a variable whose type is the interface? The answer is no.

More concretely, if we define (notice, no implements DecisionInt)

  public class IsPositive {
    public boolean isOK(int x)
    {return x > 0;}
  }
then we CANNOT write
  int answer = AdvancedPrompt.forInt("Enter Positive",
                                      new IsPositive(),
                                      "...bad choice");
The Java compiler believes that an object constructed from the class IsPositive cannot be passed to a parameter whose type is specified by DecisionInt. So, when it comes to interfaces, it does not matter whether or not a class implements the specifications in an interface: it does matter whether or not its definitions says that it implements the interface. Of course, if it says it does, but really doesn't, the Java compiler will detect and report an error when it tries to compile that class.

Second, what happens if we have a variable whose type is specified by an interface, and we try to use it to call a method that is not specified in the interface. Whether or not the object that it refers to has such a method, the Java compiler will detect and report an error. The only methods that the Java compiler allows to be called on a variable whose type is an interface, are those methods specified in the interface. Again, this is very important for our future study of Java.

Third, even if we leave off public when specifying an interface, it is declared public by default (the same holds for all its method specifications). The whole purpose of an interface is to specify methods that a class must define, that we can call: so they can be only public.

Fourth, can we construct an object from an interface? Again, the answer is no. If we tried, the Java compiler would detect and report an error. So, writing DecisionInt d = new DecisionInt(); is MEANINGLESS to Java and NOT ALLOWED. Thus, the declare/constuct pattern that we've seen does not work for interfaces (but see below for how we can use an interface to construct an object from an anonymous class)!

Finally, in reality interfaces can also specify public static fields. Although doing so it legal, it is not often done. In fact, even if the fields are not specified with these access modifiers, they are automatically applied by Java.

One more generalization. We can use the names of interfaces in instanceof and class casting. If x is the name of a reference variable and I is the name of an interface, Java's returns true for x instanceof I if x refers to an object constructed from any class that implements I. If we cast (I)x, Java treats the resulting reference as if it refers to an object constructed from any class that implements I; thus, we can call any methods specified in I on the casted reference, or store it in a variable whose type is specified by I.


Anonymous Classes Sometimes we want to construct just one object from a class that implements an interface; and often, that class is very small: it defines few members because interfaces are often small. If that class has no constructor too, Java allows us to construct an object from it anonymously, by using the name of the interface directly.

Before we get to the main example of an anonymous class, we need to deal with one more point. When we study inheritance, we will learn that every class automatically inherits an equals method; if we never call this method, there is no reason for another class to override it. When calling Arrays.sort with any class implementing the Comparator interface, the equals method is never called, so we can inherit its (incorrect) definition and not bother redefinining it, and sorting still works. Thus, we really have to define only the compare method.

Now, Java allows us to construct an object from an anonymous class by directly using an interface. For example, we can call

  Arrays.sort(x, new Comparator() {
                  public int compare(Object o1, Object o2)
                  {return ((Integer)o1).compareTo(o2);}
                 });
Here, for the second argument, Java constructs an object from an anonymous class, using the Comparator interface. The anonymous class implements this interface by defining compare, the one required method (not inherited). Note that we don't need to cast the argument to compareTo because the parameter specifies Object. Writing this is the equivalent of writing
  public class SomeName implements Comparator {
    public int compare(Object o1, Object o2)
    {return ((Integer)o1).compareTo(o2);}
  }

  Arrays.sort(x, new SomeName());
Sometimes programmers construct objects from anonymous classes when they don't want to go to the "bother" of definining a class; technically in Java, every class should be placed in its own file (with the same name as the name fo the class), which requires even more work. We will find it convenient to use anonymous classes sporadically.

Problem Set To ensure that you understand all the material in this lecture, please solve the the announced problems after you read the lecture.

If you get stumped on any problem, go back and read the relevant part of the lecture. If you still have questions, please get help from the Instructor, a CA, or any other student.

  1. Write a class named IsSquare that implements the DecisionInt interface, whose isOK method returns true when its parameter is a perfect square. Hint: use casting, Math.sqrt and one (or more) relational operators.

  2. Write a class named Contraction that implements the Univariate interface, whose evaluate method computes the formula Math.sqrt(1. - v2/c2) where c represent the speed of light (assume it is 2.99x108 meters per second)

  3. If we construct the object new IsBetween(5,5), for what values (if any) will its isOK method return true?

  4. Rewrite the integrate method to use the trapezoid rule: the height of the rectangle for the interval[x,x+h] is [f(x)+f(x+h)]/2. Write a version that computes f(x) only once for each value of x.

  5. Read the Javadoc of the Point class, which store int x,y coordinates. Assume that we declare Point[] ps; and store in it a reference to an array object filled with Point objects. Write a class named OriginDistance that implements the Comparator interface. By calling Arrays.sort(ps, new OriginDistance()); the Points in the array should be sorted from closest-to-the-origin (coordinate 0,0) to farthest away from the origin.

  6. Read the previous problem. But this time, generalize the class you are to write. Call it DistanceFrom and allow passing its constructor a Point object. The Points in the array become sorted based on how close they are to the specified point (closer ones appear earlier in the array). For example, calling
      Arrays.sort(ps, new DistanceFrom(new Point(0,0)));
    would produce the same result as in the previous problem.

  7. Assume that we declare
      Comparator c1 = new IntegerComparator();
      Comparator c2 = new ReverseAComparator(new ReverseAComparator(c1));
    • Will c1.compare(x,y) ever return a different result from c2.compare(x,y)? If so, for what value(s).
    • Will c1.equals(c2) return true or false?

  8. Assume that the postconditon of sorting Object[] a by Comparator c was only
      c.compare(a[0],a[1]) <= 0 and
      c.compare(a[1],a[2]) <= 0 and
      ....
      c.compare(a[a.length-2],a[a.length-1]) <= 0
    (constrast this with the actual, two part postcondition above) Describe how sort could work to satisfy this postcondition, but not satisfy our intuitive understanding of sorting.

  9. Why can't we write a constructor for an anonymous class? How can we still use an anonymous class when calling AdvancedPrompt.forInt when trying to accept values only in a specified range (as in the IsBetween class)? Hint: What other way, besides a constructor, can we use to initialized instance variables.

  10. Write a class named NegateADecision that implements the DecisionInt interface, and performs the equivalent purpose as ReverseAComparator.

  11. Which is Java's rule for automatically writing the constructor
      public class Name()
      {}
    1. It is automatically written if NO PARAMETERLESS CONSTRUCTOR is defined in the class.
    2. It is automatically written if NO CONSTRUCTOR is defined in the class.

  12. If an interface specifies
      public boolean isOK(int x);
    is it legal or not for Java to write a method implementing that interface with a different parameter name? e.g.,
      public boolean isOK(int i);

  13. Without using the negate operator, rewrite the line return -realComparator.compare(o1,o2); in a clever way to return the correct result. Argue which way is simpler to understand.