Objects 3

Passing/Returning Objects

These are sections 8.2, 8.3 in the textbook. We have already discussed this issue much earlier. To recall, an object is passed "by-reference-by-value." When an object is passed as a parameter, the argument and the parameter share the same object content. Unless the parameter is set to a new object, all effects on the parameter will be on the argument.

The following example more-or-less mimics what is done in the textbook. It relies on the class from Chapter 6:
BankAccount.java  

ChangeAccount
public class ChangeAccount {
 
  public static void main(String[] args) {
 
    BankAccount acct = new BankAccount(500);
 
    System.out.println("1. balance = " + acct.getBalance()) ;
 
    doWithdrawal(acct, 100);
 
    System.out.println("2. balance = " + acct.getBalance()) ;
 
    BankAccount other_acct = makeDeposit(acct, 300);
 
    System.out.println("3. balance = " + acct.getBalance()) ;
 
    System.out.println("4. balance = " + other_acct.getBalance()) ;
 
  }
 
  private static void doWithdrawal(BankAccount acct, double amt) {
    acct.withdraw(amt);
  }
 
  private static BankAccount makeDeposit(BankAccount acct, double amt) {
    double current = acct.getBalance();
    BankAccount newAcct = new BankAccount(current);
    newAcct.deposit(amt);
    return newAcct;
  }
 
}
select

The toString() method

This is section 8.4 in the textbook. Let's start with the simple Box class that we've used before.

Box.java
public class Box {
  private double width;
  private double height;
 
  public Box(double width, double height) {
    this.width = width;
    this.height = height;
  }
 
  public double getHeight() {
    return height;
  }
  public double getWidth() {
    return width;
  }
 
  public void scale(double amt) {
    width *= amt;
    height *= amt;
  }  
}
select
Let's recall about printing Java entities. Everything can be printed, it's just that the printed content is not useful for One of the advantages of an ArrayList over an array is that it does give useful information when printed.

Here's a little demo test program:

PrintBox.java
public class PrintBox {
 
  public static void main(String[] args) {
    Box box = new Box(45.3, 34.2);
 
    System.out.println("box = " + box);
  }
}
select
How does any object get printed? It has to do with a very fundamental member function which is applicable to every object:
public String toString()
Explaining exactly what it means to be "applicable to every object" is something taught in the Computer Science III course. At this point we simply want to be able to use it to allow
System.out.println("box = " + box);
to generate useful information. The idea is relatively simple: define the toString method within the Box class, making it return the String information you want printed.

Add this member function into the Box class
  public String toString() {
    return String.format("Box(width=%.2f, height=%.2f)", width, height);
  }
Recall that the
String.format
function generates a String by inserting values into a format string the way that System.out.printf works. With this in place, run the main test program, commenting out the line which prints the array. You should get:
box = Box(width=45.30, height=34.20)
The UML diagram also reflects the change:

Box

- double width
- double height

+ Box(double width, double height)

+ double getWidth()
+ double getHeight()
+ void scale(double amt)
+ String toString()

The @Override annotation

Once you add this toString member function, NetBeans will recognize that there is something special about it and "flag" the declaration line, suggesting:
Add @Override Annotation
Go ahead and select it and observe the outcome:
  @Override
  public String toString() {
    return String.format("Box(length=%.2f, width=%.2f)", length, width);
  }
Explaining what this means is beyond the scope of this course, but it makes NetBeans happy again! Actually it is useful for us in that it serves as a check that you got the member function signature correct. For example, try altering the name to "tostring":
  @Override
  public String tostring() {
    return String.format("Box(length=%.2f, width=%.2f)", length, width);
  }
Now the @Override line is flagged as an error. If you remove the @Override line, the error is gone, but now it does not work. Consequently, you should always use this @Override line as suggested.

Explicit use of toString()

It turns out that Java automatically refers to the toString() method whenever it is asked to print an object. If it is not "overriden", it falls back on some default version of this method.

The toString() method can be called explicitly; for example we could equally write this in our test program
System.out.println( "box = " + box.toString() );
Of course, doing so in this situation is unnecessary. The primary need for explicit usage of toString() is if you need to pass a String to a method. Here is a trivial example of sorts:

PrintBox.java (modified)
public class PrintBox {
 
  public static void main(String[] args) {
    Box box = new Box(45.3, 34.2);
 
    System.out.println("box = " + box);
 
    printString(box.toString());
  }
 
  private static void printString(String str) {
    System.out.printf("|%s|\n", str);
  }
}
select
In this case we cannot pass the box directly like:
printString(box);
A more compelling example is when you want to write the toString function for multiple random boxes. Consider this example which we'll use later:

RandBoxes
import java.util.ArrayList;
import java.util.Random;
 
public class RandBoxes {
  private ArrayList<Box> boxes;
 
  public RandBoxes(int num) {
    Random rand = new Random();
    boxes = new ArrayList<>();
    for (int i=0; i < num; ++i) {
      double width = 10 * rand.nextDouble();
      double height = 10 * rand.nextDouble();
      boxes.add( new Box(width,height) );
    }
  }
 
  @Override
  public String toString() {
    String result = "";
    for (Box box : boxes) {
      result += box.toString() + "\n";
    }
    return result;
  }
}
select
What we're trying to illustrate is the creation of the toString method in RandBoxes. Run this simple driver program:

ShowRandBoxes
public class ShowRandBoxes {
 
  public static void main(String[] args) {
    RandBoxes randboxes = new RandBoxes(3);
 
    System.out.println("boxes = \n" + randboxes);
  }
}
select
The code we are using to create an output string is this:
public String toString() {
  String result = "";
  for (Box box : boxes) {
    result += box.toString() + "\n";
  }
  return result;
}
Here we must use box.toString() explicitly, because within toString we are not printing the box, just using its String representation. If we wanted a more complex output we could use the String.format function, e.g.,
...
    result += String.format("%s\n", box.toString());
...

Textbook Example

The author writes a toString method for his Stock class and then demos its implementation.
Stock.java  
StockDemo1.java  

Object Equality

This is section 8.5 in the textbook. We are taught that, for primitive types, the binary boolean operator == measures equality; but for String objects the ".equals" operator is used. What about the equality of our user-defined objects? This function is available for any object
public boolean equals(Object obj)
By default, equals measures the same thing as "==", meaning that
obj1.equals(obj2)
the two objects are identical. What we want is some alternative measure of equality based on the content of the two objects, and so we have to add out own definition of equals to our class.

String equality

If you have two strings:
String str1 = /* ... */;
String str2 = /* ... */;
Then to compare for equality, you are taught to do this:
str1.equals(str2);
and not this:
str1 == str2
To see the difference between the two comparisons, look at a small demo program:

CompareStrings.java
public class CompareStrings {
 
  public static void main(String[] args) {
 
    String str1 = "hello";
    String str2 = "hello";
    String str3 = "hell" + "o";
    String str4 = "HELLO".toLowerCase();
 
    System.out.println("1.equals(2) = " + str1.equals(str2));
    System.out.println("1.equals(3) = " + str1.equals(str3));
    System.out.println("1.equals(4) = " + str1.equals(str4));
 
    System.out.println("----------------------------");
 
    System.out.println("1==2: " + (str1 == str2));
    System.out.println("1==3: " + (str1 == str3));
    System.out.println("1==4: " + (str1 == str4));
 
    System.out.println("----------------------------");
 
    System.out.println("1.equals(null): " + str1.equals(null));
  }
}
select
When you run it, the results are somewhat unexpected in that "==" sometimes behaves as if ".equals" were used. The unexpected behavior is the result of Java throwing you a curve ball, so to speak. In order to save memory Java creates a "string cache" and tries to match a String object to one of the strings in its cache.

So, in reality, str1, str2, and str3 are identical objects in that they point to the exact same content. Java only makes this happen in certain cases. Java doesn't recognize str4, in the way it is generated, as having the same content as the others.

Equality for user-defined objects

For a user-defined class, Java doesn't do any tricks. If we have separate objects
MyClass obj1 = new MyClass(...);
MyClass obj2 = new MyClass(...);
then this is always false:
obj1 == obj2
However, as mentioned above, unless we intervene,
obj1.equals(obj2)
will fall back on the == comparison, giving a false value in every circumstance. Let's use a simple User class:

User.java
public class User {
 
  private int id;
  private String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
 
  public int getId() {
    return id;
  }
  public String getName() {
    return name;
  }
 
  @Override
  public String toString() {
    return String.format("User(%s,%s)", id, name);
  }
}
select
Consider this short demo program:

CompareUsers.java
public class CompareUsers {
 
  public static void main(String[] args) {
    User user1 = new User(1111, "Alice Carter");
 
    User user2 = new User(2222, "Jim Jones");
 
    // user1 gets married to user2 and changes name, same person
    User user3 = new User(1111, "Alice Jones");
 
    System.out.println("compare: identical users");
    System.out.println("1==1 :        " + (user1 == user1));
    System.out.println("1.equals(1) : " + user1.equals(user1));
 
    System.out.println("------------------------------");
 
    System.out.printf("compare: %s and %s\n", user2, user3);
    System.out.println("2==3 :        " + (user2 == user3));
    System.out.println("2.equals(3) : " + user2.equals(user3));
 
    System.out.println("------------------------------");
 
    System.out.printf("compare: %s and %s\n", user1, user3);
    System.out.println("1==3 :        " + (user1 == user3));
    System.out.println("1.equals(3) : " + user1.equals(user3));
 
    System.out.println("------------------------------");
 
    System.out.println("1.equals(null) : " + user1.equals(null));
  }
}
select
We're suggesting in the comments that user1 and user3 are really the same user, i.e., the user is the id, like with a social security number.

Textbook equals

The way to put this desired equality into effect is to write your own .equals method like this:
  public boolean equals(User user) {
    if (user == null) {
      return false;
    }
    return this.id == user.id;
  }
With this member function in place, we now get the desired:
compare: User(1111,Alice Carter) and User(1111,Alice Jones)
1.equals(3) : true

Equality for Boxes

This is a pretty simple idea. We want lengths and widths to be the same. Here is the member function we want to add:
  public boolean equals(Box box) {
    if (box == null) {
      return false;
    }
    return width == box.width && height == box.height;
  }

Textbook example

As dictated by the textbook, add an equals method into the Stock class (with some improvements):
  public boolean equals(Stock stock) {
    if (stock == null) {
      return false;
    }
    return symbol.equals(stock.symbol)
        && sharePrice == stock.sharePrice;
  }
The interesting new feature is that, because symbol is a String, we need to use the equals to compare it.
StockCompare.java  

Equals Done Correctly

Take the User class which we have already written with the overloaded equals added:

User.java
public class User {
 
  private int id;
  private String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
 
  public int getId() {
    return id;
  }
  public String getName() {
    return name;
  }
 
  @Override
  public String toString() {
    return String.format("User(%s,%s)", id, name);
  }
 
  public boolean equals(User user) {
    if (user == null) {
      return false;
    }
    return this.id == user.id;
  }
}
select
Then consider this main program which uses an ArrayList of User objects:

SearchForUser.java
import java.util.ArrayList;
 
public class SearchForUser {
 
  public static void main(String[] args) {
    User joe1 = new User(111, "Joe");
    User dave = new User(222, "Dave");
    User joe2 = new User(111, "joe");
 
    ArrayList<User> users = new ArrayList<>();
    users.add(joe1);
    users.add(dave);
 
    System.out.println("joe1 = " + joe1);
    System.out.println("dave = " + dave);
    System.out.println("joe2 = " + joe2);
 
    System.out.println("users = " + users);
    System.out.println("joe1.equals(joe2) = " + joe1.equals(joe2));
 
    System.out.println("users.contains(joe2) = " + users.contains(joe2));
  }
}
select
The output of the run is:
joe1 = User(111,Joe)
dave = User(222,Dave)
joe2 = User(111,joe)
users = [User(111,Joe), User(222,Dave)]
joe1.equals(joe2) = true
users.contains(joe2) = false
Even though the users joe1 and joe2 are considered equal, the ArrayList contains operation is regarding them as not equal, saying that joe2 is not in the list, even though joe1, an equal, was entered into the list.

This problem is not with ArrayList contains, but with the textbook version of equals used here. We need an improved version:

User.java
public class User {
 
  ...
 
  @Override
  public boolean equals(Object obj) {
    if (obj == null) {
      return false;
    }
    if (! (obj instanceof User)) {
      return false;
    }
    User user = (User) obj;
    return this.id == user.id;
  }
 
//  public boolean equals(User user) {
//    if (user == null) {
//      return false;
//    }
//    return this.id == user.id;
//  }
}
With this new version, we now see the expected:
users.contains(joe2) = true
Without going any deeper, the textbook version of equals is referred to as an overloaded operation, not a replacement (override) and, although the overloaded version will work in most circumstances, it will not work in all circumstances. In particular, when searching for objects in an arraylist.

The instanceof operator

To achieve the desired outcome, we've introduced the instanceof operator which tests whether an object "belongs to" a given class. The null value never belongs to any class, so we can actually drop the initial null test, leaving:
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof User)) {
      return false;
    }
    User user = (User) obj;
    return this.id == user.id;
  }
As a final step, run CompareUsers to verify that the new equals method gives correct results.

Corrections for other classes

The Box example:
class Box {
  ...
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof Box)) {
      return false;
    }
    Box box = (Box) obj;
    return width == box.width && height == box.height;
  }
}
The textbook example:
class Stock
  ...
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof Stock)) {
      return false;
    }
    Stock stock = (Stock) obj;
    return symbol.equals(stock.symbol) && sharePrice == stock.sharePrice;
  }
}

ArrayList Search Operations

We indicated in the previous section that the contains operation is used for searching an ArrayList. There are 2 more general operations for searching:
indexOf(obj) = position where object found (forward from start)
lastIndexOf(obj) = position where object found (backward from end)
If the object is not found, the value -1 is returned from both. In particular, contains can be easily derived from either. Here is an example of the usage:

ArrayListIntSearch
import java.util.ArrayList;
import java.util.Scanner;
 
public class ArrayListIntSearch {
 
  public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
 
    list.add(50);
    list.add(20);
    list.add(30);
    list.add(10);
    list.add(20);
    list.add(15);
 
    System.out.println(list);
 
    Scanner keyboard = new Scanner(System.in);
 
    while(true) {
      System.out.print("=> enter an int (return to stop): ");
      String line = keyboard.nextLine().trim();
      if (line.isEmpty()) {
        break;
      }
      int key = Integer.parseInt(line);
 
      int pos;
 
      pos = list.indexOf(key);
      System.out.printf("%s forward at position %s\n", key, pos);
 
      pos = list.lastIndexOf(key);
      System.out.printf("%s backward at position %s\n", key, pos);
    }
  }
 
}
select
In order to reinforce our knowledge of equality of objects, let's again refer to the User class we've just created:
UserShow Hide

User
public class User {
 
  private int id;
  private String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
 
  public int getId() {
    return id;
  }
  public String getName() {
    return name;
  }
 
  @Override
  public String toString() {
    return String.format("User(%s,%s)", id, name);
  }
 
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof User)) {
      return false;
    }
    User user = (User) obj;
    return this.id == user.id;
  }
 
}
select
Use this demo program to illustrate indexOf for a list of User objects:

ArrayListUserSearch
import java.util.ArrayList;
 
public class ArrayListUserSearch {
 
  public static void main(String[] args) {
    ArrayList<User> list = new ArrayList<>();
 
    list.add(new User(111,"joe"));
    list.add(new User(222,"maggie"));
    list.add(new User(333,"dave"));
 
    System.out.println(list);
    System.out.println("------------------");
 
    User u;
 
    u = new User(222, "maggie");
 
    System.out.printf("user %s at position %s\n", u, list.indexOf(u));
 
    u = new User(123, "mary");
 
    System.out.printf("user %s at position %s\n", u, list.indexOf(u));
 
  }  
}
select

Copy Constructor

This is section 8.6 of the textbook. We know that assignment of one object to another simply copies the reference to the same object. This is done implicitly between argument and parameter when the object is passed into a function. Of course, it can be done explicitly by:
MyClass obj1 = new MyClass(...);
MyClass obj2 = obj1;
This is never a useful thing to do; it just gives a useless second name to the exact same thing. What we probably had intended doing is making a copy of the object in the sense of creating a new object which copies the data.

Although generating a copy could be done through a member function, it's usually more obvious as a constructor, since this automatically is called with "new" already in use. What we want to do is:

MyClass obj1 = new MyClass(...);
MyClass obj2 = new MyClass(obj1);
Let's refer back to our recent version of the Box class with toString and equals methods:

Box (with equals added)
public class Box {
  private double width;
  private double height;
 
  public Box(double width, double height) {
    this.width = width;
    this.height = height;
  }
 
  public double getHeight() {
    return height;
  }
  public double getWidth() {
    return width;
  }
 
  public void scale(double amt) {
    width *= amt;
    height *= amt;
  }
 
  @Override
  public String toString() {
    return String.format("Box(width=%.2f, height=%.2f)", width, height);
  }
 
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof Box)) {
      return false;
    }
    Box box = (Box) obj;
    return width == box.width && height == box.height;
  }
}
select
The idea of a copy constructor is simply to duplicate the data members, and so one for Box is:
  public Box(Box box) {
    width = box.width;
    height = box.height;
  }
We could use:
this.width = box.width;
this.height = box.height;
but there is no need, since there is no issue of shadowing.

With this added, here is a simple test program:

TestCopyBox.java
public class TestCopyBox {
 
  public static void main(String[] args) {
    Box box1 = new Box(11.3, 22.2);
 
    Box box2 = new Box(box1);
 
    System.out.println("box1 = " + box1);
    System.out.println("box2 = " + box2);
 
    System.out.println("equal: " + box1.equals(box2));
  }
 
}
select

Textbook Example

Continuing with the Stock example, add this into the Stock class:
  public Stock(Stock stock) {
    symbol = stock.symbol;
    sharePrice = stock.sharePrice;
  }
The driver program is:
ObjectCopy.java  
It has been modified from the original, removing the usage of the redundant "copy" method in the Stock class.

Aggregation

This is section 8.7 of the textbook. Aggregation means using user-defined classes within other user-defined classes.

Boxes Example

Let's again use the Box class with everything thrown in:
Box (complete): Show Hide
public class Box {
  private double width;
  private double height;
 
  public Box(double width, double height) {
    this.width = width;
    this.height = height;
  }
 
  public Box(Box box) {
    width = box.width;
    height = box.height;
  }
 
  public double getHeight() {
    return height;
  }
  public double getWidth() {
    return width;
  }
 
  public void scale(double amt) {
    width *= amt;
    height *= amt;
  }
 
  @Override
  public String toString() {
    return String.format("Box(width=%.2f, height=%.2f)", width, height);
  }
 
  @Override
  public boolean equals(Object obj) {
    if (! (obj instanceof Box)) {
      return false;
    }
    Box box = (Box) obj;
    return width == box.width && height == box.height;
  }
}
select
We will start with the same RandBoxes class used before:
RandBoxes: Show Hide

RandBoxes
import java.util.ArrayList;
import java.util.Random;
 
public class RandBoxes {
  private ArrayList<Box> boxes;
 
  public RandBoxes(int num) {
    Random rand = new Random();
    boxes = new ArrayList<>();
    for (int i=0; i < num; ++i) {
      double width = 10 * rand.nextDouble();
      double height = 10 * rand.nextDouble();
      boxes.add( new Box(width,height) );
    }
  }
 
  @Override
  public String toString() {
    String result = "";
    for (Box box : boxes) {
      result += box.toString() + "\n";
    }
    return result;
  }
}
select
Take this and add this member function:
  public Box getFirstBox() {
    return boxes.get(0);
    //return new Box(boxes.get(0));
  }
Then create and run the following driver program:

RandBoxesDemo
public class RandBoxesDemo {
 
  public static void main(String[] args) {
    RandBoxes randboxes = new RandBoxes(3);
 
    System.out.println("boxes = \n" + randboxes);
 
    Box first = randboxes.getFirstBox();
 
    System.out.println("first = " + first);
    System.out.println();
 
    first.scale(2);
 
    System.out.println("first = " + first);
    System.out.println();
 
    System.out.println("boxes = \n" + randboxes);
  }
}
select
The getFirstBox member function returns the first box itself. In doing so it makes it possible to violate the integrity of the randboxes object. The book refers to this effect as a security issue caused be returning a shallow copy of the object.

The fix of this problem is to use the alternative return statement in the getFirstBox member function. Using this makes the member function return a deep copy of the box.

Textbook Example

The textbook example creates two classes Instructor and TextBook and an aggregate class Course which uses both of these. The key new idea is the Course getters:
public Instructor getInstructor() {
  return new Instructor(instructor);
}
  
public TextBook getTextBook() {
  return new TextBook(textBook);
}
Both Instructor and TextBook support copy constructors and Course uses these to return the objects requested as copies.
Instructor.java  
TextBook.java  
Course.java  
CourseDemo.java  

Read-only data members and immutable classes

Let's take a look again at the User class above.

User.java
public class User {
 
  private int id;
  private String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
 
  public int getId() {
    return id;
  }
  public String getName() {
    return name;
  }
 
  @Override
  public String toString() {
    return String.format("User(%s,%s)", id, name);
  }
 
  @Override
  public boolean equals(Object obj) {
    if (! (obj instance of User)) {
      return false;
    }
    User user = (User) obj;
    return this.id == user.id;
  }  
}
select
NetBeans flags the two data member lines suggesting to "make them final." The reason we can do this is because they can be treated like constants. The constructor initializes the values, but thereafter, they are unchanged by any member function. In particular, there are no setters in this class.

If we follow the suggestion, we'll get:

User.java
public class User {
 
  private final int id;
  private final String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
  ...  
}
select
We call this version of the User class immutable because there is no way to alter the content of an instantiated object. An immutable class with both the toString and equals methods defined is very much like the String class or other wrapper classes.

Using shallow vs. deep copies

If a class is immutable, we can return shallow copies Let's consider the classes we've examined in this section: When immutable classes are used within other classes, we can simply return them as shallow copies, because they cannot be modified. Otherwise we are usually obliged to return deep copies. Considering what we have used in this section so far,

Create deep copies when creating data members

This advisement refers to how the Course constructor is defined:
  public Course(String name, Instructor instr, TextBook text) {
    courseName = name;
    instructor = new Instructor(instr);
    textBook = new TextBook(text);
  }

Textbook Example Revisited

The current version of CourseDemo doesn't really illustrate the need for the deep copies. Here is version which does:

CourseDemoSecure
public class CourseDemoSecure {
  public static void main(String[] args) {
    // Create the course
 
    Instructor myInstructor = new Instructor("Kramer", "Shawn", "RH3010");
 
    TextBook myTextBook = new TextBook(
        "Starting Out with Java", "Gaddis", "Scott/Jones");
 
    Course myCourse = new Course("Intro to Java", myInstructor, myTextBook);
 
    System.out.println("--------------------------");
    System.out.println(myCourse);
 
    // Change the instructor
    myInstructor.set("Kline", "Robert", "UNA138");
 
    // Verify that the course is unchanged
    System.out.println("--------------------------");
    System.out.println(myCourse);
 
    // Retrieve and change the textbook used
    TextBook theBook = myCourse.getTextBook();
    theBook.set("Advanced Java", "Knuth", "Pearson");
 
    // Verify that the course is unchanged
    System.out.println("--------------------------");
    System.out.println(myCourse);
  }
}
select
The observation to make is that the original course information is unchanged despite our attempts to do so. If we had used shallow copies of the Instructor or TextBook object, the course information would have been altered.

Null values

Despite what the author may say, the code you write tends to assume that null values will not be used for objects. Often it is an error if they are used. For example, just try replacing the definition of myCourse in the above program by:
Course myCourse = new Course(null,null,null);

Using "this" for an overloaded constructor

This is section 8.8 in the textbook. We've already addressed the first topic. What remains is to see how the this keyword can be used to call an overloaded constructor. Let's again consider the User class that we have developed up to this point.

Let's add a setter for the id. Once we've added a setter, we need to consider adding a copy constructor as well. So here's the full class with all the features we've been disussing:

User.java (with, toString, equals, copy)
public class User {
 
  private int id;
  private String name;
 
  public User(int id, String name) {
    this.id = id;
    this.name = name;
  }
 
  public User(User user) {
    return new User(user.id, user.name);
  }
 
  public setId(int id) {
    this.id = id;
  }
 
  public int getId() {
    return id;
  }
  public String getName() {
    return name;
  }
 
  @Override
  public String toString() {
    return String.format("User(%s,%s)", id, name);
  }
 
  @Override
  public boolean equals(Object obj) {
    if (! (obj instance of User)) {
      return false;
    }
    User user = (User) obj;
    return this.id == user.id;
  }  
}
select
Suppose we want to add a User to the collection, who has a name but doesn't yet have an id. We'll assign all such users the id of 0. We could of course do the obvious overload:
  public User(String name) {
    this.name = name;
    this.id = 0;
  }
but, if the construction were somehow more complicated and we want to avoid reproducing the code, we can do so by calling the already-existing constructor:
  public User(String name) {
    this(name, 0);
  }
When this is used as you see here, it must be the very first statement within the constructor.

Enumerated Types

This is section 8.9 in the textbook. It's not the author's finest work. He overlooks many things. Also, this topic is not that important compared to everything else in this chapter.

Garbage Collection/Class Collaboration

These are sections 8.10/8.11 in the textbook. Ignore them.


© Robert M. Kline