Chapter 15 - Building Java Programs

advertisement
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
Download