Java Objects

Introducing Objects and Classes

Up to this point you have actually made extensive use of Java pre-defined objects including String, Scanner, Random, etc. The textbook illustrates these usages in a demo program:
ObjectDemo.java  
The point is that you've already used existing classes and made objects from them with the statements:
Scanner keyboard = new Scanner(System.in);
 
Random rand = new Random();
 
PrintWriter outputFile = new PrintWriter("numbers.txt");
The keyword new is what is used to generate an object instance of the specific class. You actually have usec this object generation with the String class as well, although not explicitly. For example:
System.out.print("How many random numbers should I write? ");
is really regarded in Java as this:
System.out.print( new String("How many random numbers should I write? ") );

Class/Object Motivation

Consider a program which maintains information about students, where a student consists of these 3 fields:
String name;
int id;
double gpa;

PrintStudents.java (version 1)
public class PrintStudents {
 
  public static void main(String[] args) {
 
    String student1_name = "Joe Smith";
    int student1_id = 111111;
    double student1_gpa = 3.22;
 
    String student2_name = "Alice Merrick";
    int student2_id = 222222;
    double student2_gpa = 2.67;
 
    print_student(student1_name, student1_id, student1_gpa);
    print_student(student2_name, student2_id, student2_gpa);
  }
 
  public static void print_student(String name, int id, double gpa) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            name, id, gpa
            );
  }
}
select
The way we're doing this is very clunky. We want the pieces of student information to be "grouped together," but in reality they are not. Secondly, the method usage is also very clunky, we have to pass in all 3 pieces of information, making it too easy to make a mistake like this:
print_student(student2_name, student2_id, student1_gpa);
Perhaps the only good thing about this program is the introduction of the very useful function
printf
The idea is simple:
System.out.printf(
   "string defining insertion points", insert1, insert2, ...);
The "insertion points" are defined by special character sequences beginning with the "%" character. When you first learn to use it, you can make simply make all insertion points be "%s"; eventually other characters can replace "s" to create other effects.

The printf statement, like print does not automatically add a newline which is done using println, and so you must explicitly add the newline character at the end of the output string.

Create a Student class

The key point about a class is that it encapsulates the data. We start out by creating a new Java source file:

Student.java (initial)
public class Student {
  public String name;
  public int id;
  public double gpa;
}
Here we are introducing a new variable concept:
data members
So far you only have used variables as local variables within methods. Using this class, we write a new driver program:

PrintStudents.java (version 2)
public class PrintStudents {
 
  public static void main(String[] args) {
 
    Student student1 = new Student();
    student1.name = "Joe Smith";
    student1.id = 111111;
    student1.gpa = 3.22;
 
    Student student2 = new Student();
    student2.name = "Alice Merrick";
    student2.id = 222222;
    student2.gpa = 2.67;
 
    print_student(student1);
    print_student(student2);
  }
 
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.name, student.id, student.gpa
            );
  }
}
select
Hopefully you can appreciate the improvement in that the student fields are "carried along" with student; this concept is called encapsulation. The textbook discusses passing an object as the parameter of a method, but it is no different than passing a String in that the object is passed "by-reference-by-value."

Hide the data

The type of encapsulation used so far is reminiscent of the struct from the C language, which is the ancestor of Java. In general the goal is to hide the actual data and provide accessor methods to retrieve or modify it and so the very first step is to make the actual data inaccessible from outside the Student class like this:

Student.java
public class Student {
  private String name;
  private int id;
  private double gpa;
}
We are introducing a new access qualifier:
private
With this change in effect we can no longer do what we have done because we can neither write nor read the data fields directly:
student1.name
student1.id
student1.gpa

Getters and Setters

The solution is to introduce public methods which allow us to access the private fields either by reading them with "getters" or writing them with "setters." The textbook prefers this terminology:
accessor   instead of   getter
mutator    instead of   setter
In order to reproduce what we already have done, we would need both getters and setters for each field.

Key in the code For best learning results, do not copy/paste the methods of the Student class; likewise, do not replace the code of the main function of "version 2" by copy/paste.

Instead, one-by-one, key in the setter members and alter the code in the main function, seeing the errors disappear. Then key in the getters and see the errors in the support method of the main class disappear. Lastly, add the "empty student".


Student.java
public class Student {
  private String name;
  private int id;
  private double gpa;
 
  public void setName(String _name) {
    name = _name;
  }
 
  public void setId(int _id) {
    id = _id;
  }
 
  public void setGpa(double _gpa) {
    gpa = _gpa;
  }
 
  public String getName() { 
    return name;
  }
 
  public int getId() { 
    return id;
  }
 
  public double getGpa() { 
    return gpa;
  }
}
select
Our new version of PrintStudents.java is this:

PrintStudents.java (version 3)
public class PrintStudents {
 
  public static void main(String[] args) {
 
    Student student = new Student();
 
    Student student1 = new Student();
    student1.setName("Joe Smith");
    student1.setId(111111);
    student1.setGpa(3.22);
 
    Student student2 = new Student();
    student2.setName("Alice Merrick");
    student2.setId(222222);
    student2.setGpa(2.67);
 
    print_student(student);
    print_student(student1);
    print_student(student2);
  }
 
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
}
select
We've included the student object initialized without any other settings. The output of this run is:
The student null with id 0 has gpa = 0.0
The student Joe Smith with id 111111 has gpa = 3.22
The student Alice Merrick with id 222222 has gpa = 2.67

Class concepts

Like the main class, this new class follows this structure
access-specifier class class-name {
  ...
}
For our purposes, there will only be one class per file.

The public class access specifier

Do we need "public" in
public class Student { ... }
The answer is "yes and no." The textbook says in section 6.2, when discussing it's own class
public class Rectangle
that the public access specifier
"indicates that the class will be publicly available to code that is written outside the Rectangle.java file."
So what if we remove public in front of Student? Will the class Student no longer be available to PrintStudents? Try it: you'll see that it doesn't change anything. The real need for public is when the class is put into a package. For example the class Random is really
java.util.Random
The java.util prefix represents a package, and Random belongs to this package. For the most part, only public features of a class within a package are accessible to other classes outside the package. Usage of packages is beyond the scope of this course.

The other important usage of public is to make the file name match the class name. For example: You'll see that NetBeans will not accept the change as is, so remove set the name back to Student. When Java runs, it identifies the classes it needs by class name, and finds the code for this class by looking for a file of the same name.

The private data

Although it's not necessary, the class data variables are commonly listed first:
public class Student {
  private String name;
  private int id;
  private double gpa;
  ...
}
Whenever the access qualifier private is used it means:
the entity qualified by it can only be used within the class itself.
As we indicated above, it's usually important to hide the actual data, to prevent inadvertent changes outside of the control of the member functions.

Instantiation

Creating a Student object is done like any other object (except String which can hide the actual creation);
Student student = new Student();
The creation method uses the default (no argument) constructor, a concept we will explore a bit later. The left side Student represents the type of the variable and the right side Student represent the class being instantiated. They are often the same, but they don't have to be; for example this is also OK:
Object student = new Student();
As you would expect, every instance of a user-defined class "is a" Java Object. The implications of this "is a" relationship is beyond the scope of this course.

Prior to assignment, the initial part of the statement,
Student student 
creates:
student: 
null
Recall that Java auto-initializes variables if not explicitly initialized. Objects are instantiated to null. Once instantiated by
Student student = new Student();
you get a reference to the encapsulated data:
student: 
ref
null
(name) student:
ref
0
(id) student:
ref
0.0
(gpa)
The name, being a String object is initialized to null.

The setter methods

Since private data are accessible only within the class, we need to introduce methods to manipulate it from the outside. The first concept is the setter method. We'll start with the name field:
public class Student {
  private String name;
  private int id;
  private double gpa;
 
  public void setName(String _name) {
    name = _name;
  }
  ...
}
Unlike previous methods, this does not use the keyword static. We are creating instances of the Student class, and as you see, each instance can have different field values. A static declaration means "the same for any entity of the class". Try breaking it: The next issue is the method parameter: it cannot be blindly set to the field name, i.e. this is wrong:
public class Student {
  ...
  public void setName(String name) {
    name = name;
  }
  ...
}
In this case we say that the parameter "name" is shadowing the data member "name". The outcome is that the parameter is set to its own incoming value, i.e., nothing. You actually can use the data field name as the parameter, but further code is needed to distinguish these. We'll solve this problem later.

We will see later how to get around this shadowing problem, but for now, just give the parameter a different, presumably related name. I would encourage you to employ the underscore identifier character as I have done:
public class Student {
  ...
  public void setName(String _name) {
    name = _name;
  }
  ...
}
The underscore is rarely used as the first character of an identifier; in fact, it would be discouraged to use it in this way for data members.

The textbook employs a less systematic way for its Rectangle class example:
data member    parameter
length         len
width          w
Although this is OK, the point is that it is more ad-hoc, forcing you to coming up with "yet another name" instead of simply altering what you already have.

The instantiation and setters allows us to create students, e.g.:
Student student1 = new Student();
student1.setName("Joe Smith");
student1.setId(111111);
student1.setGpa(3.22);
student1: 
ref
ref1
Joe Smith
student1:
ref
111111
student1:
ref
3.22

The getter methods

To print the student field information afterwards, we need public methods which access the fields, since the data is private. Towards this end we introduce the getter methods:
public class Student {
  private String name;
  private int id;
  private double gpa;
  ...
  public String getName() { 
    return name;
  }
 
  public int getId() { 
    return id;
  }
 
  public double getGpa() { 
    return gpa;
  }
}
With these available, we now have what we need to print a student's fields:
public class PrintStudents {
  ...
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
}

UML Diagrams

UML (Unified Modeling Language) is intended to be a design tool which provides simplified visual diagrams of the entities in a system along with their relationships. A system for us is a program and the entities are classes. The "unified" idea means that is applicable to all systems, in particular all programming languages.

Below are two possible UML-like presentations of our Student class. The one on the right is the "official" one, the one on the left is a variant which I think is better suited to Java classes:

Student

- String name 
- int id
- double gpa

+ void setName(String)
+ void setId(int)
+ void setGpa(double)
+ String getName()
+ int getId()
+ double getGpa()
modified UML
Student

- name: String 
- id: int
- gpa: double

+ setName(_name: String): void 
+ setId(_id: int): void
+ setGpa(_gpa: double): void
+ getName(): String
+ getId(): int
+ getGpa(): double
official UML

They both convey the same amount of information:

Setter/Getter method names

Alternative names for setter and getter methods are accessor and mutator. Both names appear in common usage.

Java classes uses strict naming conventions for certain public methods in order to expose properties about objects of this class. A property has a name and a Java type. For example, in order to expose the property:
String myProperty
these would be appropriate method usage:
public class SomeClass {
  public String getMyProperty() {...}                   // a "getter"
  public void setMyProperty(String param_name) {...}    // a "setter"
}
One or both of the two public methods exposes the property as a read-only, write-only, or read-write property of type String. The property as well as the getter and setter are expressed is in (lower) camel-case style. In some situations in Java, it is crucial that they be written exactly this way, because Java can discover the classes exposed properties.

A common implementation of this property is as a private data member named by the property and exposed as follows:
private String myProperty;
public String getMyProperty() { return myProperty; }
public void setMyProperty(String _myProperty) { myProperty = _myProperty;  } 
A variation in the getter/setter naming convention is employed for boolean properties in that the read property uses "is" instead of "get", e.g., for the boolean visible property would be exposed like this:
public boolean isVisible() {...}
public void setVisible(boolean param_name) {...}
Although you do not have to conform to this standard, it is a good idea to do so when possible. Java code can be written to automatically detect these exposed properties and activate the getter/setter without explicitly using the member function.

Method names need not match data member names

Although we don't usually like to make things "obvious," there is no real need to match up the getter/setter method names with the data member names. For example you could do this (although not recommended):

Student

- String whoAmI 
- int myIdentification
- double howIAmDoing

+ Student()

+ void setName(String)
+ void setId(int)
+ void setGpa(double)
+ String getName()
+ int getId()
+ double getGpa()

In this case, for example, the setName function could then be written like this:
public void setName(String name) {
  whoAmI = name;
}

Textbook Rectangle Class

The textbook works to develop and explain the following Rectangle class (it will be modified further in later sections):
Rectangle.java  
It has the UML diagram:

Rectangle

- double length
- double width

+ void setLength(double)
+ void setWidth(double)
+ double getLength()
+ double getWidth()
+ double getArea()

The novelty here is the presence of the "read-only" area property. Here is the demo program:
RectangleDemo.java  

Why hide the data?

Why do we make the data non-public? If we have getters and setters, isn't it the same thing as making it public? Other than saying "that's the way we do things," the best answer to say is that it gives us a better control of the data. Here is an illustrative example.

This is our class:

Gadget.java
public class Gadget {
  private String name;
  private double price;
  private int timesChanged = 0;
 
  public void setName(String _name) {
    name = _name;
  }
  public void setPrice(double _price) {
    price = _price;
    ++timesChanged;
  }
  public String getName() {
    return name;
  }
  public double getPrice() {
    return price;
  }
  public int getTimesChanged() {
    return timesChanged;
  }
}
select
and here is the driver program:

MakeGadgets.java
public class MakeGadgets {
 
  public static void main(String[] args) {
    Gadget gadget = new Gadget();
    gadget.setName("whiskbroom");
    gadget.setPrice(4.99);
 
    System.out.println("The gadget is a : " + gadget.getName());
 
    System.out.println("its price is now : " + gadget.getPrice());
 
    gadget.setPrice(5.99);
    gadget.setPrice(6.99);
    gadget.setPrice(7.99);
 
    System.out.println("its price is now : " + gadget.getPrice());
 
    System.out.printf("the price was set %s times\n", gadget.getTimesChanged());
  }
}
select
The Gadget class has the UML diagram:

Gadget

- String name
- double price
- int timesChanged

+ void setName(String)
+ void setPrice(double)
+ String getName()
+ double getPrice()
+ int getTimesChanged()

The key points are that:

Constructors

Let's go back to the Student class
Student Class: Show Hide
public class Student {
  private String name;
  private int id;
  private double gpa;
 
  public void setName(String _name) {
    name = _name;
  }
  public void setId(int _id) {
    id = _id;
  }
  public void setGpa(double _gpa) {
    gpa = _gpa;
  }
 
  public String getName() { 
    return name;
  }
  public int getId() { 
    return id;
  }
  public double getGpa() { 
    return gpa;
  }
}
select
Main Class: Show Hide
public class PrintStudents {
 
  public static void main(String[] args) {
 
    Student student = new Student();
 
    Student student1 = new Student();
    student1.setName("Joe Smith");
    student1.setId(111111);
    student1.setGpa(3.22);
 
    Student student2 = new Student();
    student2.setName("Alice Merrick");
    student2.setId(222222);
    student2.setGpa(2.67);
 
    print_student(student);
    print_student(student1);
    print_student(student2);
  }
 
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
}
select
Recall how an object of this class is instantiated:
Student student = new Student();
The creation of the student object is done through what is called a constructor. Without any explicit constructors declared, the so-called default constructor is used. What happens is that we get a reference to a chunk of memory formed by the data members. Each data member is assigned a default value based on its type. Objects are assigned null.

A data member can also have an assignment which will be used in place of setting a default value. For example, we could have:

Student.java
public class Student {
  private String name;
  private int id = 999999;
  private double gpa = 1.0;
  ...
}
After the instantiation call, we would have:
student: 
ref
null
student:
ref
999999
student:
ref
1.0

The No-Arg Constructor

We can, however, define the constructor used within the class, like:

Student.java
public class Student {
  private String name;
  private int id = 999999;
  private double gpa;
 
  public Student() {
    name = "Nobody";
    id = 1;
  }
  ...
}
This is called the no-arg constructor. With this in place, the creation of an object
Student student = new Student();
starts with the default constructor, but then activates the no-arg constructor, resulting in this object:
student: 
ref
ref1
Nobody
student:
ref
1
student:
ref
0.0
The default constructor has been subsumed by a user-defined no-arg constructor. The UML diagram now reflects this addition:

Student

- String name 
- int id
- double gpa

+ Student()

+ void setName(String)
+ void setId(int)
+ void setGpa(double)
+ String getName()
+ int getId()
+ double getGpa()

Whether you separate the constructors from the remaining methods appears to be optional.

A Constructor with Arguments

In our original example with a statement sequences like this:
Student student1 = new Student();
student1.setName("Joe Smith");
student1.setId(111111);
student1.setGpa(3.22);
    
Student student2 = new Student();
student2.setName("Alice Merrick");
student2.setId(222222);
student2.setGpa(2.67);
The apparent intent is to create the student and immediately initialize his/her values. We can do this more succinctly by creating a constructor with 3 arguments which does the initialization itself.

Student.java
public class Student {
  private String name;
  private int id;
  private double gpa;
 
  public Student(String _name, int _id, double _gpa) {
    name = _name;
    id = _id;
    gpa = _gpa;
  }
  ...
}
With this in place, our demo program goes like this:

PrintStudents.java (version 4)
public class PrintStudents {
 
  public static void main(String[] args) {
 
    //Student student = new Student();
 
    Student student1 = new Student("Joe Smith", 111111, 3.22);
 
    Student student2 = new Student("Alice Merrick", 222222, 2.67);
 
    //print_student(student);
    print_student(student1);
    print_student(student2);
  }
 
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
}
This is obviously an improvement over the original. Notice now that we have commented out the no-arg creation. With only the 3-argument constructor, the no-argument creation no longer works, i.e., the default constructor is no longer in effect. The UML diagram would be:

Student

- String name 
- int id
- double gpa

+ Student(String, int, double)

+ void setName(String)
+ void setId(int)
+ void setGpa(double)
+ String getName()
+ int getId()
+ double getGpa()

Objects as Parameters

We've already passed a Student object into the method:
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
We've discussed that this passing method is just like that used for Strings, pass "by-reference-by-value." The object argument is not necessarily a variable, but the reference to the object is set as the value of the parameter. Here's the diagram for the call with student1:
(parameter) student: 
ref
(argument) student1:
ref
 
(name) student1 (argument):
ref
 
(id) student1 (argument):
ref
 
(gpa)

A key difference between Student and String objects is that Student objects are mutable. For example, we might have this method:
  public static void changeGpa(Student student, double new_gpa) {
    System.out.printf("Changing gpa (%s) for student #%s.\n", 
        student.getGpa(), student.getId() 
    );
    student.setGpa(new_gpa);
  }
select
Try adding this method to PrintStudents.java, adding one statement at the end of main to test:
  changeGpa(student2, 3.01);
  System.out.println("New gpa: " + student2.getGpa());
select

Overloading

Our immediate need it to figure out how to get the default constructor back along with the 3-argument constuctor. The point is that Java allows methods and constructors with the same name so long as their argument signatures are "different enough" in the sense that the method/constructor usage is not ambiguous. In particular, we can allow this:

Student.java
public class Student {
  private String name;
  private int id;
  private double gpa;
 
  public Student(String _name, int _id, double _gpa) {
    name = _name;
    id = _id;
    gpa = _gpa;
  }
 
  public Student() {
  }
 
  ...
}
With this in place, our demo program is now back to full force:

PrintStudents.java (version 5)
public class PrintStudents {
 
  public static void main(String[] args) {
 
    Student student = new Student();
 
    Student student1 = new Student("Joe Smith", 111111, 3.22);
 
    Student student2 = new Student("Alice Merrick", 222222, 2.67);
 
    print_student(student);
    print_student(student1);
    print_student(student2);
  }
 
  public static void print_student(Student student) {
    System.out.printf("The student %s with id %s has gpa = %s\n",
            student.getName(), student.getId(), student.getGpa()
            );
  }
}
In this case, the no-arg constructor does exactly what the default constructor would do. The UML diagram reflects the overloaded constructor:

Student

- String name 
- int id
- double gpa

+ Student()
+ Student(String, int, double)

+ void setName(String)
+ void setId(int)
+ void setGpa(double)
+ String getName()
+ int getId()
+ double getGpa()

Textbook BankAccount Example

BankAccount.java  
and here is the associated test program:
AccountTest.java  
Here is the UML:

BankAccount

- double balance

+ BankAccount()
+ BankAccount(double)
+ BankAccount(String)

+ void depost(double)
+ void depost(String)
+ void withdraw(double)
+ void withdraw(String)
+ void setBalance(double)
+ void setBalance(String)
+ double getBalance()

This example is overkill on overloading, effectively regarding the balance equally as a double as well as a String. This example offers variants on the setters and getters we've seen up to this point, for example:


© Robert M. Kline