Zach Cochran
by Zach Cochran
4 min read

Categories

Tags

Today I learned about Java Lists and how to use them.

Continuing the discussion of collections, the first type of collection that was discussed in my course were Lists. Lists are the most commonly used Collection and they’re not too hard to grasp.

Lists maintain a specific iteration order. That is to say that elements will have a specific position within the array that can be accessed via iteration by use of a reference.

Supported Methods

Each type of List supports the following methods:

  • void add(int index, E element) - adds element at specific index
  • E get(int index) - get the element at the specified index, returns element
  • E remove(int index) - remove the element at the specified index, returns element
  • E set(int index, E element) - set the index to a specific value
  • boolean addAll(int index, Collection<? extends E> c) - add all elements of the collection starting at the specified index, returns true or false if they were inserted.
  • int indexOf(Object o) - returns the first index of a given object
  • int lastIndexOf(Object o) - returns the last index of a given object
  • List<E> subList(int fromIndex, int toIndex) - returns a view of the list between the specified range (toIndex is exclusive, like for loop).
    • Modifying the view will modify the original List

When defining a list, we use the <> brackets to specify the type that the list will contain. For example, if I wanted to create a list of Products, I would use the following:

List<Products> products;

This will create a new list of products for me. Now, in order to start using it, we need to initialize it using one of the implementations that the List supports: ArrayList or LinkedList.

ArrayList

ArrayLists are very similar to the arrays you know and love. Things belong at a very specific index and can be referenced directly by that index. By default, new elements are added to the end of the array. Elements inserted at a specific point will cause all existing elements after that point to be shifted right by one down. The size of the ArrayList in memory doubles every time a new element is added and the Array is full. This is expensive at the start, but becomes much less frequent as it is used.

ArrayLists are very efficient and very convenient to use. They’re probably the most used Collection (so I’m told) just based on how well they work and how easy they are to work with.

LinkedList

LinkedLists use references to associate elements. An element will have a pointer to the element before it and after it. In the case of head and tail, they will only point to their corresponding neighbors. When a new record is inserted in, records are updated to point to their new neighbors, rather than actually having to move anything in location in memory. However, because the pointers have to be used in order to traverse, it’s much slower when accessing specific data from the list. It must go reference by reference until it reaches its spot.

ArrayList Examples

Since ArrayList is the most common, ArrayLists were used in all of the examples from the lesson. I’m going to try to keep this pretty condensed, but you can find all of the code here in my github repo.

Let’s take a look at a class that will create and manage a List of items for us, and allow us to apply various changes to the list through our created API:

public class Shipment implements Iterable<Product> {

   public static final int MAX_WEIGHT = 20;
   private List<Product> products = new ArrayList<Product>();
   private List<Product> lightVanProducts = new ArrayList<Product>();
   private List<Product> heavyVanProducts = new ArrayList<Product>();

   public void add(Product product) {
       products.add(product);
   }

   public void replace(Product oldProduct, Product newProduct) {
       int idx = products.indexOf(oldProduct);
       if (idx >= 0) {
           products.set(idx, newProduct);
       }
   }

   public void prepare() {
       Collections.sort(products, Product.BYWEIGHT);
       int idx = findSplit();
       lightVanProducts = products.subList(0, idx);
       heavyVanProducts = products.subList(idx, products.size());
   }

   private int findSplit() {
       int idx = 0;
       for (Product product: products) {
           if (product.getWeightInLbs() > MAX_WEIGHT) {
               return idx;
           }
           idx += 1;
       }
       return idx;
   }

   public List<Product> getLightVanProducts() {
       return lightVanProducts;
   }

   public List<Product> getHeavyVanProducts() {
       return heavyVanProducts;
   }

   public List<Product> getProducts() {
       return products;
   }

   public Iterator<Product> iterator() {
       return products.iterator();
   }
}

As you can see, when we create a new instance of the Shipment class, we’re creating a new empty List<Product> called products. We then create a set of methods that allow us to take actions on this list, doing things like adding, replacing, and event splitting the List into subLists.

Really the only gotcha to speak of is when using the subList method to create views into the List. It’s important to remember that modifying these views will actually modify the original list. Also, the ending point on the stopping index is exclusive, not inclusive (meaning to always +1 from the last element you want back).

Conclusion

I feel like most of this is pretty straight forward. I honestly found the hardest part of this to be the sort which required setting up a Comparator (more on that tomorrow). I really wished that there had been an example using LinkedLists instead. Maybe I’ll come up with my own and post that in the next few days. I’d like to see if it’s really any different to use, or if it’s just all behind the scenes.

💚 A.B.L.