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 classWith the fields
firstName
andlastName
And methods
eat()
andgoToBed()
One could make Bob Smith an instance of the
Human
classfirstName
is BoblastName
is SmithCalling 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
havefirstName
andlastName
But Bob Smith’s
firstName
is BobIf 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
classAnother class can be made to keep track of the collection of friends —
ContactList
classThis will be covered in the following topic
3.2. Friend Class
For this particular problem, the
Friend
class can be kept simpleThis is a good thing
The only information the
Friend
objects need to know is theirFirst Name
Last Name
Email Address
A constructor will be be needed that describes the setup for the
Friend
objectAssigning the values to the fields
The behaviours of the
Friend
classA 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 followingRemember, 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 classThe class is also set to
final
since, once an instance is created, it should not change — immutableThe 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
andfinal
private
means the fields are not directly accessible outside the classIf they were assigned
public
, it would behave like Python
Although
private
, the values of the fields will ultimately be accessible, but through accessor methodsDiscussed in more detail below
The
Friend
class will be made in such a way that the data is immutable — it doesn’t change once setThus, 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 aself
parameterAlthough Java does have a similar keyword —
this
The
this
in the above example let’s Java resolve the ambiguity between the field and constructor parameterthis.firstName
is the field wherefirstName
is a local parameter for the constructorIt is not always necessary to use
this
in Java like howself
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 classNote, 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 usedIf 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 theFriend
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
This is, more or less, a memory address of where the object is in memory
For example —
Friend@77459877
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 objectSystem.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 equivalentHowever, unlike Python, it does not overload the
==
operator==
for objects is reserved for checking if two things are literally the same object – aliasesSame memory address — it compares the memory addresses
equals
is used to compare the content of the objects in some wayThis 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 too
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 classFriend
It downcast the
Object
to classFriend
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 courseBriefly, it is a function used to convert the object into an
int
hash valueAny 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 theFriend
classThis
hashCode
effectively returns the sum of the hash values of the threeString
attributesFor 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 theFriend
classIt 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
objectnew 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.
If the following two lines of code were run, two instances of a
Friend
would existFriend aFriend = new Friend("Bob", "Smith", "bsmith@gmail.com");
Friend bFriend = new Friend("Jane", "Doe", "jdoe@gmail.com");
aFriend
would have afirstName
of BobbFriend
has afirstName
of JaneThey both have the
firstName
field, but the actual value associated with it differsBelow is an example of two
Friend
objects being created and being usedGet
aFriend
's first nameUse the
toString
methodUse 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 ofaFriend
and puts the copy in thebFriend
But the contents of the
aFriend
variable is a reference to aFriend
The reference stored in
aFriend
gets copied; theFriend
is not copiedThis results in an aliases — both
aFriend
andbFriend
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 itThis would cause Java to delete the Jane
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