slides created by Alyssa Harding CSE 143 Lecture 4 Generics Interfaces Stack, Queue slides created by Alyssa Harding http://www.cs.washington.edu/143/
ArrayIntList client Now that we’ve built ArrayIntList , we can be its client ArrayIntList list1 = new ArrayIntList(); list1.add(42); list1.add(7); list1.add(-10); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); }
Generics But what if we want to have a list of Strings? We could copy ArrayIntList and change it into ArrayStringList… Java to the rescue: Generics! Redundant!
ArrayList<E> ArrayList<E> has all the methods we want: // adds given value to end of list void add(E value) // adds given value at the given index void add(E value, int index) // removes value at the given index void remove(int index) // returns value at given index E get(int index) // returns size of list int size() E is the Element type
ArrayList<E> Now we can create and use a list of any type of Object: ArrayList<String> list = new ArrayList<String>(); list.add(“hello”); String s = list.get(0); However, this only works for Objects, not primitive data types, such as ints or doubles
Wrapper classes Java has classes that wrap the primitive types in objects: Example of autoboxing: ArrayList<Integer> list = new ArrayList<Integer>(); list.add(42); int x = list.get(0); primitive type object type int Integer double Double char Character boolean Boolean
ArrayList<E> client Now we can get back to our client code ArrayList<Integer> list1 = new ArrayList<Integer>(); list1.add(42); list1.add(7); list1.add(-10); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); }
for each loop There’s another way to look at any structure that is Iterable for (int i = 0; i < <structure>.size(); i++) { <statements> } for (<type> <name> : <structure>) { <statements> } Warning: in a for each loop, you cannot remove values see which index you’re at If you need to do that, stick with a for loop!
ArrayList<E> client We can use a for each loop ArrayList<Integer> list1 = new ArrayList<Integer>(); list1.add(42); list1.add(7); list1.add(-10); for (int n : list1) { System.out.println(n); }
ArrayList<E> client We can also make methods public static void processIntList( ArrayList<Integer> list1) { list1.add(42); list1.add(7); list1.add(-10); for (int n : list1) { System.out.println(n); } }
LinkedList<E> Java also provides a LinkedList<E> with the same methods: // adds given value to end of list void add(E value) // adds given value at the given index void add(E value, int index) // removes value at the given index void remove(int index) // returns value at given index E get(int index) // returns size of list int size()
Interfaces But what if we want to use our method with a LinkedList<Integer>? We could copy the method and change the parameter to a LinkedList<Integer>… Java to the rescue: Interfaces! Redundant!
Interfaces Computer scientists think about Abstract Data Types (ADTs) Both ArrayList<E> and LinkedList<E> have the same functionality This is the functionality of a List<E> We know any List<E> will have certain methods and behavior without caring how it is implemented
List<E> public interface List<E> { } // adds given value to end of list public void add(E value); // adds given value at the given index public void add(E value, int index); // removes value at the given index public void remove(int index); // returns value at given index public E get(int index); // returns size of list public int size(); }
Interfaces Both ArrayList<E> and LinkedList<E> implement the List<E> interface public class ArrayList<E> implements List<E> { … } public class LinkedList<E> implements List<E> { They both have a contract to have the behavior and methods expected in a List<E>
Interfaces Anywhere we want a List<E>, we can use an ArrayList<E> or a LinkedList<E> List<Integer> list1 = new ArrayList<Integer>(); List<Integer> list1 = new LinkedList<Integer>(); Declare the variable with the most abstract type possible This lets the compiler know that our variable is guaranteed to have the functionality of a List Instantiate the variable with the implementation type This lets the compiler know how to perform the methods
Interfaces Tip 52 from Joshua Bloch's “Effective Java” "Refer to objects by their interfaces." “You should favor the use of interfaces rather than classes to refer to objects. If appropriate interface types exist, parameters, return values, variables and fields should all be declared using interface types." "The only time you really need to refer to an object's class is when you're creating it.”
Stacks and Queues Stacks and Queues are two other ADTs They take advantage of generics and interfaces They have methods that make them good for specific tasks add data to them remove data from them see whether they’re empty
Stacks Stacks are a Last In First Out (LIFO) structure // add value to the top of the stack void push(E value) // remove and return the value at the top E pop() // return the size int size() // return whether the stack is empty boolean isEmpty()
Queues Queues are a First In First Out (FIFO) structure // add value to the back of the queue void enqueue(E value) // remove and return the value at the front E dequeue() // return the size int size() // return whether the queue is empty boolean isEmpty()
Stack and Queue client Stack<Integer> s = ArrayStack<Integer>(); Queue<Integer> q = LinkedQueue<Integer>(); for (int i = 0; i < 3; i++) { s.push(i); q.enqueue(i); } System.out.println(s); System.out.println(q);