IntObj Examples: Part 2

More Methods for a Simple Object Type

We can easily add more methods to the IntObj class we gave in the "IntObj" examples: Part 1 notes. For example, we could add a method which subtracts a number from the value held in an IntObj:
public void sub(int m)
{
 val=val-m;
}
This could be added anywhere (though obviously not inside one of the other methods) in the file for the class IntObj. One of the advantages of the object-oriented approach is that you can make additions like this to the code for a class without having to change any other code, or even recompile it. Only the file for the class that has been changed has to be recompiled.

Suppose you wanted to add two IntObjs. If we had, say

IntObj a = new Intobj(3), b = new IntObj(4);
you might think that
a.add(b);
would cause the value inside b to be changed from 3 to 7. However, it would not work because the definition of add in IntObj is:
public void add(int m)
{
 val=val+m;
}
The argument it takes is an int, not an IntObj. While to us it is obvious what ought to be done, computers can only follow their own rules strictly, and there is no rule which enables a computer running Java to convert an IntObj into an int - we have to instruct it to do that ourselves. That is easy:
a.add(b.value());
will do what we want, as we have already seen. However, this looks a bit complex, and our program which uses IntObjs might look a bit messy if there was a lot of this. It would be nice if we actually could use the nicer looking a.add(b). In fact, we can, we just need to instruct the computer how to add one IntObj to another by adding the appropriate method to the class IntObj. Here it is:
public void add(IntObj a)
{
 val=val+a.val;
}
This can be added to the file for class IntObj. It can be added without taking away the existing add method. The reason for this is that Java allows two methods to have the same name so long as the types of their arguments are different. This is known as overloading. If we have a variable a of type IntObj, and a call a.add(p), the call will use the method for adding ints to an IntObj if p is of type int, and the method for adding IntObjs to an IntObj if p is of type IntObj. Here is a simple program that demonstrates the use of both:
 1 class Ints3
 2  {
 3
 4  public static void main(String[] args)
 5  {
 6   IntObj a = new IntObj(3), b = new IntObj(7);
 7   System.out.println("Starting with: ");
 8   System.out.println(" a's value: "+a.value());
 9   System.out.println(" b's value: "+b.value());
10   System.out.println();
11   System.out.println("Executing a.add(b)");
12   a.add(b);
13   System.out.println(" a's value: "+a.value());
14   System.out.println(" b's value: "+b.value());
15   System.out.println();
16   System.out.println("Executing b.add(5)");
17   b.add(5);
18   System.out.println(" a's value: "+a.value());
19   System.out.println(" b's value: "+b.value());
20   System.out.println();
21  }
22 }
On line 12 is the use of add with an IntObj as the argument, on line 17 the use of add with an int as an argument.

Note that the code for the version of add that has an IntObj as its argument has the line val=val+a.val. Here a.val refers to the val field of the IntObj object which is the argument. So when we make the call a.add(b), inside the add method, a.val refers to the val field of b and val on its own refers to the val field of a. This can be done because even though val is a private field, which we said means only the object it is in can access it, in fact another IntObj object can access it if it has the object passed in as a method argument. If you want to make your code a little clearer as to which val belongs to which object, an alternative way of writing the add method is:

public void add(IntObj a)
{
 this.val=this.val+a.val;
}
The keyword this in Java means "the object to which this method is attached". So in any call obj.meth(args), in the code for method meth, this refers to obj. You can think of val on its own in an IntObj method as a shorthand for this.val, and similar for any field name used without being attached to an object name in any method.

Constructive v. destructive methods

When we call a.add(b), we change the value stored in a. The old value is destroyed - even if another name had been given to a through assignment, say c=a, aliasing means the old value in c is destroyed since it is actually the same thing (not just a copy) of the old value in a. A method which changes any values in fields of the object it's applied to is known as destructive because of this. Note that it is possible, but not advisable, to have a method which changes fields of its arguments, for example:
public void badd(IntObj a)
{
 a.val=this.val+a.val;
}
has the effect that a call c.badd(d) changes the val field of IntObj object d rather than IntObj object c.

A method which does its work not by changing any fields but by creating a new object with the new field of the required value is known as a constructive method. A constructive method for adding one IntObj to another is easily defined:

 public IntObj cadd(IntObj a)
 {
  return new IntObj(this.val+a.val);
 }
Now, if e, d and f are all IntObj variables
d = e.cadd(f);
causes d to refer to a new IntObj object whose val field is the sum of the val fields of e and f. It does not change the val field of e or f. Obviously, however, if d referred to any other IntObj object before, it no longer refers to it, though it is not destroyed if any other IntObj variable also refers to it.

It is necessary to give this constructive version of add a different name because although its return type is different - IntObj rather than void, meaning it returns an IntObj value - its arguments are exactly the same as the previous add method. It is not possible to have two methods with the same name and same arguments, differing only in return type.

Here is some code which demonstrates this constructive add:

 1 class Ints4
 2 {
 3
 4  public static void main(String[] args)
 5  {
 6   IntObj a = new IntObj(3), b = new IntObj(7), c;
 7   System.out.println("Starting with: ");
 8   System.out.println(" a's value: "+a.value());
 9   System.out.println(" b's value: "+b.value());
10   System.out.println();
11   System.out.println("Executing c = a.cadd(b)");
12   c = a.cadd(b);
13   System.out.println(" a's value: "+a.value());
14   System.out.println(" b's value: "+b.value());
15   System.out.println(" c's value: "+c.value());
16   System.out.println();
17  }
18 }

Static Methods

The line 12 above looks a little odd. What is happening is that a and b are added together to make c. There is no difference between the roles a and b take, but a.cadd(b) might suggest there is. An alternative form of constructive add makes both arguments to the addition arguments to the method. Here is the alternative form:
public static IntObj add(IntObj a, IntObj b)
{
 return new IntObj(a.val+b.val);
}
To use this alternative form, add it to class IntObj and then just replace line 12 in class Ints4 above (and also change line 11 appropriately) by:
c = IntObj.add(a,b);
Note that this method call is not attached to any particular object. Instead it is attached to the class name IntObj. The reason for this is that this version of add isn't associated with any object in particular when it is called. Both the objects it refers to in its code are passed to it as arguments. A method in a class which refers only to fields of that class attached to objects of that class passed as arguments should be declared as static, as this new add method is (the word static appears before its return type). When a method is static, it has no this. When it is called in another class, the call should be attached to the name of the class the method comes from rather than to an object of that class. Static methods are very much like procedures or functions in non-object-oriented languages.

Note also this method can also be called add because it has different arguments to any other method called add in class IntObj - no other method of that name has two IntObj arguments. For complete flexibility, it would be possible to have three further static add methods

public static IntObj add(IntObj a, int b)
{
 return new IntObj(a.val+b);                                  
}

public static IntObj add(int a, IntObj b)
{
 return new IntObj(a+b.val);                                  
}

public static IntObj add(int a, int b)
{
 return new IntObj(a+b);                                  
}
This means that all possible combinations of int and IntObj arguments are covered by an appropriate method.

The toString() method

To print the value represented by an IntObj object, we have used the value() method to obtain the integer value it stores, and then printed that. Suppose we attempted to print the object directly, for example we had an IntObj object referenced by variable a and the statement:
System.out.println(" a's value: "+a);
in our main method, for example as line 13 of Ints4.java. This will compile without error, but with the code for class IntObj as described to this point, when it is run it will result in something like the following being printed:
 a's value: IntObj@1197c6c
When an object value is joined to a string using +, a string value representing the object is created and joined to the string to create the new string, just as a string value representing an integer is created when an integer is joined to a string using +. However, unless there is a method giving the string representation of an object, it will just be converted to a string consisting of the object's type plus a jumble of numbers and letters as above.

In Java there is a special name used for a method which gives the string representation of an object. If you add a method with the signature:

public String toString()
to a class, the code in the method will be used to give the string representation whenever an attempt is made to print the object or to combine it with a string using +. Here is an appropriate toString method for IntObj:
public String toString()
{
 return ""+this.val;
}
The "" represents the empty string (string of length 0). Joining any integer to any string using + converts it to the string representation then joins that to the string, so joining this.val to the empty string gives the string representation of this.val, which is also an appropriate string representation for the whole object.

If all these extra methods have been added to our original code for IntObj, we will now have:

class IntObj
{
 private int val;

 public IntObj(int n)
 {
  val=n;
 }

 public int value()
 {
  return val;
 }

 public void add(int m)
 {
  val=val+m;
 }

 public void add(IntObj a)
 {
  val=val+a.val;
 }

 public static IntObj add(IntObj a, IntObj b)
 {
  int c = a.val+b.val;
  return new IntObj(c);
 }

 public static IntObj add(int a, IntObj b)
 {
  int c = a+b.val;
  return new IntObj(c);
 }

 public static IntObj add(IntObj a, int b)
 {
  int c = a.val+b;
  return new IntObj(c);
 }

 public String toString()
 {
  return ""+this.val;
 }

}
perhaps with additional methods for the other arithmetic operations.
Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 7 Oct 1999