Lecture 11 The End of Java
Lists and Sets in Java
Lists in Real Java Code
We built a list from scratch, but Java provides a built-in List
interface and several implementations, e.g. ArrayList
.
java.util.List<Integer> L = new java.util.ArrayList<>();
L.add(5);
L.add(10);
L.add(15);
System.out.println(L);
By including import java.util.List
and import java.util.ArrayList
at the top of the file, we can make our code more compact.
import java.util.List;
import java.util.ArrayList;
public class SimpleBuiltInListExample {
public static void main(String[] args) {
List<Integer> L = new ArrayList<>();
/* If we import, we can use the “simple name” (ArrayList)
* as opposed to the longer “canonical name” (java.util.ArrayList). */
L.add(5);
L.add(10);
L.add(15);
System.out.println(L);
}
}
Sets and ArraySet
Another handy data structure is the set.
- Stores a set of values with no duplicates. Has no sense of order.
Today we’re going to write our own Set called ArraySet
.
- Won’t be implementing any specific interface (for now).
ArraySet<String> S = new ArraySet<>();
S.add("Tokyo");
S.add("Beijing");
S.add("Lagos");
S.add("São Paulo");
System.out.println(S.contains("Tokyo"));
System.out.println(S.size());
Goals
Goal 1: Create a class ArraySet with the following methods:
-
add(value)
: Add the value to the ArraySet if it is not already present. -
contains(value)
: Checks to see if ArraySet contains the key. -
size()
: Returns number of values.
Ok to ignore resizing for this exercise.
- In lecture, I’ll just give away the answer, but you might find implementing it useful.
ArraySet (Basic Implementation)
Array implementation of a Set:
-
Use an array as the core data structure.
-
contains(x)
: Checks to see if x is in the underlying array. -
add(x)
: Checks to see if x is in the underlying array, and if not, adds it.
public class ArraySet<T> {
private T[] items;
private int size;
public ArraySet() {
items = (T[]) new Object[100];
size = 0;
}
public boolean contains(T x) {
for (int i = 0; i < size; i += 1) {
if (items[i].equals(x)) {
return true;
}
}
return false;
}
public void add(T x) {
if (!contains(x)) {
items[size] = x;
size += 1;
}
}
}
Exceptions
Basic idea:
-
When something goes really wrong, break the normal flow of control.
-
So far, we’ve only seen implicit exceptions, like the one below.
public static void main(String[] args) {
ArraySet<String> s = new ArraySet<>();
s.add(null);
s.add("horse");
}
$ java ExceptionDemo
Exception in thread "main" java.lang.NullPointerException
at ArraySet.contains(ArraySet.java:16)
at ArraySet.add(ArraySet.java:26)
at ArraySet.main(ArraySet.java:40)
Explicit Exceptions
We can also throw our own exceptions using the throw keyword.
-
Can provide more informative message to a user.
-
Can provide more information to code that “catches” the exception.
More on “catching” at end of the course.
public void add(T x) {
if (x == null) {
throw new IllegalArgumentException("Cannot add null!");
}
...
}
$ java ExceptionDemo
Exception in thread "main"
java.lang.IllegalArgumentException: Cannot add null!
at ArraySet.add(ArraySet.java:27)
at ArraySet.main(ArraySet.java:42)
Arguably this is a bad exception.
-
Our code now crashes when someone tries to add a null.
-
Other fixes:
- Ignore nulls.
- Fix contains so that it doesn’t crash if items[i] is null.
Iteration
The Enhanced For Loop
Java allows us to iterate through Lists and Sets using a convenient shorthand syntax sometimes called the foreach
or enhanced for
loop.
Set<Integer> javaset = new HashSet<>();
javaset.add(5);
javaset.add(23);
javaset.add(42);
for (int i : javaset) {
System.out.println(i);
}
- This doesn’t work with our ArraySet.
- Let’s strip away the magic so we can build our own classes that support this.
ArraySet<Integer> aset = new ArraySet<>();
aset.add(5);
aset.add(23);
aset.add(42);
for (int i : aset) {
System.out.println(i);
}
$ javac IterationDemo
error: for-each not applicable to expression type
for (int i : S) {
^
required: array or java.lang.Iterable
found: ArraySet<Integer>
How Iteration Really Works
An alternate, uglier way to iterate through a List is to use the iterator()
method.
public Iterator<E> iterator();
//"Nice" iteration
Set<Integer> javaset = new HashSet<Integer>();
...
for (int x : javaset) {
System.out.println(x);
}
//"Ugly" iteration.
Set<Integer> javaset = new HashSet<Integer>();
...
Iterator<Integer> seer = javaset.iterator();
while (seer.hasNext()) {
System.out.println(seer.next());
}
Supporting Ugly Iteration in ArraySets
To support ugly iteration:
-
Add an
iterator()
method toArraySet
that returns anIterator<T>
. -
The
Iterator<T>
that we return should have a usefulhasNext()
andnext()
method.
public interface Iterator<T> {
boolean hasNext();
T next();
}
Iterator<Integer> aseer = aset.iterator();
while (aseer.hasNext()) {
System.out.println(aseer.next());
}
Completed ArraySet iterator Method
private class ArraySetIterator implements Iterator<T> {
private int wizPos;
public ArraySetIterator() {
wizPos = 0;
}
public boolean hasNext() {
return wizPos < size;
}
public T next() {
T returnItem = items[wizPos];
wizPos += 1;
return returnItem;
}
}
public Iterator<T> iterator() {
return new ArraySetIterator();
}
The Enhanced For Loop
Our code now supports “ugly” iteration, but enhanced for loop still doesn’t work.
The problem: Java isn’t smart enough to realize that our ArraySet has an iterator() method.
- Luckily there’s an interface for that.
To support the enhanced for loop, we need to make ArraySet implement the Iterable interface.
- There are also some default methods in Iterable, not shown.
public interface Iterable<T> {
Iterator<T> iterator();
}
public class ArraySet<T> implements Iterable<T> {
...
public Iterator<T> iterator() { ... }
}
Iteration Summary
To support the enhanced for loop:
-
Add an
iterator()
method to your class that returns anIterator<T>
. -
The
Iterator<T>
returned should have a usefulhasNext()
andnext()
method. -
Add implements
Iterable<T>
to the line defining your class.
Object Methods:Equals and toString()
Objects
All classes are hyponyms of Object.
-
String toString()
-
boolean equals(Object obj)
-
Class<?> getClass()
-
int hashCode()
- Very important, but will disscuss later.
-
protected Object clone()
-
protected void finalize()
-
void notify()
-
void notifyAll()
-
void wait()
-
void wait(long timeout)
-
void wait(long timeout, int nanos)
toString()
The toString() method provides a string representation of an object.
-
System.out.println(Object x)
callsx.toString()
- If you’re curious:
println
callsString.valueOf
which calls toString.
- If you’re curious:
-
The implementation of
toString()
in Object is the the name of the class, then an @ sign, then the memory location of the object.
ArraySet toString
Let’s try implementing toString for ArraySet.
One approach is shown below.
- Warning: This code is slow. Intuition: Adding even a single character to a string creates an entirely new string. It’s because Strings are “immutable”.
@Override
public String toString() {
String returnString = "{";
for (int i = 0; i < size; i += 1) {
returnString += keys[i];
returnString += ", ";
}
returnString += "}";
return returnString;
}
Much faster approach is shown below.
- Intuition: Append operation for a StringBuilder is fast.
@Override
public String toString() {
StringBuilder returnSB = new StringBuilder("{");
for (int i = 0; i < size; i += 1) {
returnSB.append(items[i]);
returnSB.append(", ");
}
returnSB.append("}");
return returnSB.toString();
}
Equals vs. ==
As mentioned in an offhand manner previously, ==
and .equals()
behave differently.
==
compares the bits. For references,==
means “referencing the same object.”
To test equality in the sense we usually mean it, use:
-
.equals
for classes. Requires writing a.equals
method for your classes.- Default implementation of .equals uses == (probably not what you want).
-
BTW: Use
Arrays.equal
orArrays.deepEquals
for arrays.
The Default Implementation of Equals
ArraySet<Integer> aset = new ArraySet<>();
aset.add(5);
aset.add(23);
aset.add(42);
System.out.println(aset);
ArraySet<Integer> aset2 = new ArraySet<>();
aset2.add(5);
aset2.add(23);
aset2.add(42);
System.out.println(aset.equals(aset2));
/* Returns false because the default implementation of equals just uses ==. */
ArraySet equals
The implementation below is a good start, but fails if o is null or another class.
@Override
public boolean equals(Object o) {
ArraySet<T> other = (ArraySet<T>) o;
if (this.size() != other.size()) { return false; }
for (T item : this) {
if (!other.contains(item)) {
return false;
}
}
return true;
}
The implementation below is much better, but we can speed things up.
@Override
public boolean equals(Object o) {
if (o == null) { return false; }
if (this.getClass() != o.getClass()) { return false; }
ArraySet<T> other = (ArraySet<T>) o;
if (this.size() != other.size()) { return false; }
for (T item : this) {
if (!other.contains(item)) {
return false;
}
}
return true;
}
The code below is pretty close to what a standard equals method looks like.
@Override
public boolean equals(Object o) {
if (o == null) { return false; }
if (this == o) { return true; } // optimization
if (this.getClass() != o.getClass()) { return false; }
ArraySet<T> other = (ArraySet<T>) o;
if (this.size() != other.size()) { return false; }
for (T item : this) {
if (!other.contains(item)) {
return false;
}
}
return true;
}
Summary
We built our own Array based Set implementation.
To make it more industrial strength we:
-
Added an exception if a user tried to add null to the set.
- There are other ways to deal with nulls. Our choice was arguably bad.
-
Added support for “ugly” then “nice” iteration.
- Ugly iteration: Creating a subclass with next and hasNext methods.
- Nice iteration: Declaring that ArraySet implements Iterable.
-
Added a toString() method.
- Beware of String concatenation.
-
Added an equals(Object) method.
- Make sure to deal with null and non-ArraySet arguments!
- Used getClass to check the class of the passed object. Use sparingly.
Even Better toString
and ArraySet.of
@Override
public String toString() {
List<String> listOfItems = new ArrayList<>();
for (T x : this) {
listOfItems.add(x.toString());
}
return "{" + String.join(", ", listOfItems) + "}";
}
public static <Glerp> ArraySet<Glerp> of(Glerp... stuff) {
ArraySet<Glerp> returnSet = new ArraySet<Glerp>();
for (Glerp x: stuff) {
returnSet.add(x);
}
return returnSet;
}