If you’ve ever needed or wanted to observe an ArrayList for changes, it’s easy to extend it so that it follows the observable pattern.
The Observer
First we’ll make an interface for our observer, which defines all of the methods that we want to observe. Since ArrayList is parameterized, we’ll add a generic parameterization to our observer as well. I’ve added most, if not all, of the methods that modify the list. In your case, you might not need to observe all of these methods, so you can limit the number of methods in your interface to only the ones you need (or extend an abstract class like I do below).
import java.util.Collection;
public interface ArrayListObserver<E> {
public void onAdd( E element );
public void onAdd( int index, E element );
public void onAddAll( Collection<? extends E> elements );
public void onAddAll( int index, Collection<? extends E> elements );
public void onClear();
public void onRemove( int index );
public void onRemove( Object obj );
public void onRemoveAll( Collection<?> c );
public void onRetainAll( Collection<?> c );
public void onSet( int index, E element );
public void onSubList( int fromIndex, int toIndex );
}
The Observable
Next we’ll extend ArrayList by making our very own ObservableArrayList. We’ll override all of the methods we want to observe, let the super class do its work, and we’ll notify the observers which method was called.
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ObservableArrayList<E> extends ArrayList<E> {
private List<ArrayListObserver<E>> observers = null;
private static final long serialVersionUID = 1L;
public ObservableArrayList( ArrayListObserver<E> observer ) {
registerObserver( observer );
}
@Override
public boolean add( E e ) {
boolean result = super.add( e );
if ( result ) {
for ( ArrayListObserver<E> o : getObservers() ) {
o.onAdd( e );
}
}
return result;
}
@Override
public void add( int index, E element ) {
super.add( index, element );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onAdd( index, element );
}
}
@Override
public boolean addAll( Collection<? extends E> c ) {
boolean result = super.addAll( c );
if ( result ) {
for ( ArrayListObserver<E> o : getObservers() ) {
o.onAddAll( c );
}
}
return result;
}
@Override
public boolean addAll( int index, Collection<? extends E> c ) {
boolean result = super.addAll( index, c );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onAddAll( index, c );
}
return result;
}
@Override
public void clear() {
super.clear();
for ( ArrayListObserver<E> o : getObservers() ) {
o.onClear();
}
}
public List<ArrayListObserver<E>> getObservers() {
if ( observers == null ) {
observers = new ArrayList<ArrayListObserver<E>>();
}
return observers;
}
public void registerObserver( ArrayListObserver<E> observer ) {
getObservers().add( observer );
}
@Override
public E remove( int index ) {
E toRet = super.remove( index );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onRemove( index );
}
return toRet;
}
@Override
public boolean remove( Object obj ) {
boolean result = super.remove( obj );
if ( result ) {
for ( ArrayListObserver<E> o : getObservers() ) {
o.onRemove( obj );
}
}
return result;
}
@Override
public boolean removeAll( Collection<?> c ) {
boolean result = super.removeAll( c );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onRemoveAll( c );
}
return result;
}
@Override
public boolean retainAll( Collection<?> c ) {
boolean result = super.retainAll( c );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onRetainAll( c );
}
return result;
}
@Override
public E set( int index, E element ) {
E toRet = super.set( index, element );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onSet( index, element );
}
return toRet;
}
@Override
public List<E> subList( int fromIndex, int toIndex ) {
List<E> toRet = super.subList( fromIndex, toIndex );
for ( ArrayListObserver<E> o : getObservers() ) {
o.onSubList( fromIndex, toIndex );
}
return toRet;
}
public void unregisterObserver( ArrayListObserver<E> observer ) {
getObservers().remove( observer );
}
}
Abstract Observer
At the moment, for the purpose of this article, I don’t want to observe every method that I specified in my interface. I also don’t want to remove those methods from my interface because in the future I might need them. To get around having to implement each method specified in the interface, I’ll make an abstract observer which, by default, does nothing in each method. Then, I’ll write an implementation extending the abstract class which overrides only the methods I want to observe.
import java.util.Collection;
public abstract class AbstractArrayListObserver<E> implements ArrayListObserver<E> {
@Override
public void onAdd( E element ) {
}
@Override
public void onAdd( int index, E element ) {
}
@Override
public void onAddAll( Collection<? extends E> elements ) {
}
@Override
public void onAddAll( int index, Collection<? extends E> elements ) {
}
@Override
public void onClear() {
}
@Override
public void onRemove( int index ) {
}
@Override
public void onRemove( Object obj ) {
}
@Override
public void onRemoveAll( Collection<?> c ) {
}
@Override
public void onRetainAll( Collection<?> c ) {
}
@Override
public void onSet( int index, E element ) {
}
@Override
public void onSubList( int fromIndex, int toIndex ) {
}
}
Observer Implementation
Here, I just want to observe the add(), remove(), and clear() methods of the ArrayList, so I’ll only override these corresponding methods. For testing purposes, I’m just going to print a message to the console whenever the methods I’m observing are called.
public class MyArrayListObserver<E> extends AbstractArrayListObserver<E> {
@Override
public void onAdd( E element ) {
System.out.println( "Added element: " + element );
// Do something useful...
}
@Override
public void onClear() {
System.out.println( "Cleared list!" );
// Do something useful...
}
@Override
public void onRemove( Object obj ) {
System.out.println( "Removed object: " + obj );
// Do something useful...
}
}
Example Usage
In my main class, I’ll create an ObservableArrayList, register an observer, call some methods on my list and see what the output is.
public class ArrayListObserverTest {
public static void main( String[] args ) {
ArrayListObserver<String> observer = new MyArrayListObserver<String>();
List<String> myList = new ObservableArrayList<String>( observer );
myList.add( "Test1" );
myList.remove( "Test1" );
myList.add( "Test2" );
myList.clear();
}
}
Output
You can see in the output that the observer was appropriately notified when each of the observed methods were called.
Added element: Test1 Removed object: Test1 Added element: Test2 Cleared list!
