Lab 1

Identity Crisis
Due by 8pm, Sunday 16 Sep 2012

In this starter lab, you will create your first implementation of a data structure from the Java Collections Framework. You will also learn how to use Generics and get some practice writing test cases for you program. The purpose of this lab is to:

If you've not done so, I suggest you do Lab 0 first.


Part 1 - MyArrayList Basics

You'll be constructing your very own implementation of the ArrayList data structure so that you understand its inner workings. You should use the prefix "My" in your class names so that you don't accidentally use the standard ArrayList in your testing. However, you will be matching its behaviour, so when in doubt, you can refer back to the ArrayList documentation for clarification and/or use an ArrayList as a working reference solution.

Your MyArrayList class will implement the Java List interface. That is, you will need to provide implementation for all of the (abstract) methods contained therein. There are more than 20 such methods, however, and that could take you awhile to get through. Moreover, some of the methods are "redundant" in the sense that they can be implemented using the other methods (for example, you can implement isEmpty() by returning (size()==0)). Fortunately, the folks at Java have provided a lovely abstract class AbstractList that provides some very basic and default behaviour for a List. Some of the methods still aren't implemented (that is, they are abstract) and some of them may have inefficient implementation (that is, you'll want to override them), but it's useful nonetheless. Thus your MyArrayList class should extend AbstractList in order to reap the benefits; because AbstractList implements the List interface, you will implicitly be required to do so as well (but do not have to declare your intention to implement explicitly).

You'll be using generics for your implementation, and taking advantage of the fact that List and AbstractList are similarly parameterized. Just declare your class like the following:

    public class MyArrayList<AnyType> extends AbstractList<AnyType>

AbstractList gives you all methods except for get(int index) and size(), however, you'll need to override many other methods to get your ArrayList in fighting form.

You should be using Javadoc style comments in your programs. You can use the following command to generate the Javadoc comments when you are done with your assignment. However, please delete them before submitting your assignment.

    % javadoc -d docs -link http://cs.oberlin.edu/~gr151/jdk-1.6/docs/api *.java

Basic Design

As you might expect, the backing storage for an ArrayList is an array. That is, an ArrayList is really just an array with the extra functionality of dynamic resizing. The MyArrayList's array will not always be full because an ArrayList may change its size when the underlying array does not; therefore, your class will need to keep track of the number of items that are currently in the underlying array according to the ArrayList (which will at most times be different than the capacity/length of the array). Also, you need to keep the data in the array packed to the front so there are no gaps.

Private data

Add the following members to your MyArrayList class.

int size
number of items currently stored in the list (which may differ from the list's current capacity)
AnyType[] data
backing storage for MyArrayList

Constructors

Implement the following constructors in your MyArrayList class.

MyArrayList(int startSize)
startSize is the initial guess as to the number of items in the list. You should just round this up to the nearest power of 2 and use that as the initial max capacity. (You can just start with the value of 2 and keep doubling that until it is no smaller than the requested size.)
MyArrayList()
Use an initial capacity of 2. NOTE: you should not be duplicating code from your other constructor in this one. Have this constructor call the other with appropriate arguments (that is, use this(2)).

You may get a compiler warning about allocating an array of a generic type. This can be solved by using casting and a special marker in the source code to suppress the warning. Allocate in a manner similar to:

    AnyType[] someArray=(AnyType [])new Object[numElements];

and have a line

    @SuppressWarnings("unchecked")

between your JavaDoc comments and the method header. This doesn't remove the warning in 1.5, but does in 1.6, and it will get rid of the error message in Eclipse.

Private Methods

You will need to implement a private resize method that doubles the size of the array (do not just increase its length by one. Hopefully you saw on the prelab how inefficent that is!) by creating a new array of twice the current size, copying the data over, then resetting the data pointer to point to the new array.

Feel free to write more private methods as you see fit.

Public Methods (part 1. More to follow.)

Implement the following methods in your MyArrayList class

int size();
Return the number of items stored in the array, not the maximum capacity (i.e, not the size of the data array)
void add(int index, AnyType element);
Adds the element at the specified index, shifting everything else right one space.
You are allowed to add at any index location up to and including the current size. (index==size is the first unused location) Resize the array if necessary.
Throw an IndexOutOfBoundsException if the index is not within the allowed range.
(Remember, an Exception is just an Object, so you create it just like you do a regular object. For example, you may try the following:
	throw new IndexOutOfBoundsException();
or even better:
	throw new IndexOutOfBoundsException("Index Out of Bounds! You tried to get " +
	index + " but the size is " + size );
	
. Or something to that effect.)
boolean add(AnyType element);
Adds an item to the end of the array.
This method returns true if the item was added. Since you will always be adding the item specified, you should always return true.
AnyType get(int index);
Return the value stored at the given index.
Throw an IndexOutOfBoundsException if the index is not within the allowed range. Note that this range is smaller than the range allowed for add().

Good programming style suggestions

When the array is full, and a user tries to add something in, you should double the size of the array (rather than just increase the size by one), copy the old array into the new array, and add the new element. You'll need to copy the previous items into the same slots in the new array. You should probably have a private method for this array resizing. I'd suggest printing yourself a message when it is called at first indicating what size it is and what size it becomes. Just be sure to remove it after doing some testing.

Also, take advantage of your existing methods and don't duplicate logic. You've got two constructors and two add() methods. Only one needs to do the real work, the second should just figure out a way to call the first.

You should include a meaningful message when you construct the IndexOutOfBoundsException. (For example, when Java's ArrayList throws an IndexOutOfBounds exception, it tells you which index was accessed and the size of the arraylist. This seems like good information.)


Part 2 - Testing with JUnit

We now interrupt this lab to bring you an important message about testing.

The programs you will write in this course get increasingly complex, and increasingly difficult to debug. We want to strongly encourage you to get into good testing habits now, right off the bat. Many of you have survived thus far by writing allll your code for a lab in one feel swoop and then testing (if that) as an afterthought at the end. Some of you are even strangely proud of this fact. This is no longer a feasible approach. Your life will be much simpler if you "write a bit, test a bit". So before you proceed to the rest of your MyArrayList implementation, you will test the code you have written thus far, using a fancy Java testing framework called JUnit.

As a side note, some people take this to the extreme, and practice "Test Driven Development." As the name suggests, instead of designing your program to fit some guidelines and THEN deciding what tests are adequate, test driven development FIRST decides on a set of tests that you want your program to "pass", and only then do you design the program to pass those tests. You can read more about this software development technique in many places, including Wikipedia.

The "old" way of testing was to write test cases in a main method somewhere. Although this approach is convenient and simple, it can be ineffective:

The JUnit framework addresses these issues, and more.

JUnit

JUnit is a widely-used API that enables developers to easily create Java test cases. It provides a comprehensive assertion facility to verify expected versus actual results.

It is simple to write a JUnit test case, especially in eclipse:

It is also simple to run your tests: So what can you actually do in these tests? Let me talk you through some examples:
  • You can even test that the correct exceptions are thrown by changing the @Test tag before your test method. For example, to test the exception that should be thrown by the add method when adding "off the left" of the list, we could use this code:
        @Test(expected=IndexOutOfBoundsException.class)
        public void testForAddLeftException() throws Exception {
    	MyArrayList<Integer> test = new MyArrayList<Integer>();
    	test.add(-1, 5);
        }
        
    To test the exception that should be thrown by the add method when adding "off the right" of the list, we would use a second method:
        @Test(expected=IndexOutOfBoundsException.class)
        public void testForAddRightException() throws Exception {
    	MyArrayList<Integer> test = new MyArrayList<Integer>();
    	test.add(test.size()+1, 5);
        }
        

    Now let's have you try. In each of the following test methods, implement the recommended test.

    • testAddE(): Tests your constructor, the single-parameter add method (which, hopefully calls the two-parameter add method), and the get method. Under the covers, it will also test your private resize method. This also tests adds to the end of the list and adds when the list is at capacity.

      Read Strings from the file test1.txt, creating a MyArrayList of Strings (and probably also an ArrayList of the same strings) by inserting each line of the file, one at a time, into a MyArrayList<String> using the add(element) method. Then loop through your two lists element-by-element and assert that the ith element should be equal for each i.

      If you are doing this from eclipse in the lab (and maybe at home), you may have to specify the entire path of the file, not just the relative path. It seems as though eclipse is being run from some mystery location, so this is how it must be done!

    • testAddIntEFront(): Tests adds to the beginning of the list.

      Again, read Strings from test1.txt, but insert each line at the front of the list using add(index, element).

    • testAddIntEBack(): Tests adds to the middle of the list.

      Add on to the end of the previous test by reconstructing your two arrays. Perform the same steps, but insert each line at the midpoint (size/2) of the list.

    • Write methods to test the IndexOutOfBounds exceptions on both borders (index -1 and index size or size+1, depending on the method) for the parameterized add method and the get method.

    There you go, your very first JUnit tests! As you implement the rest of your arraylist methods, please write the accompanying JUnit test, and add to any previous ones.


    Part 3 - Completing MyArrayList

    Now spend some time finishing up your implementation of your MyArrayList class. Don't forget to write (and run!) your tests as you go along.

    Public Methods

    Implement the following methods in your MyArrayList class

    AnyType set(int index, AnyType element);
    Change the value at the specified index to element. Return the previous value.
    Throw an IndexOutOfBoundsException if the index is not within the allowed range. Note that this range is smaller than the range allowed for add().

    In testSet(): Read and store the Strings in test1.txt, then use get() and set() to reverse the order of the elements. Check the list against a working arraylist to confirm it worked.

    AnyType remove(int index);
    Remove the item at the specified index, shifting all other elements to the left. Return the value that was removed.
    Throw an IndexOutOfBoundsException if the index is not within the allowed range. Note that this range is smaller than the range allowed for add().

    In testRemove(): Read in the test1.txt, then remove every even-indexed entry, starting at 0 (so, entries 0, 2, 4,...) and add them into a second MyArrayList. Check against a working ArrayList.

    boolean isEmpty();
    Return true if there are no items stored, false otherwise.

    Test this method appropriately.
    void clear();
    Empty out the list. Be sure to allow garbage collection to take place.

    Test this method appropriately.

    Efficiency Testing

    Great! You are done your implementation, and you have written some JUnit tests to make sure that each method works. Hopefully everything is being added, set, and removed correctly. The last thing we want to quickly check is that your program is working efficiently. Add the following two tests to your test file (precede the method header with the @Test tag):

    • testAddEfficiency(): Create a test program that inserts 1,000,000 Integers starting at 1 onto the end of the list (using the single-parameter add). Every 10,000 adds, print out the value. Note the speed with which the program completes. Now insert the same Integers but onto the front of the list. Does the speed change? Why or why not?

      You may want to change this method's @Test tag to an @Ignore tag before proceeding. This will indicate to JUnit to ignore this test for the time being (which is good, because it takes awhile to run.)

    • testRemoveEfficiency(): As with the previous test, insert 1,000,000 Integers starting at 1 onto the end of the list (using the single-parameter add). Now remove all the elements from the back of the list (printing out the value every 10,000 removes). This should run quickly (and if not, take another look at your remove method).

    • testMemory(): Finally, create a test program that stores Integers and have it store values starting with 1 until it runs out of memory or throws an exception (use an infinite loop). Every 10,000 adds, print out the value. Note what the last value printed was. Now run it again telling it to use more memory and notice if the value changes or not. You can tell eclipse to use more memory by clicking Run  >  Run Configurations..., clicking on the Arguments tab, and inserting -Xmx500m in the VM arguments textbox. Or, you can run java from the command line:

      java -Xmx500m Test8

      Also, pay attention to the rate at which it prints out numbers. Do you notice any changes? Run it again. Does it behave the same? Why do you think this is happening?

    Please put your answers to the above questions in a README file, and submit it with your lab.


    handin

    Look through your programs and make sure you've included your name at the top of all of them. If you know that there is a problem in one of your programs, document that at the top. If you adhered to the honor code in this assignment, add the following statement as a comment at the top of your README, MyArrayList.java and MyArrayListTest.java files:

    I have adhered to the Honor Code in this assignment.

    Quit eclipse, and then go through and delete any *.class files and backup files. (If you don't quit eclipse first, it will keep regenerating your class files when it autocompiles!)

    handin

    You now just need to electronically handin all your files.

        % cd          # changes to your home directory
        % cd cs151    # goes to your cs151 folder
        % handin      # starts the handin program
                                # class is 151
                                # assignment is 1
                                # file/directory is lab1
    
        % lshand      # should show that you've handed in something
    

    You can also specify the options to handin from the command line

        % cd ~/cs151                 # goes to your cs151 folder
        % handin -c 151 -a 1 lab1
    
        % lshand      # should show that you've handed in something
    

    Last Modified: July 18, 2012 - Alexa Sharp. A big thanks to Benjamin A. Kuperman for providing the original lab.VI Powered