Module 5: A First Look at Objects
Supplemental material
Static objects: encapsulation
What is a static object?
- This is a little-used term for Java classes with only
static components.
- In the "old" days, we would call this encapsulation.
Let's look at an example:
// A simple static object. Note: all members (data and methods) are declared
// with the "static" keyword.
class ObjX {
static int i;
static void print ()
{
System.out.println ("i=" + i);
}
} //end-ObjX
// The class that has "main"
public class StaticExample {
public static void main (String[] argv)
{
// Refer to a member of ObjX using the class name.
ObjX.i = 5;
// Call a method in the class using the class name and dot-operator.
ObjX.print();
}
}
Note:
- There are two class'es in the same file above:
ObjX and StaticExample.
- StaticExample is the one that contains
main() and therefore, the file has to have the
name StaticExample.java.
- Rule: the class that has main must be declared public.
- The one we created, ObjX has two members:
a piece of data i (an integer) and a method print().
- Both members are declared using the keyword static.
- To access members from another class, we use the class name
(ObjX) and the . (dot) operator, as in:
// Refer to a member of ObjX using the class name.
ObjX.i = 5;
// Call a method in the class using the class name and dot-operator.
ObjX.print();
- The main purpose of static objects is encapsulation.
What is encapsulation?
- In early programming languages, data and methods were strewn
all over the place, allowing "global" data (outside methods) to be
accessed by any method.
⇒
This invites name clashes, illegal accesses, bad coding practices
- Later languages promoted the idea of encapsulation
⇒
Keep related data and methods together in a "package"
- In Java, that "package" is a static object
⇒
a class with only static members.
In-Class Exercise 1:
Download StaticExample.java
and add a second data member (say, another int) to the
class ObjX. Then, in main, assign a value
to the new variable and print it.
Let's now examine the "memory picture" for a static object:
- Static members are assigned space in the global area.
- Important: This allocation of space is done before the program starts running.
- In general, when studying the "memory picture", we'll need
to understand both where and when space is allocated.
More about encapsulation:
- It is unusual to allow static variables to be accessed from
another class.
⇒ we just used this as an illustration.
- More commonly, static objects are used to put a bunch of
related methods in one place.
Here's an example of placing a bunch of related methods together:
public class MathStuff {
// Compute a^b
public static int power (int a, int b)
{
if (b == 0) {
return 1;
}
return (a * power (a, b-1));
}
// Compute n!
static int factorial (int n)
{
if (n == 1) {
return 1;
}
return ( n * factorial(n-1) );
}
// Compute f_n, the n-th Fibonacci number.
static int fibonacci (int n)
{
// Base cases rolled into one:
if (n <= 2) {
return (n-1);
}
return ( fibonacci(n-1) + fibonacci(n-2) );
}
}
Note:
- The class MathStuff contains only static methods.
- There is no main() method. So, how are these
methods used?
⇒
From another class, e.g.
public class MathStuffUsageExample {
public static void main (String[] argv)
{
int p = MathStuff.power (3, 4);
System.out.println ("3^4 = " + p);
int m = MathStuff.factorial (5);
System.out.println ("5! = " + m);
int f = MathStuff.fibonacci (6);
System.out.println ("6-th fibonacci number is: " + f);
}
}
- In this case, the class'es MathStuff
and MathStuffUsageExample are in separate files.
- As you might expect, the Java library has its own collection
of useful math functions, all put together in a class
called Math.
- Example:
public class MathExample {
public static void main (String[] argv)
{
// The constant "e"
double e = Math.E;
// Compute e^2.
double y = Math.exp (2.0);
// Compute log_e(y)
double z = Math.log (y);
// ...
}
}
About class'es and files:
- Each class is usually written in a separate file.
- With only one class per file, the class is
declared public.
- However, for some applications, we'll need a "secondary"
class that shouldn't be used outside the file
⇒
These "secondary" classes will be in the same file (and not public).
- To simplify presentation and downloading, we will strive to
put all relevant classes in a single file, even if this breaks with
Java convention.
Dynamic objects
Let's look at an example with a dynamic object:
// Dynamic object definition
class ObjX {
// No "static" keyword for either member.
int i;
void print ()
{
System.out.println ("i=" + i);
}
}
public class DynamicExample {
public static void main (String[] argv)
{
// First create an instance, which allocates space from the heap.
ObjX x = new ObjX ();
// Now access members via the variable and the dot-operator.
x.i = 5;
x.print();
}
}
Note:
- The definition of ObjX now does not have the
static keyword in front of any member.
⇒
All members are dynamic.
- There is some data (the int i) and one method (print()).
- Before accessing the members, an
instance needs to be created using the new
operator:
ObjX x = new ObjX ();
In-Class Exercise 2:
Download DynamicExample.java
and add a second data member (say, another int) to the
class ObjX. Then, in main, assign a value
to the new variable and print it.
Next, we'll make a small modification:
class ObjX {
// No "static" keyword for either member.
int i;
void print ()
{
System.out.println ("i=" + i);
}
}
public class DynamicExample2 {
// A simple variable declaration.
static ObjX x;
public static void main (String[] argv)
{
// First create an instance, which allocates space from the heap.
x = new ObjX ();
// Now access members via the variable and the dot-operator.
x.i = 5;
x.print();
}
}
Note:
- The only change we've made is to declare the variable x as
a "global" static variable in the class
DynamicExample2:
// A simple variable declaration.
static ObjX x;
- In main, we simply create an instance since the
variable has been already declared:
x = new ObjX ();
We'll now examine what memory looks like
- The memory picture for the above program before execution
starts in main():
- After the new operator is executed in the first
line of main():
- After the execution of the second line (which assigns a
value to i):
In-Class Exercise 3:
Go back to the first example,
DynamicExample.java,
and draw the memory pictures for this example at various
points in the execution of main().
A few words about nomenclature:
- Java books refer to dynamic objects as instances.
- Thus, variables in a dynamic object are called instance variables.
- Variables in a static object are called class variables.
- Similarly, there are instance methods and class methods.
- In other languages, the use of the term object
always refers to dynamic objects.
- For Java programmers, when you get used to the differences
between the types of objects, you will also get used to their nomenclature.
Dynamic objects: more examples
Consider this example:
class ObjX {
int i;
void print ()
{
System.out.println ("i=" + i);
}
} //end-ObjX
public class DynamicExample3 {
public static void main (String[] argv)
{
// Create an instance and do stuff with it.
ObjX x = new ObjX ();
x.i = 5;
x.print();
// Create another instance assigned to the same variable.
x = new ObjX ();
x.i = 6;
x.print();
}
}
Note:
- After the first three lines of main(), memory looks like this:
- The memory picture after the execution of the lines
// Create another instance assigned to the same variable.
x = new ObjX ();
x.i = 6;
- What happens to the "floating" instance containing "5"?
⇒
It gets garbage-collected.
- Finally, after main() completes, all the heap
and stack memory is freed -up.
- About garbage collection:
- This is a process that runs invisibly in the background.
- The garbage collector looks for "unattached" chunks of
heap memory (nothing points to these chunks) and scoops them up.
⇒
Puts the memory back into the "available pool" of heap memory.
- The garbage collector always runs in the background, every
few milliseconds.
In-Class Exercise 4:
Consider the example below and draw the memory picture
just after the execution of x2.i=6:
class ObjX {
// ... same as before
}
public class DynamicExample4 {
public static void main (String[] argv)
{
// Create an instance and do stuff with it.
ObjX x = new ObjX ();
x.i = 5;
x.print();
// Create another instance assigned to the same variable.
ObjX x2 = new ObjX ();
x2.i = 6;
x2.print();
}
}
An example with an array of objects:
class ObjX {
int i;
void print ()
{
System.out.println ("i=" + i);
}
} //end-ObjX
public class DynamicExample5 {
public static void main (String[] argv)
{
// Make space for 4 ObjX pointers.
ObjX[] xArray = new ObjX [4];
// Make each of the 4 pointers point to ObjX instances.
for (int k=0; k < 4; k++) {
xArray[k] = new ObjX ();
}
// Now assign data to some of them.
xArray[0].i = 5;
xArray[1].i = 6;
// Print all.
for (int k=0; k < 4; k++) {
xArray[k].print();
}
}
}
Note:
- Observe how an array variable (for ObjX instances) is
declared:
ObjX[] xArray = new ObjX [4];
- We could have, alternatively, split the declaration and
space allocation:
ObjX[] xArray;
xArray = new ObjX [4];
- This creation of space only makes space for 4 ObjX
pointers:
- We have to create four instances of ObjX ourselves
with the code:
// Make each of the 4 pointers point to ObjX instances.
for (int k=0; k < 4; k++) {
xArray[k] = new ObjX ();
}
The memory picture after this is:
- The memory picture after the two assignments:
// Now assign data to some of them.
xArray[0].i = 5;
xArray[1].i = 6;
In-Class Exercise 5:
Consider the example below and draw the memory picture
just after the execution of x2.i=6:
class ObjX {
// ... same ...
}
public class DynamicExample6 {
public static void main (String[] argv)
{
ObjX x = new ObjX ();
x.i = 5;
// Assignment between object variables.
ObjX x2 = x;
x2.i = 6;
x.print();
x2.print();
}
}
What gets printed out?
Object instances can be passed as parameters to methods
and can be returned from methods:
class ObjX {
// ... same ...
} //end-ObjX
public class DynamicExample7 {
public static void main (String[] argv)
{
// Call a method that returns an instance of ObjX.
ObjX x = makeAnObject();
// Pass the instance as parameter to a method.
printTheObject (x);
}
// Note: return type is ObjX
static ObjX makeAnObject ()
{
ObjX obj = new ObjX ();
obj.i = 5;
return obj;
}
// Note: parameter type is ObjX
static void printTheObject (ObjX obj)
{
obj.print();
}
}
Note:
- Consider the state of memory just before the return
statement is executed in makeAnObject():
- After makeAnObject() returns to main():
In-Class Exercise 6:
What is the memory picture just after executing the only
line in printTheObject(), but before the method
returns?
In-Class Exercise 7:
Download DynamicExample8.java
and draw the memory picture just after the last statement in main().
Objects that contain objects
Consider this example:
class ObjX {
int i;
ObjY y;
}
class ObjY {
String name;
}
public class DynamicExample9 {
public static void main (String[] argv)
{
// Make an ObjX instance.
ObjX x = new ObjX ();
x.i = 5;
// The y instance variable in x is assigned an instance of ObjY
x.y = new ObjY ();
// Note the repeated use of the dot-operator.
x.y.name = "Mr. Y";
}
}
Note:
- The memory picture after the first two lines:
- After the third line executes, the memory picture is:
In-Class Exercise 8:
Draw the memory picture after the last line executes in main().
Now for an interesting variation:
class ObjX {
int i;
// A variable of the same type:
ObjX anotherX;
}
public class DynamicExample10 {
public static void main (String[] argv)
{
// Make an ObjX instance.
ObjX x = new ObjX ();
x.i = 5;
// Make the anotherX variable point to a new ObjX instance.
x.anotherX = new ObjX ();
x.anotherX.i = 6;
}
}
In-Class Exercise 9:
Draw the memory picture after the last line executes in main().
The special method toString()
The method toString(), if written for any object as shown below, has
a special use in Java:
class ObjX {
int i;
public String toString ()
{
String s = "i=" + i;
return s;
}
}
public class DynamicExample11 {
public static void main (String[] argv)
{
// Make an ObjX instance and assign something to its variable i.
ObjX x = new ObjX ();
x.i = 5;
// Note how "x" is directly given to println()
System.out.println (x);
// Another use:
String outputStr = "Object x: " + x;
System.out.println (outputStr);
}
}
Note:
- The method toString() must be defined exactly as shown
above, with the signature
public String toString ()
{
}
- As the return type indicates, it must return a String.
- Whenever the Java compiler sees an object variable in the
context of a String, as in
String outputStr = "Object x: " + x;
- The compiler places a call to the object's toString() method.
- The call returns a String, as we would expect.
- This String is then used in place of the object.
- Thus, in the above example, the string "i=5" gets
concatenated with the string "Object x:" to give "Object x: i=5".
In-Class Exercise 10:
Download the above
program and remove the toString() method from
ObjX. Then compile and run. What do you see?
Constructors
Recall how we create object instances with the new operator:
// Make an ObjX instance.
ObjX x = new ObjX ();
You might wonder why there are parentheses at the end.
Before we answer that question, we will enhance our ObjX
example and add a few so-called constructors:
class ObjX {
int i;
String name;
// This is the first constructor: only takes in one parameter for the name.
public ObjX (String initialName)
{
name = initialName;
i = 0;
}
// This is the second constructor: both name and ID are initialized.
public ObjX (String initialName, int ID)
{
name = initialName;
i = ID;
}
public String toString ()
{
return "My name is " + name + " and my ID number is " + i;
}
}
public class DynamicExample12 {
public static void main (String[] argv)
{
// The old way: without using constructors. Will not compile
ObjX x = new ObjX ();
x.i = 5;
x.name = "Mr. X";
System.out.println (x);
// This compiles fine:
// Use the first constructor.
x = new ObjX ("Mr. X");
System.out.println (x);
// Use the second one.
x = new ObjX ("Mr. X", 5);
System.out.println (x);
}
}
First, let's fix the compilation problem by removing the "old
way":
class ObjX {
int i;
String name;
// This is the first constructor: only takes in one parameter for the name.
public ObjX (String initialName)
{
name = initialName;
i = 0;
}
// This is the second constructor: both name and ID are initialized.
public ObjX (String initialName, int ID)
{
name = initialName;
i = ID;
}
public String toString ()
{
return "My name is " + name + " and my ID number is " + i;
}
}
public class DynamicExample13 {
public static void main (String[] argv)
{
// Use the first constructor.
ObjX x = new ObjX ("Mr. X");
System.out.println (x);
// Use the second one.
x = new ObjX ("Mr. X", 5);
System.out.println (x);
}
}
Note:
- A constructor executes just once when an object instance is created.
- Thus, when the first instance is created above:
ObjX x = new ObjX ("Mr. X");
the first (one-parameter) constructor executes.
- Similarly, when the second instance is created above
x = new ObjX ("Mr. X", 5);
the second constructor (with two parameters) executes.
- Constructors are similar to methods in that they take
parameters and execute like code.
- But constructors are unlike methods in that:
- They are declared differently: they must have the same name
as the class and they cannot declare a return type.
- They cannot be called as a regular method. This fails to
compile:
ObjX x = new ObjX ("Mr. X");
x.ObjX ("Mr. Y"); // Will not compile.
- They execute only once for each instance, and execute at
the time of creation. Thus, the constructor finishes execution
by the time we get to the second line below:
// Use the first constructor.
ObjX x = new ObjX ("Mr. X");
System.out.println (x);
- Other facts about constructors:
- Any number of them can be defined, as long as they have
different signatures.
So, now let's get back to some nagging questions:
- Why were there parentheses earlier when we didn't define any constructor?
- Why, after we defined constructors, didn't the "old way" work?
- And, finally, why bother with constructors at all?
Answers:
- When you don't define your own constructors, Java puts one in
for you (but you don't see the code, of course).
⇒
This is the "default no-parameter" constructor.
⇒
This is why the parentheses are part of the syntax.
- When a programmer defines constructors, the Java compiler
takes away the default no-parameter constructor
⇒
It now becomes a compiler error to try and use one.
- You can of course, define your own no-parameter constructor:
class ObjX {
int i;
String name;
// This is the first constructor: only takes in one parameter for the name.
public ObjX (String initialName)
{
name = initialName;
i = 0;
}
// This is the second constructor: both name and ID are initialized.
public ObjX (String initialName, int ID)
{
name = initialName;
i = ID;
}
// No-parameter constructor that we added
public ObjX ()
{
name = "X"; // Default name
i = 0; // Default value of i
}
public String toString ()
{
return "My name is " + name + " and my ID number is " + i;
}
}
public class DynamicExample14 {
public static void main (String[] argv)
{
// Use the no-parameter constructor.
ObjX x = new ObjX ();
x.i = 5;
System.out.println (x);
}
}
- Why use constructors at all, especially if you can
initialize values directly?
ObjX x = new ObjX ();
x.i = 5;
- First, it's bad practice to place initialization code like
this outside the class.
- The right place for initialization code is the class itself,
in the constructor.
- Most often a class is written by one programmer, and used by another.
⇒
The creator of the class shouldn't allow others to modify data
⇒
Data should be declared private
- Thus, the right way to declare data in ObjX is:
class ObjX {
private int i;
private String name;
// ... constructors, methods ... etc
}
- Then, something like this in main()
ObjX x = new ObjX ();
x.i = 5; // Compiler error!
won't compile.
More about objects
There's a lot more to objects than we have described here
⇒
Our purpose was to introduce objects in enough detail for the
data structures we will examine soon.
What we haven't covered:
- Gory details about constructors.
- Objects that have both static and dynamic members.
- Inheritance.
- Method overriding and callbacks.
- Abstract classes.
- Interfaces.
- Casting in objects.
- Java's Object class and its implications.
- Esoteric Java topics like: shadowed variables, the
this operator, the use of instanceof,
static initializers, and inner classes.
To read more:
- See Modules 4, 5, 6, 7 and 10 of CS-143
- See the two short overviews of objects in the
CS-143
Appendix entitled "Why use objects?" and "More examples on objects".
© 2006, Rahul Simha (revised 2017)