Lab 3 - Prelab Reading
Lab 3 - Prelab Reading
Spring 2025
1.1 java.lang.Object
For the sake of this week’s lab, you are only required to know that all classes in Java inherit (directly or indirectly) from
the class Object (java.lang.Object), which means that they inherit all the methods defined in this class. Only the class
Object itself does not have a parent class (it is on the top of the inheritance hierarchy).
When we created the Book class, it automatically inherited all the methods above. To verify that our Book class inherits
the methods in Object, create two Book objects, book1 and book2, then try the following codes:
1 System .out. println ( book1 . toString ()); // equivalent to System .out. print ( book1 )
2 System .out. println ( book1 . equals ( book2 ));
• toString(): returns a String that is a function of the class name and memory location of the object (try the codes
above). Typically, we would want a different String representation; for example, we might want the book to be
represented by its title and author, rather than by a code that is a function of its memory location.
1
• equals(): by default is equivalent to ==, that is, it returns if the two references are aliases (i.e., if they are pointing to
the same memory location.. more explanation on this in the next section). So even if book1 and book2 have the same
title, author, and publication year, comparing them with equals will return false by default unless they are aliases.
We might want to change (override) this default behavior, and this is what you will do in lab 3.
• clone(): has a default implementation that is good enough, but the method is not public, so we need to override its
visibility to public. In lab 3, you will override the visibility of the clone method, and you will also give your own
implementation for it (although we can reuse the inherited implementation, but this would require additional coding
related to exception handling, which we will not deal with in this lab).
Key Takeaways
• All classes in Java inherit directly or indirectly from the Object class, which provides some useful methods like
toString, clone, and equals. We can directly use these methods with their default implementations, or we can
override (i.e., customize) their behavior for our newly created types.
• The proper way of comparison for primitive types (e.g., int, double) is using ==, whereas the proper way of
comparison for reference types (e.g., String, Book) is using the equals method, which is inherited from Object.
By default, i.e., if it is not overridden, equals is equivalent to ==.
2
2 References vs. Objects, and Copying Objects
There is a distinction between references and objects: References “point to” objects in memory, and multiple references can
point to the same object.
2.1 Example
Consider the definitions below from the skeleton of problem 2 in lab 3:
1 Book originalBook = new Book(" Mornings in Jenin ", " Suzan Abulhawa ", 2010) ;
2 Book copiedBook = new Book( originalBook );// copy constructor
3 Book clone = originalBook . clone ();
4 Book shallowCopiedBook = originalBook ; //A shallow copy (alias )
The above lines of code create four references but only three distinct objects in memory. Particularly, shallowCopiedBook
and originalCopy point to the same object; i.e., they are aliases. This is illustrated in Figure 1.
Key Takeaways
To safely copy objects, use clone() or a copy constructor or any equivalent copying technique that ensures creating a
new memory location; do not use the assignment operator for copying except in these cases:
1. If it is your intention to create aliases (and you are aware of the consequences)
2. If the copied object is of a primitive type (e.g., int x = 3; int y = x;)
3
3 Immutability
3.1 What is Immutability
The String type in Java is immutable. This means that a String object at a given memory location cannot be mutated,
and even if you attempt to mutate (by concatenating additional characters for example), the contents of the existing memory
location do not change; instead, a new String object (in a new memory location) is created to hold the modified value,
leaving the old location unchanged, as demonstrated in the example of Figure 2.
On the other hand, StringBuilder is another type in Java which is similar to String, but it is mutable; that is,
StringBuilder has methods that mutate the object (at the same memory location), as shown in the example of Figure 3.
In both of the examples above, the outcome is “ab”, so what is the difference? Mutability impacts are mainly
seen when multiple references point to the same object in memory. If a mutable object is modified, all aliases are affected,
whereas if it is immutable, then the aliases are not affected, as illustrated in the example below.
4
• Because it is safe, we can make many aliases pointing to the same memory location, thus saving memory space: this is
actually used for the String type, where Java keeps a pool of the commonly used String literals to be re-used: when
a String object is created using this syntax: String s = “hello”, if an object “hello” already exists in memory, then
java will typically reuse it where s will be safely made an alias to it (no new object created unnecessarily). On the other
hand, if the String object is created using the new keyword (not recommended): String s = new String(“hello”),
then the creation of a new String object is forced.
Key Takeaways
Immutability is one way to avoid the risks of shallow copying. For example, you can always copy String objects using
the assignment operator without worrying about the risks of shallow copying. This is because String is immutable and
any changes to one String reference will not really change its content, but rather create a new String and make it point
to it without affecting the old String or any aliases pointing to it.