Building Java Programs Chapter 15 Implementing a Collection Class: ArrayIntList Exercise • Write a program that reads a file (of unknown size) full of integers and prints the integers in the reverse order to how they occurred in the file. Consider example file data.txt: 17 932085 -32053278 100 3 – When run with this file, your program's output would be: 3 100 -32053278 932085 17 2 Solution using arrays int[] nums = new int[100]; int size = 0; // make a really big array Scanner input = new Scanner(new File("data.txt")); while (input.hasNextInt()) { nums[size] = input.nextInt(); // read each number size++; // into the array } for (int i = size - 1; i >= 0; i--) { System.out.println(nums[i]); // print reversed } index 0 value 17 932085 -32053278 100 size 1 2 3 4 5 6 ... 98 99 3 0 0 ... 0 0 5 3 Unfilled arrays int[] nums = new int[100]; int size = 0; • We often need to store an unknown number of values. – Arrays can be used for this, but we must count the values. – Only the values at indexes [0, size - 1] are relevant. • We are using an array to store a list of values. – What other operations might we want to run on lists of values? index 0 1 2 3 value 17 932085 -32053278 100 size 5 4 5 6 ... 98 99 3 0 0 ... 0 0 4 Other possible operations public public public public ... static static static static void void void void add(int[] list, int size, int value, int index) remove(int[] list, int size, int index) find(int[] list, int size, int value) print(int[] list, int size) • We could implement these operations as methods that accept a list array and its size along with other parameters. – But since the behavior and data are so closely related, it makes more sense to put them together into an object. – A list object can store an array of elements and a size, and can have methods for manipulating the list of elements. • Promotes abstraction (hides details of how the list works) 5 Exercise • Let's write a class that implements a list using an int[] – We'll call it ArrayIntList – behavior: •add(value), •get(index), •size() •remove(index) •indexOf(value) •toString() ... add(index, value) set(index, value) – The list's size will be the number of elements added to it so far. – How will the list be used?... 6 Implementing add • How do we add to the end of a list? index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.add(42); index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 42 0 0 0 7 7 Implementing add, cont. • To add to end of list, just store element and increase size: public void add(int value) { list[size] = value; size++; } index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.add(42); index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 42 0 0 0 7 8 Implementing add (2) • How do we add to the middle or end of the list? index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.add(3, 42); index value size // insert 42 at index 3 0 1 2 3 4 5 6 7 8 9 3 8 9 42 7 5 12 0 0 0 7 9 Implementing add (2) cont. • Adding to the middle or front is hard (see book ch 7.3) – must shift nearby elements to make room for the new value index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.add(3, 42); index value size // insert 42 at index 3 0 1 2 3 4 5 6 7 8 9 3 8 9 42 7 5 12 0 0 0 7 – Note: The order in which you traverse the array matters! 10 Implementing add (2) code public void add(int index, int value) { for (int i = size; i > index; i--) { list[i] = list[i - 1]; } list[index] = value; } index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.add(3, 42); index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 42 7 5 12 0 0 0 7 11 Other methods • Let's implement the following methods next: – – – – size get set toString 12 Implementing remove • How can we remove an element from the list? index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.remove(2); index value size // delete 9 from index 2 0 1 2 3 4 5 6 7 8 9 3 8 7 5 12 0 0 0 0 0 5 13 Implementing remove, cont. • Again, we need to shift elements in the array – this time, it's a left-shift – in what order should we process the elements? – what indexes should we process? index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.remove(2); index value size // delete 9 from index 2 0 1 2 3 4 5 6 7 8 9 3 8 7 5 12 0 0 0 0 0 5 14 Implementing remove code public void remove(int index) { for (int i = index; i < size; i++) { list[i] = list[i + 1]; } size--; list[size] = 0; // optional (why?) } index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 0 0 0 0 6 – list.remove(2); index value size // delete 9 from index 2 0 1 2 3 4 5 6 7 8 9 3 8 7 5 12 0 0 0 0 0 5 15 Running out of space • What should we do if the client adds more than 10 elements? index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 12 4 8 1 6 10 – list.add(15); // add an 11th element index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 value 3 8 9 7 5 12 4 8 1 6 15 0 0 0 0 0 0 0 0 0 size 11 16 Convenience methods • Implement the following methods: – indexOf - returns the first index an element is found, or -1 if not – isEmpty - returns true if list has no elements – contains - returns true if the list contains the given int value • Why do we need isEmpty and contains when we already have indexOf and size ? – These methods provide convenience to the client of our class. if (myList.size() == 0) { if (myList.isEmpty()) { if (myList.indexOf(42) >= 0) { if (myList.contains(42)) { 17 More ArrayIntList • Let's add some new features to our ArrayIntList class: 1. A method that allows client programs to print a list's elements 2. A constructor that accepts an initial capacity (By writing these we will recall some features of objects in Java.) • Printing lists: You may be tempted to write a print method: // client code ArrayIntList list = new ArrayIntList(); ... list.print(); – Why is this a bad idea? What would be better? 18 The toString method • Tells Java how to convert an object into a String ArrayIntList list = new ArrayIntList(); System.out.println("list is " + list); // ("list is " + list.toString()); • Syntax: public String toString() { code that returns a suitable String; } • Every class has a toString, even if it isn't in your code. – The default is the class's name and a hex (base-16) number: ArrayIntList@9e8c34 19 toString solution // Returns a String representation of the list. public String toString() { if (size == 0) { return "[]"; } else { String result = "[" + elementData[0]; for (int i = 1; i < size; i++) { result += ", " + elementData[i]; } result += "]"; return result; } } 20 Multiple constructors • existing constructor: public ArrayIntList() { elementData = new int[10]; size = 0; } • Add a new constructor that accepts a capacity parameter: public ArrayIntList(int capacity) { elementData = new int[capacity]; size = 0; } – The constructors are very similar. Can we avoid redundancy? 21 this keyword • this : A reference to the implicit parameter (the object on which a method/constructor is called) • Syntax: – To refer to a field: this.field – To call a method: this.method(parameters); – To call a constructor from another constructor: this(parameters); 22 Revised constructors public ArrayIntList(int capacity) { elementData = new int[capacity]; size = 0; } public ArrayIntList() { this(10); // calls (int) constructor } 23 Size vs. capacity • What happens if the client tries to access an element that is past the size but within the capacity (bounds) of the array? – Example: list.get(7); on a list of size 5 (capacity 10) index value size 0 1 2 3 4 5 6 7 8 9 3 8 9 7 5 0 0 0 0 0 5 – Answer: Currently the list allows this and returns 0. • Is this good or bad? What (if anything) should we do about it? 24 Preconditions • precondition: Something your method assumes is true at the start of its execution. – Often documented as a comment on the method's header: // Returns the element at the given index. // Precondition: 0 <= index < size public void remove(int index) { return elementData[index]; } – Stating a precondition doesn't really "solve" the problem, but it at least documents our decision and warns the client what not to do. – What if we want to actually enforce the precondition? 25 Bad precondition test • What is wrong with the following way to handle violations? // Returns the element at the given index. // Precondition: 0 <= index < size public void remove(int index) { if (index < 0 || index >= size) { System.out.println("Bad index! " + index); return -1; } return elementData[index]; } – returning -1 is no better than returning 0 (could be a legal value) – println is not a very strong deterrent to the client (esp. GUI) 26 Throwing exceptions (4.5) throw new ExceptionType(); throw new ExceptionType("message"); • Causes the program to immediately crash with an exception. • Common exception types: – ArithmeticException, ArrayIndexOutOfBoundsException, FileNotFoundException, IllegalArgumentException, IllegalStateException, IOException, NoSuchElementException, NullPointerException, RuntimeException, UnsupportedOperationException • Why would anyone ever want the program to crash? 27 Exception example public void get(int index) { if (index < 0 || index >= size) { throw new ArrayIndexOutOfBoundsException(index); } return elementData[index]; } – Exercise: Modify the rest of ArrayIntList to state preconditions and throw exceptions as appropriate. 28 Postconditions • postcondition: Something your method promises will be true at the end of its execution. – Often documented as a comment on the method's header: // Makes sure that this list's internal array is large // enough to store the given number of elements. // Postcondition: elementData.length >= capacity public void ensureCapacity(int capacity) { // double in size until large enough while (capacity > elementData.length) { elementData = Arrays.copyOf(elementData, 2 * elementData.length); } } – If your method states a postcondition, clients should be able to rely on that statement being true after they call the method. 29 Writing testing programs • Some programs are written specifically to test other programs. • If we wrote ArrayIntList and want to give it to others, we must make sure it works adequately well first. • Write a client program with a main method that constructs several lists, adds elements to them, and calls the various other methods. 30 Tips for testing • You cannot test every possible input, parameter value, etc. – Even a single (int) method has 2^32 different possible values! – So you must think of a limited set of tests likely to expose bugs. • Think about boundary cases – positive, zero, negative numbers – right at the edge of an array or collection's size • Think about empty cases and error cases – 0, -1, null; an empty list or array – an array or collection that contains null elements • Write helping methods in your test program to shorten it. 31 More testing tips • Focus on expected vs. actual behavior • the test shouldn't just call methods and print results; it should: – – – – call the method(s) compare their results to a known correct expected value if they are the same, report that the test "passed" if they differ, report that the test "failed" along with the values • test behavior in combination – maybe add usually works, but fails after you call remove – what happens if I call add then size? remove then toString? – make multiple calls; maybe size fails the second time only 32 Example ArrayIntList test public static void main(String[] args) { int[] a1 = {5, 2, 7, 8, 4}; int[] a2 = {2, 7, 42, 8}; int[] a3 = {7, 42, 42}; helper(a1, a2); helper(a2, a3); helper(new int[] {1, 2, 3, 4, 5}, new int[] {2, 3, 42, 4}); } public static void helper(int[] elements, int[] expected) { ArrayIntList list = new ArrayIntList(elements); for (int i = 0; i < elements.length; i++) { list.add(elements[i]; } list.remove(0); list.remove(list.size() - 1); list.add(2, 42); for (int i = 0; i < expected.length; i++) { if (list.get(i) != expected[i]) { System.out.println("fail; expect " + Arrays.toString(expected) + ", actual " + list); } } } 33 Finishing ArrayIntList • Let's add the following features to ArrayIntList: – a constant for the default list capacity – better encapsulation and protection of implementation details – a better way to print list objects 34 Class constants public static final type name = value; • class constant: a global, unchangeable value in a class – used to store and give names to important values used in code – documents an important value; easier to find and change later • classes will often store constants related to that type – Math.PI – Integer.MAX_VALUE, Integer.MIN_VALUE – Color.GREEN // default array length for new ArrayIntLists public static final int DEFAULT_CAPACITY = 10; 35 "Helper" methods • Currently our list class has a few useful "helper" methods: – public void checkResize() – public void checkIndex(int index, int min, int max) • We wrote them to help us implement other required methods. • We don't want clients to call these methods; they are internal. – How can we stop clients from calling them? 36 A private method private type name(type name, ..., type name) { statement(s); } • a private method can be seen/called only by its own class – encapsulated, similar to fields – your object can call the method on itself, but clients cannot call it – useful for "helper" methods that clients shouldn't directly touch private void checkIndex(int index, int min, int max) { if (index < min || index > max) { throw new IndexOutOfBoundsException(index); } } 37 Printing an ArrayIntList • Currently our list class has a print method: // client code ArrayIntList list = new ArrayIntList(); ... list.print(); – Why is this a bad idea? What would be better? 38 The toString method • Tells Java how to convert an object into a String ArrayIntList list = new ArrayIntList(); System.out.println("list is " + list); • Syntax: public String toString() { code that returns a suitable String; } • Every class has a toString, even if it isn't in your code. – The default is the class's name and a hex (base-16) number: ArrayIntList@9e8c34 39 toString solution // Returns a String representation of the list. public String toString() { if (size == 0) { return "[]"; } else { String result = "[" + elementData[0]; for (int i = 1; i < size; i++) { result += ", " + elementData[i]; } result += "]"; return result; } } 40 Exercise • Write a class called StutterIntList. – Its constructor accepts an integer stretch parameter. – Every time an integer is added, the list will actually add stretch number of copies of that integer. • Example usage: StutterIntList list = new StutterIntList(3); list.add(7); // [7, 7, 7] list.add(-1); // [7, 7, 7, -1, -1, -1] list.add(2, 5); // [7, 7, 5, 5, 5, 7, -1, -1, -1] list.remove(4); // [7, 7, 5, 5, 7, -1, -1, -1] System.out.println(list.getStretch()); // 3 41 Inheritance • inheritance: Forming new classes based on existing ones. – a way to share/reuse code between two or more classes – superclass: Parent class being extended. – subclass: Child class that inherits behavior from superclass. • gets a copy of every field and method from superclass 42 An Employee class public class Employee { ... public int getHours() { return 40; } // works 40 hours / week public double getSalary() { return 40000.0; // $40,000.00 / year } public int getVacationDays() { return 10; // 2 weeks' paid vacation } } public String getVacationForm() { return "yellow"; // use the yellow form } • Lawyers, Secretaries, etc. have similar behavior to the above. • How to implement those classes without redundancy? 43 Inheritance syntax public class name extends superclass { – Example: public class Lawyer extends Employee { ... } • By extending Employee, each Lawyer object now: – receives a copy of each method from Employee automatically – can be treated as an Employee by client code 44 Overriding methods • override: To replace a superclass's method by writing a new version of that method in a subclass. – No special syntax is required to override a method. Just write a new version of it in the subclass. public class Lawyer extends Employee { // overrides getSalary method in Employee class; // give Lawyers a $5K raise public double getSalary() { return 45000.00; } } 45 super keyword • Subclasses can call overridden methods with super super.method(parameters) – Example: public class Lawyer extends Employee { // give Lawyers a $5K raise (better) public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + 5000.00; } } – This version makes sure that Lawyers always make $5K more than Employees, even if the Employee's salary changes. 46 Calling super constructor super(parameters); – Example: public class Lawyer extends Employee { public Lawyer(String name) { super(name); // calls Employee constructor } ... } – super allows a subclass constructor to call a superclass one. – The super call must be the first statement in the constructor. – Constructors are not inherited; If you extend a class, you must write all the constructors you want your subclass to have. 47 Exercise solution public class StutterIntList extends ArrayIntList { private int stretch; public StutterIntList(int stretchFactor) { super(); stretch = stretchFactor; } public StutterIntList(int stretchFactor, int capacity) { super(capacity); stretch = stretchFactor; } public void add(int value) { for (int i = 1; i <= stretch; i++) { super.add(value); } } public void add(int index, int value) { for (int i = 1; i <= stretch; i++) { super.add(index, value); } } } public int getStretch() { return stretch; } 48 Subclasses and fields public class Employee { private double salary; ... } public class Lawyer extends Employee { ... public void giveRaise(double amount) { salary += amount; // error; salary is private } } • Inherited private fields/methods cannot be directly accessed by subclasses. (The subclass has the field, but it can't touch it.) – How can we allow a subclass to access/modify these fields? 49 Protected fields/methods protected type name; // field protected type name(type name, ..., type name) { statement(s); // method } • a protected field or method can be seen/called only by: – the class itself, and its subclasses – also by other classes in the same "package" (discussed later) – useful for allowing selective access to inner class implementation public class Employee { protected double salary; ... } 50 Our list classes • We implemented the following two list classes: – ArrayIntList index 0 1 2 value 42 -3 17 data next – LinkedIntList front 42 data next -3 data next 17 – Problem: • We should be able to treat both lists the same way in client code. 51 Recall: ADT interfaces (11.1) • abstract data type (ADT): A specification of a collection of data and the operations that can be performed on it. – Describes what a collection does, not how it does it. • Java's collection framework describes ADTs with interfaces: – Collection, Deque, List, Map, Queue, Set, SortedMap • An ADT can be implemented in multiple ways by classes: – ArrayList and LinkedList – HashSet and TreeSet – LinkedList , ArrayDeque, etc. implement List implement Set implement Queue • Exercise: Create an ADT interface for the two list classes. 52 An IntList interface (16.4) // Represents a list of integers. public interface IntList { public void add(int value); public void add(int index, int value); public int get(int index); public int indexOf(int value); public boolean isEmpty(); public void remove(int index); public void set(int index, int value); public int size(); } public class ArrayIntList implements IntList { ... public class LinkedIntList implements IntList { ... 53 Our list classes • We have implemented the following two list collection classes: – ArrayIntList index 0 1 2 value 42 -3 17 data next – LinkedIntList front 42 data next -3 data next 17 – Problem: • They can store only int elements, not any type of value. 54 Type Parameters (Generics) ArrayList<Type> name = new ArrayList<Type>(); • Recall: When constructing a java.util.ArrayList, you specify the type of elements it will contain between < and >. – We say that the ArrayList class accepts a type parameter, or that it is a generic class. ArrayList<String> names = new ArrayList<String>(); names.add("Marty Stepp"); names.add("Stuart Reges"); 55 Implementing generics // a parameterized (generic) class public class name<Type> { ... } – By putting the Type in < >, you are demanding that any client that constructs your object must supply a type parameter. • You can require multiple type parameters separated by commas. – The rest of your class's code can refer to that type by name. • Exercise: Convert our list classes to use generics. 56 Generics and arrays (15.4) public class Foo<T> { private T myField; public void method1(T param) { myField = new T(); T[] a = new T[10]; } // ok // error // error } – You cannot create objects or arrays of a parameterized type. 57 Generics/arrays, fixed public class Foo<T> { private T myField; public void method1(T param) { myField = param; T[] a2 = (T[]) (new Object[10]); } // ok // ok // ok } – But you can create variables of that type, accept them as parameters, return them, or create arrays by casting Object[]. 58 Comparing generic objects public class ArrayList<E> { ... public int indexOf(E value) { for (int i = 0; i < size; i++) { // if (elementData[i] == value) { if (elementData[i].equals(value)) { return i; } } return -1; } } – When testing objects of type E for equality, must use equals 59 Generic linked list nodes public class ListNode<E> { public E data; public ListNode<E> next; ... } – For a generic linked list, the node class must also accept the type parameter E 60 Generic interface (15.3, 16.5) // Represents a list of values. public interface List<E> { public void add(E value); public void add(int index, E value); public E get(int index); public int indexOf(E value); public boolean isEmpty(); public void remove(int index); public void set(int index, E value); public int size(); } public class ArrayIntList<E> implements IntList<E> { ... public class LinkedIntList<E> implements IntList<E> { ... 61