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