514 views
# Equality, Casting and Memory Diagrams ::: danger These notes have the lecture 9 material on equality and casting types, but do not have all of the memory diagram content from that lecture. ::: ## Motivating Question What does it mean for two objects to be "equal"? ## Conceptual Study Questions - In what cases would two objects created through two uses of `new` be equal under `==`? - Why do we use `.equals` rather than `==` to compare strings? - What's an example in which two objects could be "equal" according to `toString` comparisons, but not equal via either `==` or an `equals` method? - How would casting the type of an object change what's in the memory diagram for a program? ## Defining "Equal" on objects Consider the following code. It creates two (mutable) lists with the same numbers in the same order, then has four `println` statements that ask whether the two lists are equal. ```=java= public static void equalityExample() { MutableList<Integer> L1 = new MutableList<Integer>(); L1.addFirst(6); L1.addFirst(8); System.out.println("L1 is " + L1); MutableList<Integer> L2 = new MutableList<Integer>(); L2.addFirst(6); L2.addFirst(8); System.out.println("L2 is " + L2); // what do you expect each of these to produce? // (what do == and .equals mean?) System.out.println(L1 == L2); System.out.println(L1.equals(L2)); System.out.println(L1.toString() == L2.toString()); System.out.println(L1.toString().equals(L2.toString())); } ``` Line 14 uses `==` to compare objects. This returns true when both sides refer to the exact same object *in memory* (or the same atomic value like an int or bool). Line 15 uses a method called `equals` to decide whether two objects are considered the same. The programmer who creates a class can decide what it means for two objects to be equal. For example, imagine a `Course` class with a department name, course number, and current enrollment. We might consider two `Course` objects to be the same if they had the same department and course number, even if they had different enrollments (we'll see how to write this `equals` method momentarily). Lines 16 and 17 try a different approach: they convert each object to a string (using `toString`, another method that programmers control), then compare the strings. The `equals` method on strings compares the characters one by one (so line 17 would return `true`). But `==` on strings asks whether the two strings are in the same spot in memory (as with objects). This gets into a technicality about strings being stored inside objects, that we don't care about here (line 16 would produce `false`). Rule of thumb for this course: always use `.equals` to compare strings. Put differently, `==` lets you test "object equality", where `equals` lets you check "content equality" (also known as "structural equality"), where the creator of a class determines what that means for objects of that class. Every class starts with a default `equals` method that checks `==`. If you want finer-grained control, write an `equals` method. ## Writing `equals` methods Let's write an `equals` method for a `Course` class. We'll say that two courses are equal if their `dept` and `number` fields have the same values, but we don't care about the `enrolled` field. We're going to develop this in stages, to show how to get from our core idea to working Java code. Here's the core idea that we want to implement: ```=java= public class Course { private String dept; private int number; private int enrolled; @Override // Initial (not yet finished) example of an equals method. public boolean equals(Course other) { return (this.dept.equals(other.dept)) && (this.number == other.number); // could also compare enrolled if wanted to } } } ``` This is a start, but it isn't sufficiently general. What if someone wrote the code `myCourse.equals("Bruno")`? This is perfectly valid code, but the above `equals` method can't handle this because it expects the input argument to be a `Course`. Why do we care? There's no way that a Course could be equal to a String anyway. True, but we're trying to override the default `equals` method, which is designed to work on ANY other object. We have to provide that functionality. We can let the `other` input be of any type by replacing the type `Course` with the type `Object`. `Object` is a built-in class that every class you create extends (even though you don't see it). Using type `Object` tells Java "accept any object as a valid input to `other`". ```=java= public class Course { @Override // Initial (not yet finished) example of an equals method. public boolean equals(Object other) { return (this.dept.equals(other.dept)) && (this.number == other.number); // could also compare enrolled if wanted to } } } ``` Now, Java complains again: line 5 asks for `other.dept`, but that field only exists on `Course` objects. We need to make sure that we only try to access the `dept` field on `Course` objects. We can ask what class an object is from using something called `instanceOf`. This construct should ONLY be used within `equals` methods (or perhaps in the creation of more advanced Java tools, beyond what we will do in this course). ```=java= public class Course { private String dept; private int number; private int enrolled; @Override // Example of an equals method. Since equals can be called with any // other object as the argument, we use type Object for the parameter public boolean equals(Object other) { if (!(other instanceof Course)) { // if other isn't a Course, this and otherObj aren't equal return false; } else { return (this.dept.equals(other.dept)) && (this.number == other.number); // could also compare enrolled if want to } } } ``` Unfotunately, Java still won't accept this code. It will complain that it can't confirm that `other` is a `Course` on line 15. What the heck? Isn't that what we just checked on line 11? Indeed it is, but the part of the language that runs programs doesn't know that. The error checking part of Java works with the types that the program has ascribed for each object. From a types perspective, all the programmer has explicitly said (in code) is that `other` is an `Object`. (While it would be technically possible for Java to share the type information from the if with other parts of the code, it turns out to be very expensive to do this in the general case). ## Casting So, we need some way to tell Java that it should treat `other` as a `Course`. See the new line 15 for how this gets done: ```=java= public class Course { private String dept; private int number; private int enrolled; @Override // Example of an equals method. Since equals can be called with any // other object as the argument, we use type Object for the parameter public boolean equals(Object other) { if (!(other instanceof Course)) { // if other isn't a Course, this and other aren't equal return false; } else { Course otherC = (Course)other; // tell Java otherObj is a Course return (this.dept.equals(otherC.dept)) && (this.number == otherC.number); // could also compare enrolled if want to } } } ``` The notation `(Course)otherObj` tells Java "in your internal tracking of types, record the type of `other` to be `Course`". Another interpretation is "hey Java, stop bugging me about the type of `other` because I (the programmer) know that it is a `Course` at this point." This instruction to re-type something is called "casting". What does casting actually do? It just makes an internal note that when you actually run the program (as oppose to build it to look for errors), Java should make sure that `other` is actually a `Course`. If it isn't, then Java can halt your program with a `RuntimeException`. Casting can't do magic. Consider the following code that tries to "convert" a `Boa` into a `Course`: ```=java Boa b = new Boa("knotty", 15, "berries") Course c = (Course)b; ``` Java would halt your program when you tried to run it, because `b` is not actually a `Course`. In our `equals` method, in contrast, `other` really *is* a `Course` at that point in the code, it's just that Java didn't know that when checking for errors. ## The class `Object` We mentioned that `Object` is a built-in Java class that every class you define extends. `Object` defines a bunch of basic methods for all classes, including: - a default `equals` method that checks whether two objects are `== - a default `toString` method that returns the location of the object in memory - many others, some of which we'll see later this semester You as the programmer can `@Override` any of these defaults to what you want to have for your classes. The `Object` type is useful in cases where we want to say "I need some object, any object, but I really don't care what class it is from" (as with the other input to `equals`). Most times, it's far too general purpose to be useful though, so we only see it in limited situations.