610 likes | 632 Views
Learn to implement a collection class (ArrayIntList) in Java by writing a program to read, store, and manipulate integers in reverse order. Explore implementing list operations and enhancing features for ArrayIntList.
E N D
Building Java ProgramsChapter 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
Solution using arrays int[] nums = new int[100]; // make a really big array int size = 0; 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 }
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?
Other possible operations public static void add(int[] list, int size, int value, int index) public static void remove(int[] list, int size, int index) public static void find(int[] list, int size, int value) public static void 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)
Exercise • Let's write a class that implements a list using an int[] • We'll call it ArrayIntList • behavior: • add(value), add(index, value) • get(index), set(index, value) • size() • remove(index) • indexOf(value) • toString() ... • The list's size will be the number of elements added to it so far. • How will the list be used?...
Implementing add • How do we add to the end of a list? • list.add(42);
Implementing add, cont. • To add to end of list, just store element and increase size: public void add(int value) { list[size] = value; size++; } • list.add(42);
Implementing add (2) • How do we add to the middle or end of the list? • list.add(3, 42); // insert 42 at index 3
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 • list.add(3, 42); // insert 42 at index 3 • Note: The order in which you traverse the array matters!
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; } • list.add(3, 42);
Other methods • Let's implement the following methods next: • size • get • set • toString
Implementing remove • How can we remove an element from the list? • list.remove(2); // delete 9 from index 2
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? • list.remove(2); // delete 9 from index 2
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?) } • list.remove(2); // delete 9 from index 2
Running out of space • What should we do if the client adds more than 10 elements? • list.add(15); // add an 11th element
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)) {
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?
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
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; } }
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?
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 this(parameters); from another constructor:
Revised constructors public ArrayIntList(int capacity) { elementData = new int[capacity]; size = 0; } public ArrayIntList() { this(10); // calls (int) constructor }
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) • Answer: Currently the list allows this and returns 0. • Is this good or bad? What (if anything) should we do about it?
Preconditions • precondition: Something your method assumes is trueat 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?
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)
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?
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.
Postconditions • postcondition: Something your method promises will be trueat 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.
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.
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.
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
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); } } }
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
Class constants public static final typename = 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;
"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?
A private method privatetypename(typename, ..., typename) { 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); } }
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?
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
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; } }
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
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
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?
Inheritance syntax public class nameextends 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
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; } }
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.
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.
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; } }
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?
Protected fields/methods protectedtypename; // field protectedtypename(typename, ..., typename) { 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; ... }