Sunday, May 21, 2023

Deep Dive into Java Collections: List And ArrayList

java,Collection Framework,ArrayList,List,programming,software development,technology
In this tutorial, we'll look at how to use Java's Collection Framework to organize and work with data. Different types of collection interfaces and the classes that implement them are available in the Collection Framework. These interfaces can be separated into: the Collection interface and the Map interface. We can build an ordered or index-based collection object using the Collection interface. On the other hand, we can make key-value-paired collection objects using the Map interfaces.

Collection(I)

The Collection interface is referred to as the Collection Framework's root interface. This interface contains a few general-purpose abstract methods, and its child classes or interfaces automatically have access to these methods. Important methods are as follows:
boolean add(Object object)
boolean remove(Object object)
boolean retainAll(Collection collection) : all the objects will be removed except those present in collection
boolean contains(Object object)
int size()
Object[] toArray()
Iterator iterator();
The Collection interface has no implementation class. The List and Set interfaces extend the Collection interface.

java,programming,ArrayList,List,Software Development

List(I)

The Java Collection Framework's List interface stores an ordered collection of elements. This interface contains methods for adding, removing, and accessing collection elements. Among the most essential methods in the List interface are:
// Adds an element at the specified index
void add(int index, Object object)

// Inserts the provied collection object at the specified index
boolean addAll(int index, Collection collection)

// Removes an element from the specified index
Object remove(int index)

// Retrieves an element from the specified index
Object get(int index)

// this will replace the element present at specified index with the object provided and returns the old object
Object set(int index, Object object)

// returns the index of the object provided
int indexOf(object object)

// returns the index of the object provided
int lastIndexOf(object object)
Insertion order is preserved in the List. This means that the elements will be kept in the order in which they were inserted. We can keep duplicate elements in it.

Because List is an abstract interface, you cannot directly create an instance. You must instead make an instance of a class that implements the List interface. ArrayList, LinkedList, and Vector are some classes that implement the List interface.

ArrayList

The ArrayList is a class in java.util package that implements the List interface. It offers dynamic arrays that can expand as needed. ArrayLists can be created with a fixed capacity and will automatically resize when that capacity is reached. We can insert heterogeneous objects into it. Inserting NULL values is also possible.

👉 Here are some ArrayList constructors:
// We can use this constructor to create an empty ArrayList with the default capacity (10). 
// When the ArrayList is full, a new ArrayList with a new capacity is created.
ArrayList()

// We can use this constructor to create an ArrayList with the specified capacity.
ArrayList(int capacity)

// We can use this constructor to create an ArrayList with the specified Collection object.
// Using this constructor, we can convert any Collection object to ArrayList.
ArrayList(Collection collection)

👉 Here's some code that shows how to use List and ArrayList:
import java.util.*;
public class ArrayListDemo {
    public static void main(String[] args) {
        System.out.println("ArrayList using Homogeneous elements : ");
        
        List nameList = new ArrayList();
        nameList.add("Sally Becton");
        nameList.add("Larry Bowen");
        nameList.add("Elizabeth A. Fabian");
        nameList.add("Maria Juarez");
        
        Object[] nameArray = nameList.toArray();
        for (int i=0; i<nameArray.length; i++) {
            System.out.println(i + " : " + nameArray[i]);
        }
    }
}
We created an ArrayList object and assigned it to a List in the preceding code snippet. We can do this because ArrayList is the List interface's implementation class. Then we populate the ArrayList with some homogeneous elements (String type). In line 13, we use the toArray() method to convert the ArrayList into an array object. This toArray() method was inherited by ArrayList from the Collection interface. The array values are then printed using the index.

👉 Consider the following code snippet:
import java.util.*;
public class ArrayListHeteroDemo {
    public static void main(String[] args) {
        System.out.println("ArrayList using Heretogeneous elements : ");
        
        ArrayList differentList = new ArrayList();
        differentList.add(4362);
        differentList.add("Aberdeen");
        differentList.add(true);
        differentList.add(13.97);
        differentList.add("wsalmon@example.com");
        
        for (int i=0; i<differentList.size(); i++) {
            System.out.println(i + " : " + differentList.get(i));
        }
        
        System.out.println("\nIndex of element \'13.97\' is : " + differentList.indexOf(13.97));
    }
}
In this case, we've added some heterogeneous objects to the ArrayList. The ArrayList elements are then printed using the get(int index) method and retrieve the index of an existing element using the indexOf(Object obj) method.

👉 Here's an example of the generic type ArrayList:
import java.util.*;
public class ArrayListGenericDemo {
    public static void main(String[] args) {
        List<String> genericList = new ArrayList<>();
        
        try {
            genericList.add("Sally Becton");
            genericList.add("Larry Bowen");
            genericList.add("Elizabeth A. Fabian");
            
            //genericList.add(100.77);
            genericList.add(String.valueOf(123.77));
            //System.out.println(genericList);
            
            System.out.println("ArrayList elements : ");
            for(String str : genericList) {
                System.out.println(str);
            }
            
            //double _value = genericList.get(3);
            System.out.println("\nDouble value : " + Double.parseDouble(genericList.get(3)));
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            //System.out.println(genericList);
        }
    }
}
We create a String-type generic ArrayList and populate it with String elements. In line 12, we insert a double element; in line 13, we insert another double element, but this time we convert it to a String. If we run this code, we will get a compile-time error that says "incompatible types: double cannot be converted to String" for line 12. There will be no complaints about line 13. As a result, when we make an ArrayList generic, we can't store other primitive or custom data types in it. We must use the generic type to store other primitive data types.

ArrayList also supports the RandomAccess interface. This is why we can quickly retrieve values from the large ArrayList from any position.
However, inserting or deleting any element from a large ArrayList will take time. Because inserting an element in the middle of a large ArrayList necessitates shifting the other elements to insert the element at the specified index. As a result, this will also take time.

Synchronized ArrayList

ArrayList objects are non-synchronized by default. However, using the Collections class's synchronizedList() method, we can create a synchronized version of the ArrayList object, meaning multiple threads can access the list concurrently without causing any inconsistencies or errors.

👉 Here's an example:
import java.util.*;
public class ArrayListSynchronizedDemo {
    public static void main(String[] args) {
        String _status = "AVAILABLE";
        
        List<String> seatlList = new ArrayList<>();
        seatlList.add("Seat no 1 is " + _status);
        seatlList.add("Seat no 2 is " + _status);
        seatlList.add("Seat no 3 is " + _status);
        seatlList.add("Seat no 4 is " + _status);
        seatlList.add("Seat no 5 is " + _status);
        seatlList.add("Seat no 6 is " + _status);
        seatlList.add("Seat no 7 is " + _status);
        seatlList.add("Seat no 8 is " + _status);
        seatlList.add("Seat no 9 is " + _status);
        seatlList.add("Seat no 10 is " + _status);
        
        System.out.println("Available seats : " + seatlList + "\n");
        
        // Create a synchronized ArrayList from existing list
        List<String> synchronizedList = Collections.synchronizedList(seatlList);
        
        // Creating multiple threads to change seat status
        Thread t2_booked = new Thread(new SeatStatusModifier(synchronizedList, 2, "BOOKED"));
        t2_booked.setName("t2_booked");
        
        Thread t5_booked = new Thread(new SeatStatusModifier(synchronizedList, 5, "BOOKED"));
        t5_booked.setName("t5_booked");
        
        Thread t3_booked = new Thread(new SeatStatusModifier(synchronizedList, 3, "BOOKED"));
        t3_booked.setName("t3_booked");
        
        Thread t6_unavailable = new Thread(new SeatStatusModifier(synchronizedList, 6, "UN-AVAILABLE"));
        t6_unavailable.setName("t6_unavailable");
        
        Thread t8_booked = new Thread(new SeatStatusModifier(synchronizedList, 8, "BOOKED"));
        t8_booked.setName("t8_booked");
        
        Thread t9_booked = new Thread(new SeatStatusModifier(synchronizedList, 9, "BOOKED"));
        t9_booked.setName("t9_booked");
        
        Thread t2_canceled = new Thread(new SeatStatusModifier(synchronizedList, 2, "CANCELED"));
        t2_canceled.setName("t2_canceled");
        
        // starts all the threads
        t2_booked.start();
        t5_booked.start();
        t3_booked.start();
        t8_booked.start();
        t9_booked.start();
        t2_canceled.start();
        t6_unavailable.start();
    }
}

// A runnable class to modify the Seat Status
class SeatStatusModifier implements Runnable {
    private List<String> list;
    int seatNo;
    String status;
    
    public SeatStatusModifier(List<String> list, int seatNo, String status) {
        this.list = list;
        this.seatNo = seatNo;
        this.status = status;
    }
    
    @Override
    public void run() {
        synchronized (list) {
            System.out.println("\n" + java.time.LocalDateTime.now() + " : Make seat no "+ (this.seatNo) + " : " + this.status);
            list.set(this.seatNo-1, "Seat no " + (this.seatNo) + " is : " + this.status);
            System.out.println(Thread.currentThread().getName() + ": " + list);
        }
    }
}
We created a non-synchronized ArrayList with ten elements in the preceding code. Each element represents a seat with a corresponding status. Then we use the Collections.synchronizedList() method to create a synchronized ArrayList, which takes an ArrayList as a parameter and returns a synchronized version of the list.

We've made a couple of Threads, each with its own SeatStatusModifier class that implements the Runnable interface and overrides the run() method. We have a synchronized block in the run() method. The ArrayList object, mainly the seat status, is modified within a synchronized block to ensure thread-safe access, and the modified list elements are printed. Because the list is synchronized, multiple threads can safely modify it without causing inconsistencies or errors.

It should be noted that the order of the modified elements in the list may vary depending on how the threads access the list.

👉 It is important to note that using a synchronized ArrayList can add some performance overhead, so it should only be used when necessary, such as when multiple threads need to access the same list concurrently.

Happy coding!!! 😊
in

No comments:

Post a Comment

Popular posts