ArrayList Implementation in Java Can someone please evaluate my code and let me know how I can improve it?

public class ArrayListImplementation {

static class ListNode&lt;T&gt; {
    private T value;

    public ListNode(T val) {
        this.value = val;
    }

    public T getValue() {
        return this.value;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.value);
        return sb.toString();
    }
}

static class List&lt;T&gt; {
    private ListNode&lt;T&gt;[] node;
    private int listSize;
    private int initialCapacity;

    public List(int size) {
        node = new ListNode[size];
        initialCapacity = size;
    }

    public int size() {
        return listSize;
    }

    public boolean isEmpty() {
        return listSize == 0;
    }

    public T get(int index) {
        if(index &gt;= initialCapacity) {
            throw new ArrayIndexOutOfBoundsException();
        }

        return node[index].getValue();
    }

    public void add(T val) {
        if(listSize == initialCapacity) {
            System.out.println("No space to add a new list element");
            return;
        }

        ListNode&lt;T&gt; head = null;
        if(listSize == 0) {
            head = new ListNode&lt;&gt;(val);
            node[0] = head;
            listSize++;
            return;
        }

        ListNode&lt;T&gt; cur = new ListNode&lt;&gt;(val);
        node[listSize++] = cur;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[ ");
        for(int i = 0; i &lt; initialCapacity - 1; ++i) {
            sb.append(node[i].getValue() + ", ");
        }
        sb.append(node[initialCapacity - 1].getValue() + " ]");
        return sb.toString();
    }

    public void clear() {
        node = new ListNode[initialCapacity];
        listSize = 0;
        return;
    }

    public boolean remove(T value) {
        boolean result = false;
        for(int i = 0; i &lt; initialCapacity; ++i) {
            if(node[i].getValue() == value) {
                result =  remove(node[i], i);
                if(result) {
                    listSize--;
                }
            }
        }   
        return result;
    }

    private boolean remove(ListNode&lt;T&gt; toBeRemoved, int index) {
        if(index &lt; 0 || index &gt;= initialCapacity) {
            return false;
        }

        if(initialCapacity &gt; 1) {
            int prevSize = initialCapacity;
            ListNode&lt;T&gt;[] copyNode = node;

            node = new ListNode[--initialCapacity];
            for(int i = 0; i &lt; index; ++i) {
                node[i] = copyNode[i];
            }

            for(int i = index + 1; i &lt; prevSize; ++i) {
                node[i-1] = copyNode[i];
            }
        }
        else {
            this.clear();
        }
        return true;
    }
}

public static void main(String[] args) {
    List&lt;Integer&gt; list = new List&lt;Integer&gt;(5);
    System.out.println(list.isEmpty());
    list.add(5);
    list.add(10);
    list.add(15);
    System.out.println(list.get(0));
    System.out.println(list.get(2));
    System.out.println(list.size());
    list.add(20);
    System.out.println(list.size());
    list.add(25);
    System.out.println(list.get(4));
    System.out.println(list.remove(20));
    System.out.println(list.remove(45));
    System.out.println(list.remove(5));
    System.out.println(list.remove(25));
    System.out.println(list.remove(10));
    System.out.println(list.remove(15));
    //System.out.println(list.get(5)); //ArrayIndexOutOfBoundsException
    list.clear();
    System.out.println(list.size());
}}
In your ListNode::toString() the use of StringBuilder is useless overhead as you just append one single string. You could strip it down method to just this:

public String toString() {
    if (this.value == null) { // beware of null!
        return "null";
    }
    return this.value.toString();
}

In your List::add(T) method you should throw an Exception (maybe an ArrayIndexOutOfBoundsException with a proper message) if the list is full (if(listSize == initialCapacity) {) instead of writing a note to System.out. Otherwise the calling code of your implementation wouldn't notice the problem. See Fail Fast Principle.
In your add(T) method, why are there two different paths for the first and the other items, when the do exactly the same?
Is it by intention, that you reduce the intial/maximum size of your internal array in the remove(ListNode&lt;T&gt; toBeRemoved, int index) method?

node = new ListNode[--initialCapacity];  // &lt;&lt; newSize = initialCapacity -1


It seems a bit strange to me.


In your remove methods you use initialCapacity where you rather should use listSize (except from the line mentioned in 4a).
Within the remove you could reuse the add method. I would refactor it to something like this:

public boolean remove(T value) {
    boolean result = false;
    for(int i = 0; i &lt; listSize; ++i) { // note: only check elements added
        if(node[i].getValue() == value) {
            result =  remove(node[i], i);
            if(result) {
                listSize--;
            }
            // break; // note: exit loop if you only want to remove first occurence.
        }
    }   
    return result;
}

private boolean remove(ListNode&lt;T&gt; toBeRemoved, int deleteIdx) {
    if(deleteIdx &lt; 0 || deleteIdx &gt;= listSize) { // note: if no element -&gt; exit
        return false;
    }

    if(listSize &gt; 1) { // note: listSize relevant here
        ListNode&lt;T&gt;[] oldNode = node;

        node = new ListNode[initialCapacity]; // note: same size here
        int sourceIdx = 0; 
        for(int targetIdx = 0; targetIdx &lt; listSize; targetIdx++) {
            if (targetIdx == deleteIdx) {
                sourceIdx++; // skip deleted element
            }
            node[targetIdx] = oldNode[sourceIdx++];
        }
    }
    else {
        this.clear();
    }
    return true;
}