Comp 271 Week 2
Damen 339, 4:15-5:30+5:30-6:20 Tues, 4:15-5:30 Thurs
Four Classes
They were Point, Association, BankAccount, and
Ratio. Each class had two fields. All had field accessors for both.
Point also has field mutators to update the two fields independently.
Association has a mutator only for the second field; once you create an
Association object, you never change the key field. BankAccount is
similar: once created, the account field is never changed.
Ratio has mutators for neither field; they are updated together, by
mathematical operations that act on both fields in a consistent manner.
toString()
Note the toString() method of the Ratio class. You can call this
explicitly whenever needed, as r.toString(). However, note that toString() works (for us) more
generally; it is our first example of inheritance.
(There is nothing special about toString(); any method can, in the
right circumstances, have its workings affected by inheritance.) The
master parent class Object defines toString(); any subclass can override
that definition, as is being done here. In println(), when something
needs to be printed then toString() is called implicitly; the rules of
inheritance ensure that the most specific version of toString is the one to be invoked.
Class demo:
- change Ratio.toString() so it prints differently.
- verify that conversion is not automatic; we can't do String s = r1. Println must be explicitly calling toString!
ArrayList sizes
Last week we did a demo with the inspector and a real ArrayList:
sizes were 10, 16, 25, 38, ...
Demo:
arraylisttest (I have not put this online), with methods addOne(String
s) and addSome(int count, String s). I also added
addArray(ArrayList<String>) to add a bunch at once.
Here is the actual ArrayList.java source code; let's find where the numbers 10, 16, 25, .. come from. Note that the code here is not the most recent version.
Note that if the current size is, say, 16, and we're adding a
collection of 14, we can proceed in one of two ways. We can apply the
3/2 approach to 16 to get a new capacity of 25, which isn't enough so
we increase again to 38. Alternatively, we can apply the 3/2 rule to
30, and just start off with 45. What does ArrayList.java actually do?
What does our source code say it should do?
add(String [] sa) would be pretty easy to add too. You would probably want to resize the underlying array things[] only once.
Lists
Just what is a list, anyway? What are some basic operations?
The List interface defines some; class AbstractList defines this
another way. But these approaches are very java-centric! Other
languages see lists slightly differently; what are the core
similarities?
Bailey §3.1: some problems with using arrays as lists. The fixed size means that you
either commit to a very large size, or put the burden on the user, or
run out of room. This suggests that dynamic sizing is a major part of what a List is.
Bailey's version of Vector in Chapter 3 does not use generics, which
means that Vectors are Vector<Object>. This means that when you
take anything out, you have to cast it to what you want:
String s = (String) V.get(i)
We need to be able to move through a list sequentially, and we need to
be able to add things. The LISP language prefers to add things to the
front of a list; java prefers to add at the end. Adding in the middle
is also possible.
Class demo: ObjList
This implements a MyList as an array of Object; it is like ArrayList<Object>. Here we need the inheritance hierarchy. Every variable can be assigned a variable of that type, or of any subclass type. Thus, a variable of type Object can take values of any class type. This is the Subclass Assignment Rule.
While we're here, it might be a good time to point out that when it comes time to invoking any methods
on the contents of a class variable when the Subclass Assignment Rule
is in place, then the method is invoked as if the variable's type were
adjusted to match the object's type. That is, if we have
Object o = new Ratio(3,5);
System.out.println(o.toString());
then there is on the face of it an ambiguity:
do we use the toString() defined in class Object, or the version
defined in Ratio? We use the latter; this is sometimes called the
most-specific-method rule, and lies at the heart of inheritance.
Note that it is somewhat mysterious when we need the cast and when we
do not. main() works without a cast! (why?) But concat does not. Well,
sort of it does too! What is going on? Basically, if the operation on
Object makes sense, it works. When we're printing, Object.toString() is
called, which, for things that are really Strings, returns itself as a
String. When we're trying to concatenate strings, toString() is again
called implicitly.
Note also that nothing prevents us from putting the wrong thing into
the list! Method addWrong() adds the list to itself, and also an
Integer object. It isn't so much that "heterogeneous" lists are
intrinsically bad, as we sure didn't intend here to be including anything but Strings!
Generic classes
How can we fix ObjList?
Let's look at the example from Bailey §4.2.1, pp 72-73, of class Association<K,V>.
K and V (for Key and Value) are in the angled brackets of the first
line of the class declaration, but in the body they become simply
types. If we wanted an ArrayList of K within the body of the class, we
would declare it as ArrayList<K>.
MyList
Take 1: just use EltType in the array declaration. We get an error
"Generic array creation". This turns out to be a no-no, for deep
cryptic reasons. When we create arrays, we cannot create arrays of
generic type.
Take 2: make the array an array of Object, like ObjList.
We notice with Take 2 that we now get an annoying message about dangerous operations, due to the cast operation in get(). Is this a real risk? No!,
because we've made sure that the set() method can only put EltType
things into the MyList, so it is safe to assume that get() will only
retrieve these. (If we made the fields public, we couldn't be sure of
this, and if we made them protected, then someone defining a subclass
could break this.)
Take 3: add the line
@SuppressWarnings("unchecked")
before the get() method (which is the only one with a cast right now). The line will suppress compiler warning pertaining to unchecked type issues.
Thursday
What do get() and set() do for out-of-range index values? Soon we will convert to throwing exceptions!
Another classic example of the most-specific-method rule has to do with a master class Shape, with
subclasses Rect and Circle each defining a draw() method. If we write
Shape[] sa = new Shape[2];
sa[0] = new Rect(...);
sa[1] = new Circle(...);
sa[0].draw(); sa[1].draw();
then the last line does indeed properly invoke Rect.draw() and Circle.draw() respectively (provided there is a Shape.draw() as a placeholder).
MyList.equals()
Note how two MyLists are equal if they have the same length and if the
corresponding values are equal. Element type doesn't need to enter into
it, and it's not even clear if it helps any.
MyList doesn't have a remove() operation, so all unused slots are
always null. However, we could easily add that, and have two MyLists
with the same currsize and same data up to that point, but wildly
different data beyond that point.
hashcode(): sneaky issue regarding .equals(). Java assumes that two objects that are
.equals() to each other will have the same hashcode(). But if
hashcode() is implemented by adding up the hashcodes of all the cell
contents, even beyond currsize, this will fail! (Java does this so it
can use different hashcodes as a fast way of determining that two
objects are not .equals() to each other).
Search and insert costs
These operations both take, on average, currsize/2 steps, or N/2 if we
follow the convention that N represents the current size. For search,
we have to check on average half the list; the steps we are counting
are the comparisons. For insert, the operations are the assignments
things[i+1]=things[i].
Vector: skipped
Association
The generic form in Chapter 4, §4.2.1. Note that,
because no arrays are involved (with the peculiar
no-generic-array-creation rule), the generic Association class is a
cleaner example.
Back to Chapter 3's Vector (§3.4)
Adding in the middle: you need to move from right to left. See the picture on Bailey p 52.
§3.5: analysis of costs of expansion.
-
The number of moves to insert at a random place in the middle of a list of length N is, on average, N/2.
- The number of compares to search a list of length N for a random element that is in fact present is, on average, N/2.
- The number of compares to search a list of length N for an element not there is simply N.
These costs are all linear; that is, proportional to N.
Now suppose we want to insert N items into a list initially of length
0, perhaps searching the list each time in order to insert in
alphabetical order. Each item's required position is more-or-less
random, and so takes on average size()/2 moves. That is, to insert the
1st element takes 0/2 moves, the 2nd takes 1/2, the 3rd takes 2/2, the
4th takes 3/2, the 5th takes 4/2, etc. Adding all these up gives us a
total "average" number of moves of
1/2 + 2/2 + 3/2 + 4/2 + 5/2 + ... + (N-1)/2 = 1/2*(1 + 2+ 3 + ... + (N-1))
= 1/2*(N(N-1)/2) = N2/4-N/4
Now, for large N this is approximately N2/4, that is, proportional to N2, or quadratic.
SetVector: §3.7
using vectors/Mylists to implement
an abstract Set. Note the more limited set of operations; there is no
get() and no set().
add() now works very differently: add(E e) is basically if (!contains(e) ) add(e), where the second add(e) is Vector.add(e).