1. What is ArrayList?
We can think of ArrayList as dynamically growing array.
Integer[] arr = new Integer[10];
arr[0] = 1;
arr[1] = 2;
.
.
arr[9] =9;
arr[10] = 10;
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
But this is not the case with ArrayList. You can instantiate an ArrayList as below and still can add n number of elements in it.
List<String> list = new ArrayList<String>();
To understand how it happens lets see first, capacity of ArrayList.
2. What is Capacity of ArrayList
When we
instantiate an ArrayList,it is instantiated with initial capacity of 10,which
means an array of size 10 is created to store 10 elements initially or in other
words memory for 10 elements is allocated.
Below are
the constructors of the java.util.ArrayList from source code of
java.util.ArrayList,
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
So if we create instance of ArrayList class as below :
List<String> list = new ArrayList<String>();
It will call Constructor 2,which will call constructor 1 with parameter 10.
In the first constructor below line is actually instantiating an array
elementData of size 10.
this.elementData = new Object[initialCapacity];
Here elementData is an instance variable of the java.util.ArrayList class and
is declared as below :
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
*/
private transient Object[] elementData;
List<String> list = new ArrayList<String>(-1);
It will call Constructor 1 with parameter -1.Now as in Constructor 1,it is checking that initialCapacity should not be less than zero and if so, then
throw IllegalArgumentException explicitly, hence you will get following exception :
java.lang.IllegalArgumentException: Illegal Capacity: -1
at java.util.ArrayList.<init>(ArrayList.java:110)
However ,If we want to instantiate an ArrayList with initial capacity greater than 10,as we might be already aware in starting that our ArrayList is going to take large amount of data, then we can do like below :
List<String> list = new ArrayList<String>(30);
Above line will call constructor 1 and will instantiate an array with size of 30. Actually ArrayList can contain any number of items as long there is enough memory for it and when doing large initial insertions we can tell ArrayList to allocate a larger memory space to begin with to avoid wasting CPU cycles when it tries to allocate more space for the next item.
3. How ArrayList capacity grows dynamically ?
That is interesting question actually and answer is equally interesting :)
So say we
created an ArrayList(Intial capacity 10) and added 10 elements as below :
List<String> list = new ArrayList<String>();
list.add("1");
Size of ArrayList list : 1
Capacity of ArrayList list : 10
list.add("2");
After above line,
Size of
ArrayList list : 2
Capacity of
ArrayList list : 10
list.add("3");
After above
line,
Size of
ArrayList list : 3
Capacity of
ArrayList list : 10
list.add("10");
After above line,
Size of
ArrayList list : 10
Capacity of
ArrayList list : 10
So far so
good,as intial capacity of ArrayList was 10,we added 10 elements in it.Now lets
add 11th element in it.
list.add("11");
After above line,
Size of
ArrayList list : 11
Capacity of
ArrayList list : 15
Let us see
how capacity increased from 10 to 15.
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
Now lets see what ensureCapacityInternal method is doing.It will call grow method if
following condition is true
if (minCapacity - elementData.length > 0)
minCapacity = 11
elementData.length = 10
so minCapacity - elementData.length = 11-10 =1 which is greater than zero,hence condition will be true on addition of 11th element and grow method will be called.
Now lets explore grow method
int oldCapacity = elementData.length;
In our case
oldCapacity
= 10
int newCapacity = oldCapacity + (oldCapacity >> 1);
In our case
newCapacity
=10 + 0.5*10 =15
Here
>> is right shift operator which reduces a number to its half
if (newCapacity - minCapacity < 0)
In our case
newCapacity
- minCapacity = 15-11 =4 which is not less than zero hence next line inside if
statement will not be called.
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
In our case
elementData = Arrays.copyOf(elementData, newCapacity);
Next time when 16th element will be added,new size will be calculated as below
4. Difference between Size and Capacity of ArrayList
As we discussed above, ArrayList is actually backed by an array ,i.e. elements of ArrayList are actually stored in array. The capacity of ArrayList is the size of this array which stores elements of ArrayList. The capacity of ArrayList at any time will be at least equal to the size of the ArrayList, which in other words means that capacity of ArrayList can never ever be less than size of the ArrayList. This is due to the reason that whenever it's size reaches maxmimum capacity,capacity is increased by 0.5*old Capacity.
Size of ArrayList is the actual number of elements contained within an ArrayList.
Lets see capacity and size of ArrayList as we add elements to it. Here getCapacity method gives capacity of ArrayList using reflection api.Here elementData in the getCapacity method is instance variable of java.util.ArrayList class.
public static void main(String args[]){
ArrayList<String> list = new ArrayList<String>();
list.add("a");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("b");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("c");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("d");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("e");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("f");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("g");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("h");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("i");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("j");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
list.add("k");
System.out.println("Capacity= " + getCapacity(list)+" "+"Size = "+list.size());
}
static int getCapacity(ArrayList<?> list) throws Exception {
Field dataField = ArrayList.class.getDeclaredField("elementData");
dataField.setAccessible(true);
return ((Object[]) dataField.get(list)).length;
}
Output :
Capacity = 10 Size = 1
Capacity = 10 Size = 2
Capacity = 10 Size = 3
Capacity = 10 Size = 4
Capacity = 10 Size = 5
Capacity = 10 Size = 6
Capacity = 10 Size = 7
Capacity = 10 Size = 8
Capacity = 10 Size = 9
Capacity = 10 Size = 10
Capacity = 15 Size = 11
5. ArrayList is an ordered collection
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(int i=0;i<list.size();i++){
System.out.println("Index:"+i+"element:"+list.get(i));
}
list.clear();
System.out.println("changed Order");
list.add("b");
list.add("a");
list.add("c");
for(int i=0;i<list.size();i++){
System.out.println("Index:"+i+"element:"+list.get(i));
}
}
Ouput :
Index:0 element:a
Index:1 element:b
Index:2 element:c
changed Order
Index:0 element:b
Index:1 element:a
Index:2 element:c
So we can see from above output that order in which we inserted elements in ArrayList, while retrieving it maintained same order.
6. ArrayList allows duplicates
List<String> list = new ArrayList<String>();
list.add("GB");
list.add("GB");
System.out.println("Duplicate Values");
for(String str : list){
System.out.println(str);
}
Output :
Duplicate Values
GB
GB
7. ArrayList allows null values
List<String> list = new ArrayList<String>();
list.add("1");
list.add(null);
list.add(null);
System.out.println("Allows null Values");
for(String str : list){
System.out.println(str);
}
Output :
Allows null Values
1
null
null
8. ArrayList is not synchronized
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list = Collections.synchronizedList(list);
synchronized(list){
Iterator<String> itrList = list.iterator();
while(itrList.hasNext()){
System.out.println(itrList.next());
}
}
}
Alternatively instead of ArrayList, we can use CopyOnWriteArrayList which is thread safe implementation of List<E> interface.
CopyOnWriteArrayList<String> al = new CopyOnWriteArrayList<String>();
al.add("a");
al.add("b");
al.add("c");
Iterator<String> iterator = al.iterator();
while (iterator.hasNext())
System.out.println(iterator.next());
}