These notes are an abridged, concatenated, and expanded version of Jonathan Shewchuk's 61B Spring 2014 notes. The originals can be found at: http://www.cs.berkeley.edu/~jrs/61b/lec/06 http://www.cs.berkeley.edu/~jrs/61b/lec/11 http://www.cs.berkeley.edu/~jrs/61b/lec/13 Lecture 16: Java Loose ends, featuring: .equals(), .toString(), immutability, and access control. equals() ======== Every class has an equals() method. If you don't define one explictly, you inherit Object.equals(), for which "r1.equals(r2)" returns the same boolean value as "r1 == r2", where r1 and r2 are references. However, many classes override equals() to compare the _content_ of two objects. Integer (in the java.lang library) is such a class; it stores one private int. Two distinct Integer objects are equals() if they contain the same int. In the following example, "i1 == i2" is false, but "i1.equals(i2)" is true. "i2 == i3" and "i2.equals(i3)" are both true. --- ------- --- ------- --- i1 |.+--->| 7 | i2 |.+--->| 7 |<---+.| i3 --- ------- --- ------- --- IMPORTANT: r1.equals(r2) throws a run-time exception if r1 is null. There are at least four different degrees of equality. (1) Reference equality, ==. (The default inherited from the Object class.) (2) Shallow structural equality: two objects are "equals" if all their fields are ==. For example, two SLists whose "size" fields are equal and whose "head" fields point to the same SListNode. (3) Deep structural equality: two objects are "equals" if all their fields are "equals". For example, two SLists that represent the same sequence of items (though the SListNodes may be different). (4) Logical equality. For example: Two "Set" objects are "equals" if they contain the same elements, even if the underlying lists store the elements in different orders. The equals() method for a particular class may test any of these four levels of equality, depending on what seems appropriate. Let's write an equals() method for SLists that tests for deep structural equality. The following method returns true only if the two lists represent identical sequences of items. public class SList { public boolean equals(Object other) { if (!(other instanceof SList)) { // Reject non-SLists. return false; } SList o = (SList) other; if (size != o.size) { return false; } SListNode n1 = head; SListNode n2 = o.head; while (n1 != null) { if (!n1.item.equals(n2.item)) { // Deep equality of the items. return false; } n1 = n1.next; n2 = n2.next; } return true; } } Note that this implementation may fail if the SList invariants have been corrupted. (A wrong "size" field or a loop in an SList can make it fail.) IMPORTANT: Overriding DOESN'T WORK if we change the signature of the original method, even just to change a parameter to a subclass. In the Object class, the signature is equals(Object), so in the code above, we must declare "other" to be an Object too. Suppose we make the common rookie mistake of giving the equals method the signature boolean equals(SList other). Our will compile just but it will NOT override Object's equals method. That means the code Object s = new SList(); s.equals(s); will call Object.equals(), not SList.equals(). Dynamic method lookup won't care that s is an SList, because the equals(SList) method above does NOT override equals(Object). In short, if you want to override a method, make sure the signature is EXACTLY the same. toString() ======== Explanation of string concatenation and how toString() works goes here. immutability ======== Some classes in Java are immutable. This means they can never change. Examples are String, etc. Can use final keyword. Java's "final" keyword is used to declare a value that can never be changed. If you find yourself repeatedly using a numerical value with some "meaning" in your code, you should probably turn it into a "final" constant. BAD: if (month == 2) { GOOD: public final static int FEBRUARY = 2; // Usually near top of class. ... if (month == FEBRUARY) { Why? Because if you ever need to change the numerical value assigned to February, you'll only have to change one line of code, rather than hundreds. You can't change the value of FEBRUARY after it is declared and initialized. If you try to assign another value to FEBRUARY, you'll have a compiler error. The custom of rendering constants in all-caps is long-established and was inherited from C. (The compiler does not require it, though.) For any array x, "x.length" is a "final" field. You can declare local parameters "final" to prevent them from being changed. void myMethod(final int x) { x = 3; // Compiler ERROR. Don't mess with X's! } "final" is usually used for class variables (static fields) and parameters, but it can be used for instance variables (non-static fields) and local variables too. It only makes sense for these to be "final" if the variable is declared with an initializer that calls a method or constructor that doesn't always return the same value. class Bob { public final long creationTime = System.currentTimeMillis(); } When objects of the Bob class are constructed, they record the time at that moment. Afterward, the creationTime can never be changed. Access Control ======== Before, we've seen public, private, and protected fields. A class or variable with package protection is visible to any class in the same package, but not to classes outside the package (i.e., files outside the directory). The files in a package are presumed to trust each other, and are usually implemented by the same person. Files outside the package can only see the public classes, methods, and fields. (Subclasses outside the package can see the protected methods and fields as well.) Here's the correspondence between declarations and their visibility. Visible: in the same package in a subclass everywhere Declaration "public" X X X "protected" X X default (package) X "private" Access control applies at the member level and also at the class level. (more soon)