CSCI 151 - MyArrayList Testing, testing, 1, 2, 3...

10:00pm, Sunday, 24 September

In this 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:

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

Basic Design

As you might expect from the name, 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.

private int size
number of items currently stored in the list (which may differ from the list's current capacity)
private 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.

private void resize()
Create a new array that is twice the size of the previous (use data.length)
Copy all of the old data into the new array
Set this.data to be your 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 that you get from data.length)
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. (Hint: you can use the previous method -- don't just copy the code from there.)
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().

Don't forget to be adding in your JavaDoc style comments as you go along.

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 little, test a little". 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.

    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 by setting array entries to null.

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

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

    ***** After you have completed these tests, please put @Ignore in front of the @Test to disable them. *****


    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 3
                                # file/directory is lab3
    
        % 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 3 lab3
    
        % lshand      # should show that you've handed in something
    

    Last Modified: September 11, 2015 - Benjamin A. Kuperman. A big thanks to Alexa Sharp for overhauling the original lab and adding JUnit.VI Powered