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:
- primitive types
- object types (defined by classes and interfaces), can be categorized:
- simple object type (not array, not parametrized)
- array object: array of simple type T[] or array of array T[][], or ...
- parametrized type: example ArrayList<String>
- array of parametrized (interface) type: example: Comparable<T>[] used in a method parameter
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.