CS210 Tuesday, Feb. 7
 

Binary Search—much better, used in serious apps

Needs an ordered array.  Look at int array example, using code from Weiss, pg. 185 specialized to int array case.

// ordered array of ints for binary search, set up in
// the caller of binSearch:

int[] a = { -10, -2, 5, 9, 50, 1000};
//           0    1  2  3  4   5

public static int binSearch( int [] a, int x)
{
     int low = 0;
     int high = a.length – 1;
     int mid;
     while (low <= high) {
          mid = (low + high)/2;
          if (a[mid] < x)
              low = mid + 1;   // x is to right of mid
          else if (a[mid] > x)
              high = mid - 1;    // x is to left of mid
          else return mid;            // x matches mid
     }
     return -1;   // no match
}

Example: Search for x = -10:  Here we will trace the code, that is, show the values of each important variable or expression as the program executes:

low= 0, high = 5
0 <= 5, do body
mid=  (0 + 5)/2 = 2, a[2] = 5, so a[mid] > x, set high = 1    (pass 1 of loop)
0 <= 1, do bodyS
mid=  (0 + 1)/2 = 0, a[0] = -10, matches x, return 0.      (pass 2 of loop)

Java Types

including ArrayList<T> for generic types.  We also need Comparable<T>.

Look at handout to see how Comparable<Shape> is implemented

Java type categories, used for object types and program variable's types:

Note: Instantiation of arrays of parametrized objects are not allowed: example "new ArrayList<String>[10]", so no object has this type. Sometimes we use array of Comparable<T> as a type in a method argument, however, so such types exist in the system.

A parametrized type inside the <> can be an array (arrays are full-fledged object types):  example ArrayList<String[]>, even ArrayList<int[]>, because int[] is an object type.  It can also be a parametrized type, as in ArrayList<ArrayList<String>>, which creates a list of lists of Strings.

The IS-A Relationship

Crucial idea in type compatibility: IS-A relationship:  A IS-A B  where B is more-basic than A.  Means A can act like a B, that is, respond appropriately to all of B's API.

Consider a type hierarchy among simple object types:

                                Object
                          class Base  implements I1
        Derived1: must also implement I1, could also implement I2     Derived2

a Base object IS-A Object, IS-A I1,   but is not IS-A Derived1, etc.
a Derived1 object IS-A Base, IS-A I1, IS-A I2, IS-A Object,   but is not IS-A Derived2
Note that the fact that a subclass cannot remove a method  (or make it private, etc.) from its superclass API is important here.

so A IS-A B if A is below B in the type hierarchy where extends and implements count the same way.

Can add array types to this:  if A IS-A B, then A[] IS-A B[].  Discussed on pg. 108.  Not all languages support this feature, but Java does, at cost of some complication.

If A IS-A B, then is ArrayList<A>  IS-A ArrayList<B>?  No, arrays are special in that they don't do anything to their elements, just hold them, and parametrized types can do more, so they can't be trusted to maintain all the behavior of the base type.  ArrayList might be OK this way, but other parametrized types might not.
Note that this doesn't prevent us from using an ArrayList<Shape> for various holding Squares and Circles.  It just means the if we set up an ArrayList<Square>, we can't assign the whole thing to an ArrayList<Shape>.

The real place this non-compatibility of parametrized types gets in our way is with Comparable<T>.  Comparable<Shape> and Comparable<Square> are different types, no compatibility.  Very annoying.
Comparable<Shape> means any Shapes can be compared with compareTo().  Comparable<Square> means any Squares can be compared, and Squares are a subset of Shapes.  We should be able to use Comparable<Shape> where Comparable<Square> is called for, but we can't because they are not type-compatible.

Armed with knowledge of IS-A, we can tackle the execution enviroment, the JVM.

Every object gets born via new, with a certain "runtime" type, which never changes.
new Box(10), new ArrayList<Box>, new Box[10], etc.

We often put the resulting object ref in an object type variable:
Box b = new Box(10);

Here variable b has type Box, so does the object it holds.  Easy case.

An object variable keeps its original type as the code runs.  An object variable is like a socket where an object can be plugged in.  The variable/socket has a type and the object has a type.
An array is an object with a certain type "T[]", and provides a sequence of sockets all for objects of type T.  It's like a power strip, when in use plugged into a socket and providing more sockets.

As the code executes, the JVM makes sure that for any object variable x, the object held by x  IS-A <x's type>, and causes an exception if it finds otherwise. Similarly for array spots.
This is true for all object types, even generics.  Some of the special rules about generics are there to keep this true.

For non-parametrized types, you can see what the JVM knows about x's object's type by calling x.getClass().
For parametrized types, the JVM knows only the raw class, without the <...> part.  This is called "type erasure".
For example, for ArrayList<String> the JVM knows only ArrayList, and uses the one and only ArrayList code for all <T>'s.

Whenever the compiler can detect a definite type compatibility problem it tells us at compile time.

Base b = new Derived();
b's type: Base, object's type: Derived.   Always OK because Derived IS-A Base.  

Derived d = new Base()  Never OK, "incompatible types" error from compiler,

In general, an object variable of type T can hold any object that can say it IS-A T.

Sounds easy, where's the messy part?

Downcasts

When three types get involved, as an object gets passed around from one variable to another.
Say the object variable b (itself of type Base) holds a Derived object, and then we put

Derived x = (Derived)b;     // a "downcast" Base->Derived and so needs a cast
Here x's type = Derived, b's type = Base, b's object's type = Derived, so it can work.
But if b's object's type is Base, the JVM throws an exception to avoid ending up with a Derived variable holding a Base object.

This is the case where we need a cast, to tell the world that we realize this is a special action, one called a "downcast", because x's type is lower in the inheritance hierarchy than b.

When we read code, we should feel a little tingle of worry when we see a cast, knowing that it is marking a place where things could go wrong and cause an exception.

We don't want to put in unneeded casts, needlessly worrying our readers and distracting them from other things in the code.

Since Object is at the top of any type hierarchy, assigning from type Object to any other is always a downcast and needs a cast operation.
You can cast from Object to int[], for example, but this will cause an exception at runtime unless the object being passed really is an int array.

Because the JVM doesn't know the individual parametrized types, it can't check a cast to a parametrized type.  So such casts provoke warnings at compile time.  It turns out that sometimes such a cast is really needed, so the Java compiler group didn't disallow this completely.  They did disallow "instance of" checks on parametrized types.

We have now covered the "restrictions on generics" (Section 4.7.6) that are needed to use ArrayList<T>.  The subsections on "static contexts", "instantiation of generic types", and "generic array objects" are only needed for implementing generic classes, i.e, writing the code for ArrayList<T>, which we are not yet ready to do.

Processing ArrayList<T> and array of T in methods

Although we don't want to implement generic classes yes, we do want to be able to write code that can process ArrayList<T> objects.  Here is a simple example.

void printList(ArrayList<?> a) {    //wildcard ? stands for any type
   for(Object o:a) {
      System.out.println(o);
   }
}
    <AnyType> void printList1(ArrayList<AnyType> a) {  // another way to put it: using "dummy type variable" AnyType
for(Object o:a) {
System.out.println(o);
}
}
This code works because all we need from the element type is toString(), part of  the Object API. You can add "private static" at the beginninng of these.
Similarly for an array of any type, we can use the second form:

<AnyType> void printArray(AnyType[] a) {   // or we could use (Object[] a) and avoid parametrization
   for(Object o:a) {
   System.out.println(o);
   }
}

What if we want to use a method of the element type that belongs to our application class? Say area() if these are Shapes. Then we need something more restrictive than ?.
Since area() is a method of the base class Shape, it's easy:

void printAreas1(ArrayList<Shape> a) {
   for(Shape s:a) {
   System.out.println(s.area() + " ");
   }
}

void printAreas2(Shape[] a) {
   for(Shape s:a) {
   System.out.println(s.area() + " ");
   }
}

Note that printAreas1 doesn't accept ArrayList<Square>, but that can be fixed: see pg. 133. printAreas2 does accept Square[] as is.