Hi! In previous lessons, we did a deep dive into arrays and reviewed common examples of working with arrays.
ArrayList class - 1
In general, arrays are super handy. And, as you've already noticed, you can do a lot with them :) But arrays do have a number of shortcomings.
  • Limited size. You need to know how many elements your array needs to contain at the time you create it. If you underestimate, then you won't have enough space. Overestimate, and the array will remain half-empty, which is also bad. After all, you're still allocating more memory than is necessary.

  • An array doesn't have methods for adding elements. You must always explicitly indicate the index of the position where you want to add an element. If you accidentally specify the index for a position occupied by some value you need, it will be overwritten.

  • There are no methods to delete an item. A value can only be "zeroed out".
public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat[] cats = new Cat[3];
       cats[0] = new Cat("Thomas");
       cats[1] = new Cat("Behemoth");
       cats[2] = new Cat("Lionel Messi");

       cats[1] = null;



       System.out.println(Arrays.toString(cats));
   }

   @Override
   public String toString() {
       return "Cat{" +
               "name='" + name + '\'' +
               '}';
   }
}
Output: [Cat{name='Thomas'}, null, Cat{name='Lionel Messi'}] Fortunately, Java's creators are well aware of arrays' advantages and disadvantages, and therefore created a very interesting data structure called ArrayList. Speaking as simply as possible, an ArrayList is a "souped up" array with a lot of new features. It is very easy to create:
ArrayList<Cat> cats = new ArrayList<Cat>();
Now we've created a list for storing Cat objects. Note that we aren't specifying the size of the ArrayList, because it can expand automatically. How is this possible? It's quite simple, actually. It may surprise you, but ArrayList is built on top of a very ordinary array :) Yes, it contains an array, and that's where our elements are stored. But ArrayList has a special way of working with that array:
  • When the internal array is filled, ArrayList creates a new array internally. The size of the new array is the size of the old array times 1.5 plus 1.

  • All the data is copied from the old array into the new one

  • The old array is cleaned up by the garbage collector.
This mechanism allows ArrayList (unlike an ordinary array) to implement a method for adding new elements. It's the add() method
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<Cat>();
   cats.add(new Cat("Behemoth"));
}
New items are added to the end of the list. Now there's no risk overflowing the array, so this method is completely safe. By the way, ArrayList can not only find an object by its index, but also vice versa: it can use a reference to find an object's index in the ArrayList! This is what the indexOf() method is for: We pass a reference to the object we want, and indexOf() returns its index:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   int thomasIndex = cats.indexOf(thomas);
   System.out.println(thomasIndex);
}
Output: 0 That's right. Our thomas object is indeed stored in element 0. Arrays don't only have drawbacks. They also have unquestionable advantages. One of them is the ability to search elements by index. Because we point to an index, i.e. to a specific memory address, searching an array in this manner is very quick. ArrayList knows how to do it, too! The get() method implements this:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   Cat secondCat = cats.get(1);

   System.out.println(secondCat);
}
Output: Cat{name='Behemoth'} In addition, you can easily find out whether the ArrayList contains a particular object.This is done using the contains() method:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   cats.remove(fluffy);
   System.out.println(cats.contains(fluffy));
}
The method checks whether the ArrayList's internal array contains the element, and returns a boolean (true or false). Output: false And another important thing about insertion. ArrayList lets you use an index to insert elements not only at the end of the array, but anywhere. It has two methods for this:
  • add(int index, Cat element)
  • set(int index, Cat element)
As arguments, both of these methods take the index of the position where you want to insert, and a reference to the object itself. The difference is that inserting using set() overwrites the old value. Inserting using add() first shifts by one all elements from [index] to the end of the array, and then adds the specified object in the resulting empty position. Here's an example:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.set(0, lionel);// Now we have a list of 2 cats. Adding a 3rd using set

   System.out.println(cats.toString());
}
Output: [[Cat{name='Thomas'}, Cat{name='Behemoth'}] [Cat{name='Lionel Messi'}, Cat{name='Behemoth'}] We had a list of 2 cats. Then we inserted another one as element 0 using the set() method. As a result, the old element has been replaced by a new one.
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);

   System.out.println(cats.toString());

   cats.add(0, lionel);// Now we have a list of 2 cats. Adding a 3rd using add

   System.out.println(cats.toString());
}
And here we see that add() works differently. It moves all the elements to the right and then writes the new value as element 0. Output: [Cat{name='Thomas'}, Cat{name='Behemoth'}] [Cat{name='Lionel Messi'}, Cat{name='Thomas'}, Cat{name='Behemoth'}] To completely clear the list, we use the clear() method:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();
   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   cats.clear();

   System.out.println(cats.toString());
}
Output: [] Everything was removed from the list. By the way, please note: unlike arrays, ArrayList overrides the toString() method and already displays the list appropriately as strings. With ordinary arrays, we had to use the Arrays class for this. And since I mentioned Arrays: Java lets you easily "switch" between an array and an ArrayList, i.e. convert one to another. The Arrays class has an Arrays.asList() method for this. We use it to get the contents as an array and pass them to our ArrayList constructor:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   Cat[] catsArray = {thomas, behemoth, lionel, fluffy};

   ArrayList<Cat> catsList = new ArrayList<>(Arrays.asList(catsArray));
   System.out.println(catsList);
}
Output: [Cat{name='Thomas'}, Cat{name='Behemoth'}, Cat{name='Lionel Messi'}, Cat{name='Fluffy'}] You can also go in the opposite direction: get an array from an ArrayList object. We do this using the toArray() method:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();

   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   Cat[] catsArray = cats.toArray(new Cat[0]);

   System.out.println(Arrays.toString(catsArray));
}
Note: we passed an empty array to the toArray() method. This is not an error. Inside the ArrayList class, this method is implemented in such a way that passing an empty array increases its performance. Just keep this in mind for the future (of course, you can pass an array of some specific size; that will also work). Oh, about the size. The list's current size can be found using the size() method:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>();


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   System.out.println(cats.size());
}
It's important to understand that unlike an array's length property, the ArrayList.size() method returns the actual number of elements, not the original capacity. After all, we didn't specify a size when creating the ArrayList. However, you can specify it — ArrayList has a suitable constructor. But in terms of adding new elements, this doesn't change its behavior:
public static void main(String[] args) {

   ArrayList<Cat> cats = new ArrayList<>(2);// create an ArrayList with an initial capacity of 2


   Cat thomas = new Cat("Thomas");
   Cat behemoth = new Cat("Behemoth");
   Cat lionel = new Cat("Lionel Messi");
   Cat fluffy = new Cat ("Fluffy");

   cats.add(thomas);
   cats.add(behemoth);
   cats.add(lionel);
   cats.add(fluffy);

   System.out.println(cats.size());
}
Console output: 4 We've created a list of 2 elements, but it quietly expanded when we needed it to. Another consideration is that if we create a very small list initially, it will have to expand more often, which will use some resources. We hardly touched on the process of removing elements from an ArrayList in this lesson Of course, this isn't because it slipped our mind. We've put this topic into a separate lesson that you'll meet later :)