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:

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.

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).