3. Objects Review

  • Defining objects is similar between Python and Java

  • Most of the differences are syntax related

3.1. Objects and Classes

  • Objects are things that typically contain data, can do some actions, and can be acted on

  • Often, when programming with Java, several objects will be interacting

3.1.1. Objects

  • All objects have

    • Properties

      • These are the data

      • These are called fields

      • They’re variables, but they belong to an instance of an object

    • Behaviours

      • This is what the object can do

      • These are called methods

      • They’re functions, but they belong to an object

3.1.2. Class

  • Every object belongs to a class

    • A class defines what it means to be an object of that class

  • For example

    • One could think of Human as a class

      • With the fields firstName and lastName

      • And methods eat() and goToBed()

    • One could make Bob Smith an instance of the Human class

      • firstName is Bob

      • lastName is Smith

      • Calling the method eat() would tell the specific instance to eat

  • All objects of the same class have the same fields, but their values can differ

    • All instances of Human have firstName and lastName

    • But Bob Smith’s firstName is Bob

    • If there was another instance for Jane Doe, then their firstName would be Jane

  • Objects of the same class have the same methods, but calling a method on an instance only calls it on that instance

3.1.3. But Why?

  • Classes and objects make abstraction easier

    • How many of you know exactly how your HVAC system works?

    • How many of you have ever turned up the heat in your house?

  • It also makes encapsulating ideas together easier

    • Although, there are several arguments as to why this isn’t always great

3.1.4. High-Level Idea

  • A typical class will consist of

    • Fields

    • Methods

    • Constructors (which are also methods)

  • In Java, class code is written in a file with the same name of the class

    • The code is written in a file with a “.java” file extension

    • The Human class would be in a file called Human.java

3.1.5. Contact List Example

  • It may be easier to learn these ideas with an example

  • Consider the following problem

    • There is a need to keep track of the name and email address of friends

    • There is also a need to manage several friends’ names and email addresses

  • To do this, a class could be made to represent a friend — Friend class

  • Another class can be made to keep track of the collection of friends — ContactList class

    • This will be covered in the following topic

3.2. Friend Class

  • For this particular problem, the Friend class can be kept simple

    • This is a good thing

  • The only information the Friend objects need to know is their

    • First Name

    • Last Name

    • Email Address

  • A constructor will be be needed that describes the setup for the Friend object

    • Assigning the values to the fields

  • The behaviours of the Friend class

    • A way to retrieve information from the Friend

    • A way to obtain a string representation of the Friend

    • A way to check if two Friend objects are equal

3.2.1. Setting Fields and Writing the Constructor

  • The constructor is a special method that is called automatically when an object of the class is created

  • Typically, setup related things that needs to happen for the object will be put in the constructor

  • In Python, the Friend class’ constructor and the creation and assigning of fields would look like the following

    • Remember, in Python self is used to refer to an instance of the class

1class Friend:
2
3    # Python --- Constructor and creating and setting fields
4    def __init__(self, first_name, last_name, email):
5        self.first_name = first_name
6        self.last_name = last_name
7        self.email = email

In Java, the class’ declaration of fields, constructor, and assigning values to the fields would look like the following

 4/***
 5 * A plain old java class to keep track of friends. A Friend will know its first name, last name, and email address.
 6 * This class provides getters/accessor methods for the attributes and can be checked for equality.
 7 */
 8public final class Friend {
 9
10    // Fields for the Friend Class
11    private final String firstName;
12    private final String lastName;
13    private final String email;
14
15    /**
16     * Create an instance of a Friend. The constructor takes the first name, last name, and email address of the Friend
17     * to be created.
18     *
19     * @param firstName First name of the friend
20     * @param lastName  Last name of the friend
21     * @param email     Email address of the friend
22     */
23    public Friend(String firstName, String lastName, String email) {
24        this.firstName = firstName;
25        this.lastName = lastName;
26        this.email = email;
27    }
  • The class is set to public so it can be accessed from any other class

  • The class is also set to final since, once an instance is created, it should not change — immutable

  • The fields are declared inside the class, but not within any method

    • They can be accessed by the whole class

  • To provide control over how the fields are accessed outside the class, they are set to private and final

    • private means the fields are not directly accessible outside the class

      • If they were assigned public, it would behave like Python

    • Although private, the values of the fields will ultimately be accessible, but through accessor methods

      • Discussed in more detail below

    • The Friend class will be made in such a way that the data is immutable — it doesn’t change once set

      • Thus, the fields are set to final so they can be set once and only once

  • Notice the java documentation (javadoc) comment is above the method

  • The constructor is public, has the same name as the class and file, and does not have a self parameter

    • Although Java does have a similar keyword — this

  • The this in the above example let’s Java resolve the ambiguity between the field and constructor parameter

    • this.firstName is the field where firstName is a local parameter for the constructor

    • It is not always necessary to use this in Java like how self is used in Python

Note

Two of the major differences seen between Python and Java is the use of the visibility modifiers public/private and final. This was done to tell Java that instances of this class are to be immutable.

At first one may feel these extra keywords make the code too verbose, but consider that these extra keywords provide the programmer with more explicit control over how their code is or is not used. Although these keywords are not necessary, they are very powerful.

3.2.2. Accessors

  • Below are the accessor/getter methods for the fields

  • All these methods do are return their respective values

  • They are public as they should be accessible outside the class

  • Note, however, that there are no methods to set any of the field values

    • The Friend is immutable — can access data, but cannot change it

32    public String getFirstName() {
33        return firstName;
34    }
35
36    public String getLastName() {
37        return lastName;
38    }
39
40    public String getEmail() {
41        return email;
42    }
  • In Python, accessors were not used as one could simply access the field directly

    • my_friend.first_name

  • This could be done in Java if the fields were set to public

  • However, it would make it possible to modify the fields directly, which is not ideal

    • Accessors allow the data to be accessed, but not changed

3.2.3. toString

  • In Python, for creating a string representation of an object, the __repr__ magic method was used

    • If one called print(some_object), the __repr__ would automatically get called

  • All classes inherited a __repr__ for free, but the default behaviors was not all too helpful

    • <__main__.Friend object at 0x7f130d9c52e0>

    • The inherited one simply provides the object name and a memory address

      • Inheritance is a topic discussed later in the course

  • If one wanted to change this behaviour, they would override the default __repr__

  • An example of a __repr__ for the Friend class in Python is below

1def __repr__(self):
2    return f"Friend({self.first_name}, {self.last_name}, {self.email})"
  • An f-string was used in the above example, but string concatenation could have been used

    • "Friend(" + self.first_name + ", " + self.last_name + ", " + self.email + ")"

  • The same principal exists in Java, but the method is called toString

  • The inherited behaviour is a little different — it returns a string of the class name and the object’s hash code

  • One can override the inherited toString

46    public String toString() {
47        return String.format("Friend(%s, %s, %s)", firstName, lastName, email);
48    }
  • In the above example, String.format was used, but string concatenation could have been used

    • "Friend(" + firstName + ", " + lastName + ", " + email + ")"

  • Like Python, toString is automatically get called when printing the object

    • System.out.println(aFriend);

Warning

The idea is that this returns a string; it does not print something.

3.2.4. equals

  • Python also provides the __eq__ magic method for describing equality

1def __eq__(self, other) -> bool:
2    if isinstance(other, Friend):
3        return self.first_name == other.first_name and \
4                    self.last_name == other.last_name and \
5                    self.email == other.email
6    return False
  • In Java, there is an equals method to define what it means for two objects to be equivalent

  • However, unlike Python, it does not overload the == operator

    • == for objects is reserved for checking if two things are literally the same object – aliases

      • Same memory address — it compares the memory addresses

  • equals is used to compare the content of the objects in some way

    • This is where equality between objects of the class is defined

  • Like toString, if not overridden, equals has the inherited behavior of checking sameness — ==

  • For the Friend class, two objects will be equal if all their fields match

52    /**
53     * Checks if two Friend objects are equal. Friend objects are considered equal if all their attributes are equal.
54     *
55     * @param o an "object" being compared to
56     * @return True if the two objects are equal, false otherwise
57     */
58    @Override
59    public boolean equals(Object o) {
60        // If o is actually in the same memory address of this
61        if (o == this) {
62            return true;
63        }
64        // If o is null, then it's not equal
65        if (o == null) {
66            return false;
67        }
68        // if o and this are of different classes, they're not the equal
69        if (o.getClass() != this.getClass()) {
70            return false;
71        }
72        // Cast o as a friend
73        Friend other = (Friend) o;
74        return Objects.equals(this.firstName, other.firstName) &&
75                Objects.equals(this.lastName, other.lastName) &&
76                Objects.equals(this.email, other.email);
77    }
  • There is a lot going on in this method

  • First it checks if the Object o is the actual thing being compared to — if (o == this)

    • If they are aliases for the same object

    • If they are, then obviously they are equal

  • It also check if it is null — if (o == null)

    • If it is null, then clearly this cannot be equal to o

  • It then checks if they are of the same class — if (o.getClass() != this.getClass())

    • If they are not, then they are not equal

  • If the method gets to this point, it knows that o is of class Friend

  • It downcast the Object to class Friend

    • This allows for accessing the fields and methods from Friend

  • Lastly, it check if all the attributes are equal

Note

It is important to understand the difference between someObject == someOtherObject, someObject.equals(someOtherObject), and Objects.equals(someObject, someOtherObject).

With someObject == someOtherObject, it checks if someObject and someOtherObject are referencing the same object – aliases.

someObject.equals(someOtherObject) checks if someObject and someOtherObject are equivalent based on someObject class’ equals method.

Objects.equals(someObject, someOtherObject) is the same as someObject.equals(someOtherObject), but makes the equality check null safe. In other words, it first checks if both someObject and someOtherObject are null, because then they are equal. Further, it won’t produce a NullPointerException if someObject happens to be null. Have a look at the relevant javadocs

The above example makes use of the third option to be safe around null. This is important because, based on the way the class is written, it is possible to have the fields reference null. Consider creating a Friend object with the following — new Friend(null, "Smith", "bsmith@gmail.com"). This would make the field firstName reference null, meaning a call to this.firstName.equals(other.firstName) would result in a null pointer exception.

3.2.4.1. hashCode

  • When properly writing the equals method, one should also write another special method — hashCode()

    • The full details on what hashCode is and what it is for is beyond the scope of this course

    • Briefly, it is a function used to convert the object into an int hash value

    • Any two objects that are equal must have the same hash value

    • Ideally, the hash value should aim to have different hashes

      • Any unequal objects should have different hash values

      • Unfortunately, hash collisions — cases where unequal things have the same hash — are inevitable

  • Below is an example hashCode for the Friend class

    • This hashCode effectively returns the sum of the hash values of the three String attributes

    • For simple classes like the Friend class, this pattern will be typical

82    @Override
83    public int hashCode() {
84        return Objects.hash(firstName, lastName, email);
85    }

3.3. Creating an Instance of a Friend

  • Below is an example of creating an instance of a Friend object based on the Friend class

  • It is a simple example where an instance is created, but that is all

1public class SomeClass {
2    public static void main(String[] args) {
3
4        // Declare a Friend variable
5        // Create an instance of a Friend
6        // Assign the variable to reference the newly created Friend
7        Friend aFriend = new Friend("Bob", "Smith", "bsmith@gmail.com");
8    }
9}
  • There is a bit going on:

    • Declare a variable of type Friend

      • Friend aFriend

    • Create an instance of a Friend object

      • new Friend("Bob", "Smith", "bsmith@gmail.com")

    • Assign the variable to reference the newly created object

      • The single equals is used for assignment — =

Note

Be mindful about what is actually stored in the aFriend variable. The object is not stored in the variable, but a reference to the object is.

../../_images/reference_aFriend.png

The aFriend variable holds a reference to an instance of a Friend object, not a Friend.

  • If the following two lines of code were run, two instances of a Friend would exist

    • Friend aFriend = new Friend("Bob", "Smith", "bsmith@gmail.com");

    • Friend bFriend = new Friend("Jane", "Doe", "jdoe@gmail.com");

  • aFriend would have a firstName of Bob

  • bFriend has a firstName of Jane

  • They both have the firstName field, but the actual value associated with it differs

    ../../_images/reference_aFriend_bFriend.png

    Two reference variables referencing two separate individual Friend objects.

  • Below is an example of two Friend objects being created and being used

    • Get aFriend's first name

    • Use the toString method

    • Use the equals method

1Friend aFriend = new Friend("Bob", "Smith", "bsmith@gmail.com");
2Friend bFriend = new Friend("Jane", "Doe", "jdoe@gmail.com");
3
4System.out.println(aFriend.getFirstName());
5System.out.println(aFriend);
6System.out.println(bFriend);
7System.out.println(aFriend.equals(bFriend));
  • What is the output of the above code?

3.4. References

  • As noted above, be careful about what is actually stored in these variables

  • The objects themselves are not stored in the variables

  • Instead, references to where the objects are in memory are stored in the variables

  • In the below example, bFriend = aFriend copies the contents of aFriend and puts the copy in the bFriend

    • But the contents of the aFriend variable is a reference to a Friend

    • The reference stored in aFriend gets copied; the Friend is not copied

    • This results in an aliases — both aFriend and bFriend reference the exact same object

1Friend aFriend = new Friend("Bob", "Smith", "bsmith@gmail.com");
2Friend bFriend = new Friend("Jane", "Doe", "jdoe@gmail.com");
3
4bFriend = aFriend;
  • This also means that the object that bFriend used to point to now has no reference to it

  • This would cause Java to delete the Jane Friend object

    ../../_images/reference_lost.png

    The Friend object that was referenced by bFriend now has no reference to it. The bFriend reference variable refers to the same Friend the aFriend reference variable refers to. The aFriend and bFriend reference variables are aliases for the same Friend object.

Warning

One may feel that the assignment works different between primitive types when compared to objects, but this is wrong.

Remember what is stored in the variables — the contents of the variables are copied. The variables may store a primitive type, or maybe a reference to an object. Either way, it’s the variable’s contents that are copied.

3.5. For Next Time

  • Read Chapter 1 of the text

    • 15 pages

3.5.1. Playing Code

  • Download and play with