1 ArrayList Complexity The ArrayList Iterator Implementing the Iterator

advertisement
ArrayList Complexity
Method!
Class #10:
ArrayList Iterators and
Generic Array Conversion
Software Design III (CS 340): M. Allen, 15 Feb. 16
T get( int i )!
O(1)!
int size()!
O(1)!
T set( int i, T element )!
O(1)!
boolean add( T element )!
O(1)!
[amortized]!
void add( int i, T element )!
O(n)!
T remove( int i )!
O(n)!
int indexOf( T element )!
O(n)!
Monday, 15 Feb. 2016!
The ArrayList Iterator
ArrayListIterator implements java.util.Iterator<T> !
{!
private int current = 0;!
private boolean okToRemove = false;!
public boolean hasNext() {!
return current < size( );!
}!
private class ArrayListIterator implements java.util.Iterator<T> !
{!
public boolean hasNext() {…}!
public T next() {…}
!
public void remove() {…}!
}!
public T next() {
if ( !hasNext() )
throw new java.util.NoSuchElementException();
T item = theItems[current];
current++ ;
okToRemove = true;
}!
ArrayListIterator class is a private inner class
! 
1. 
2. 
Part of list class itself, so it can access other private elements directly,
using things like theItems array.
Software Design III (CS 340)!
3!
2!
Class implements the
Iterator interface,
meaning that it must have
methods hasNext(),
next(), and remove().
Object uses own private
variable to keep track of
current position and tell
if there are any more items
to iterate over.
return item;
public void remove() {!
if( !okToRemove )!
throw new IllegalStateException( "…" );!
Written inside the list class itself, and private to it (can only be
referenced directly inside the list code).
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
Implementing the Iterator
public class MyArrayList<T> implements Iterable<T>!
{!
public Iterator iterator()!
{!
return new ArrayListIterator();
!
}!
}!
Time Complexity!
current-- ;
MyArrayList.this.remove( current );
okToRemove = false;
}!
If we try to run next()
when there are no more
elements left, we get an
exception.
}!
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
4!
1
Implementing the Iterator
Generic Classes and Generic Methods
public T next() {!
Boolean flag: ensures
if( !hasNext() )!
can only be
throw new java.util.NoSuchElementException(); remove()
!
T item = theItems[current];
current++ ;
okToRemove = true;
return item;
}!
called once after any
time we call next().
(This behavior is part
of full specification for
Iterator interface.)
public void remove() {!
if( !okToRemove )!
throw new IllegalStateException();!
current-- ;!
MyArrayList.this.remove( current );!
okToRemove = false;!
}!
Monday, 15 Feb. 2016!
Over-riding: since iterator has its
own remove() method, needs to
use the Superclass.this
notation to access the version that
comes from its parent list-class.
Software Design III (CS 340)!
5!
Returns an object of same type (T) as the collection parameter
public T get( int idx ) !
{!
if( idx < 0 || idx >= size( ) )!
throw new IndexOutOfBoundsException(…);
return theItems[idx];
!
} !
!
What happens if we write it in parameterized form?
MyArrayList.java:76: !
public <T> T get( int idx ) !
incompatible types!
{!
found
: T!
if( idx < 0 || idx >= size( ) )!
required: T!
throw new IndexOutOfBoundsException(…);
!
return theItems[ idx ];!
return theItems[idx];
!
^!
} !
1 error!
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
6!
Arrays and Inheritance
Parameterized form of method generates an apparent type error,
even though the “found” and “required” types appear the same
! 
MyArrayList.java:76: !
public <T> T get( int idx ) !
incompatible types!
{!
found
: T!
if( idx < 0 || idx >= size( ) )!
required: T!
throw new IndexOutOfBoundsException(…);
!
return
theItems[ idx ]; !
return theItems[idx];
!
^!
} !
1 error!
! 
Consider the get() method from MyArrayList<T>
! 
! 
Analyzing the Error
! 
! 
Java arrays are covariant: if TypeA conforms to TypeB,
then array of TypeA[] conforms to array of TypeB[]
java.lang.Number
!
<interface>
!
Number[]
!
java.lang.Integer
!
<class>
!
Integer[]
!
The reason: when we parameterize the method using <T>, this
creates a local type-variable
! 
! 
! 
Just like any other local method variable, if there is a name clash, the
local version will be used instead of the global class version
The type T in this method is not the same as that of the collection itself
In general, we will want to avoid this: if you do need to parameterize
the method, choose a different, new type-variable <E>, <S>, etc.
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
7!
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
8!
2
Arrays Are Covariant
! 
Collections and Inheritance
This allows the following code (and all its problems!):
Integer[] ints = new Integer[10];
Double[] dubs = new Double[10];!
Number[] nums = ints;
getSum( nums ); !
nums = dubs;
getSum( nums );!
! 
Convenient: covariance
allows use of ancestor-type
array to reference various
different descendant-type
arrays with same identifiers
!
!
!
Java collections are not covariant: content types do not
impose conformance on Collection types
java.lang.Number
!
<interface>
!
ArrayList<Number>
!
java.lang.Integer
!
<class>
!
ArrayList<Integer>
!
Fragile: run-time errors
...!
public int getSum( Object[] obs ) {!
int sum = 0;
!
for ( int i = 0; i < obs.length; i++ )!
sum += (Integer)( obs[i] );
!
return sum;
!
}!
Monday, 15 Feb. 2016!
First call to getSum() will be
fine, but the second will fail
when we try to cast Double
objects to Integer
!
Software Design III (CS 340)!
9!
Monday, 15 Feb. 2016!
Collections Are Not Covariant
! 
ArrayList<Number> nums = ints;
getSum( nums ); !
nums = dubs;
!
getSum( nums );!
!
Monday, 15 Feb. 2016!
Suppose we want to convert a generic ArrayList<T>
to an array of the same type:
public T[] toArray() !
{
!
T[] array;!
array = new T[this.size()];!
for ( int i = 0; i < this.size(); i++ )
array[i] = this.items[i];
Less fragile: we can write
simpler methods that avoid
run-time typing errors
...!
public int getSum ( ArrayList<Integer> arr ) {
int sum = 0;
!
for ( Integer i : arr ) !
sum += i;!
return sum;
!
}!
! 
!
Less
convenient: we can’t
exploit conformance and
covariance as much (method
calls here won’t compile)
getSum( ints );!
10!
Generic Arrays Cannot Be Instantiated
This allows the following code (and all its problems!):
ArrayList<Integer> ints = new ArrayList<Integer>;
ArrayList<Double> dubs = new ArrayList<Double>;!
Software Design III (CS 340)!
return array;
This won’t compile!
While we can declare the
array to have generic type,
we cannot instantiate it:
! required to give it an
we are
!
actual type first
!
}!
!
error: generic array creation!
T[] array = new T[this.size()];!
^!
Note: since we can enforce
stronger typing, no type-casts
are needed at all
Software Design III (CS 340)!
11!
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
12!
3
Getting around the Generic Array Issue
! 
To set the return type, we need to supply something that is
guaranteed to have a non-generic specified type:
Getting Around the Generic Array Issue
! 
We can use Java’s ability to do reflection to
create a new array of the same class-type as
the input array (works even if empty)
import java.lang.reflect.Array;!
...!
The reason to include the input array, rather than just get
the type of the list data itself is that we can use the input
array to provide type-checking at the calling site:
MyArrayList<Integer> lsti = new MyArrayList<Integer>();
MyArrayList<Double> lstd = new MyArrayList<Double>();
public T[] toArray( T[] a ) !
{!
T[] array = (T[]) Array.newInstance( a.getClass().getComponentType(), size() );!
Integer[] ints = lsti.toArray( new Integer[0] );
Double[] dubs = lstd.toArray( new Double[lstd.size()]
);!
for ( int i = 0; i < size(); i++ ) !
array[i] = theItems[i];
!
return array;
}!
!
getClass(): from java.lang.Object
!
Since newInstance() returns an
Object, we must cast to T[]
!
getComponentType(): from java.lang.Class
(used specifically to get type of an array element)
!
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
13!
A Small Efficiency
! 
All else being equal, we will prefer:
if ( a.length > size() )
a[size()] = null;
return a;
}!
);
This turns out to save on object typing, creation and casting, and can
run significantly faster overall.
public T[] toArray( T[] a )
!
{!
if ( a.length >= size() )
!
{
!
for ( int i = 0; i < size(); i++ )
a[i] = theItems[i];
!
}!
// code to create new array if needed...!
Monday, 15 Feb. 2016!
Monday, 15 Feb. 2016!
While we can convert even with an
empty array, there turns out to be a
reason to use the second version of this
sort of call in most cases.
!
Software Design III (CS 340)!
14!
This Week
Double[] dubs = lstd.toArray( new Double[lstd.size()]
! 
Since the array typing and casting occurs
inside of toArray() method, we can be
certain that, if the method completes, we
have actual (non-erased) arrays of the given
types (Integer or Double)
!
If we can fit the current list into the
input array, then simply copy over
without need for typing, array
creation, casting.
!
!
!
If input array larger than needed, set
!
element
at end of list-data to null.
!
Warning: this will be a problem when
list could contain null items itself,
so we always prefer to use size of list
for input array if at all possible.!
Software Design III (CS 340)!
15!
! 
Topic: Linear Structures
! 
Read: Text, chapter 03
! 
In Lab: Friday, 19 Feb.
! 
Homework 02: due Wednesday, 24 Feb. (5:00 PM)
! 
Office Hours: Wing 210
! 
! 
Tuesday & Thursday: 10:00–11:30 AM
Tuesday & Thursday: 4:00–5:30 PM
Monday, 15 Feb. 2016!
Software Design III (CS 340)!
16!
4
Download