Definition:
The notion of classes corresponds to a blueprint.
What is a blueprint?
Drawing of a structure
Plan
Instructions
Layout of design
Map
Where have you seen the notion of a blueprint?
Architecture
When you create buildings
When need to solve or maintain a solution
Coding
Machine design
City Hall
The ultimate goal of the blueprint is to duplicate or access information that is already been stated. Duplication in the context of generating multiple items of the same type. E.g., houses, apartments, stores.
The notion of blueprint in the book is called cookie cutter. The reason remains the same – to create multiple items of the same type.
In Object Oriented Programming (OOP), the notion of a class corresponds to the blueprint for a specific object that is desired to create. The classes are composed of the following items:
Fields: are the characteristics of the object. What makes an object unique. The more
Constructors: are in charge of initializing all the fields with actual values.
Methods: are the functionalities of the object by performing the operations with the fields.
To simplify the notion of object, we will discuss through the learning process the design of the Dollar Object.
We will design an object called Dollar. This dollar is the same dollar that you have in your wallet.
As shown in the figure, the dollar bill is composed of many characteristics (the fields). Let us brainstorm some of them:
Fields: the more characteristics you provide for the object, the more precise it becomes.
Face
Color
shape
Denomination
Serial number
Symbols
Verification seal
Year
Material
The next question is, how do you represent the fields in Java according to their data types? For simplicity purposes, let us pick four of them:
Face: String, e.g., “Washington,” “Jefferson,” “Lincoln,” “Hamilton,” “Jackson,” “Grant,” “Franklin.”
Denomination: int, e.g., 1, 2, 5, 10, 20, 50, 100, 1000
Serial number: String, e.g., F54931740C
Year: int, e.g., 2017
Constructors: are in charge of initializing all the fields. Java Provides the ability to create a default constructor and a customized set of constructors. Constructors have the following characteristics:
They have the same name as the class
They start with the public keyword
They DO NOT have a return type
They have (), some of them have parameters
You can have as many customized constructors as you want (polymorphism) – a.k.a. method overloading. Polymorphism provides flexibility in the usage of the methods/constructors.
Default Constructor
The default constructor does not take any argument, and it helps to initialize the fields to default values according to Java data types' default values[1]. In our particular object Dollar, the default values for:
denomination: 0
face: “” (empty string), OR null
serial: “” (empty string), OR null
year: 0
public Dollar(){
denomination = 0;
face = null;
serial = null;
year = 0;
}
Customized Constructors
The customized constructors provide you the flexibility to initialize the fields by providing a different amount of parameters. We will provide two customized constructors for the class Dollar.
Customized constructor #1: The first customized constructor typically takes the same amount of arguments as the total number of fields in the class. What will be the values for the fields on Dollar?
Therefore, the customized constructor may look as follows:
public Dollar(int newDenomination, String newFace,
String newSerial, int newYear){
denomination = newDenomination;
face = newFace;
serial = newSerial;
year = newYear;
}
Customized Constructor #2: For the second customized constructor, we will only provide 3 values:
public Dollar(int newDenomination, String newSerial, int newYear){
denomination = newDenomination;
serial = newSerial;
year = newYear;
}
Notice that based on the number of parameters, we initialized only 3 fields. But, we need to initialize ALL the fields. The remaining field to be initialized is the face. We can make a decision based on the denomination provided and initialize the field. E.g.,
public Dollar(int newDenomination, String newSerial, int newYear){
denomination = newDenomination;
serial = newSerial;
year = newYear;
if(denomination == 1)
face = "Washington";
else if(denomination == 2)
face = "Jefferson";
else if(denomination == 5)
face = "Lincoln";
else if(denomination == 10)
face = "Hamilton";
else if(denomination == 20)
face = "Jackson";
else if(denomination == 50)
face = "Grant";
else if(denomination == 100)
face = "Franklin";
}
Notice that regardless of the lack of a parameter for the face, we were able to initialize ALL the fields.
Let us test the object Dollar. We will be creating a class named DepOfTreasury (since at the department of treasury, there is machinery to create dollars for the nation).
When you create an instance of object Dollar, you must invoke the constructors. E.g.,
Dollar d1 = new Dollar(1,"Washington","F54931740C",2006);
You have seen this format on how to create an instance of an object for the past weeks. For example:
Scanner input = new Scanner(System.in);
int[] array = new int[5];
PrintWriter pr = new PrintWriter(“output.txt”);
JOptionPane p = new JOptionPane(“”);
File f = new File(“input”);
String s = new String("hello");
In the example, we are using the second constructor, the one that takes an int, String, String, and int as parameters of the constructor. The DepOfTreasury looks as follows:
1. public class DepOfTreasury{
2. public static void main(String [] args){
3. // instances of the object
4. // using the 2nd constructor
5. Dollar d1 = new Dollar(1,"Washington","K75719608D",2013);
6. // using the 3rd constructor
7. Dollar d2 = new Dollar(5,"ML12944865C",2002);
8. System.out.println("\t\tFor the first object");
9.
10. System.out.println(“$”+d1.denomination);
11. System.out.println(d1.face);
12. System.out.println(d1.serial);
13.
14. System.out.println("\t\t For the second object");
15. System.out.println(“$”+d2.denomination);
16. System.out.println(d2.face);
17. System.out.println(d2.serial);
18. }
19. }
By running the program, we obtained the following output.
For the first object
$1
Washington
K75719608D
For the second object
$5
Lincoln
ML12944865C
This demonstrates the method invocation from class to class.
The methods are the functionality of the objects. They perform operations based on the fields of the objects. In particular, we will discuss two types of methods: getters/accessors and setters/mutators
Getters: are responsible for retrieving information for the fields
Setters: are responsible for change the information for the fields
Characteristics of the Getters:
They are public methods
They have a return type based on the field you want to retrieve
They start with the word get
They do not have parameters
Use camel case style
Characteristics of the Setters:
They sometimes start with "public"[4]
They are void methods
They start with the word set
They have parameters. The parameters represent the new value for the field
Use camel case style
The setters take the parameter provided and assign to the current field.
What other methods can we define?
The type of functionality helps us to identify this object more precisely.
E.g.,
Create a method called getAge(). This method will calculate the age of the current dollar. The method will return an int representing the age of the dollar. Hint: the age can be calculated by subtracting the year of creation from the current year. E.g., 2017 – year.
Create a method called printInfo(). This method will print the general description of the object Dollar as follows:
$<denomination> face Year: ####, and is ## years old!
Notice that these methods:
· Do not require any parameters. The reason is that we have the fields as “local values” in the class Dollar. Since the variables belong to the scope of the class, they are visible in all the class methods.
· Do not have the static keyword anymore. The reason is that the blueprint is independent, from a static context. E.g., the main method. When you create an instance of the class in the main, you will create an object that contains all the fields and methods and can work independently from other Dollar objects[5].
If we go to the DepOfTreasury, and remove the previous lines of code that access each field independently of the class Dollar and substitute by:
System.out.println("\t\t For the first object");
d1.printInfo();
System.out.println("\t\t For the first object");
d2.printInfo();
Object-orientation helps us to reduce lines of code on the main and keep the high cohesion[6] of the class.
This activity requires the file: money.txt
Static class members
Often we need to have a global information that is visible to everybody in a program. These “global” variables must be defined as static class members.
In our example, the department of treasury creates certain number of dollars per year. This information is public and visible to the public. Therefore, it is needed to have a variable to keep track of how many times the department of treasury prints a dollar bill. We introduce this variable as a counter in Dollar:
public class Dollar{
// define the fields
private int denomination;
private String face;
private String serial;
private int year;
public static int counter;
}
The variable counter is:
public, which means that every entity outside of the class Dollar can access it
static, which means that can be reachable in any context, including the main on the DepOfTreasury
int, since we will keep track of each print of Dollar, must be a whole number
We have seen this behavior in the following:
> Math.PI
3.141592653589793
> Math.pow(2,3)
8.0
In addition to create this variable, every time we create a new instance of the Dollar, that counter must increase by one. Therefore, in all the constructors we need to add the statement:
counter++;
For example, in our default constructor, it will look as follows:
// default constructor
public Dollar(){
denomination = 0;
face = null;
serial = null;
year = 0;
counter++;
}
By going to the DepOfTreasury, we can add the following statements
Dollar d1 = new Dollar(1,"Washington","F54931740C",2006);
System.out.println("Total: "+d1.counter);
Dollar d2 = new Dollar(20,"Jackson","MK35065653B",2013);
System.out.println("Total: "+d2.counter);
These statements will print the following:
Total: 1
Total: 2
If we change the code as follows:
Dollar d1 = new Dollar(1,"Washington","K75719608D",2013);
Dollar d2 = new Dollar(5,"ML12944865C",2002);
System.out.println("Total: "+d1.counter);
This will generate the following:
Total: 2
Regardless of which object you are calling the static class member; it will reflect the changes that has been done by the same static members. Since we are in the same static context, we keep adding counter++ every time we run the program.
Dollar d1 = new Dollar(1,"Washington","F54931740C",2006);
Dollar d2 = new Dollar(20,"Jackson","MK35065653B",2013);
System.out.println("Total: "+Dollar.counter);
In the department of treasury, we can perform several operations. Since in this class we are creating Dollar objects and probably we will create more than two instances, then we need a holder to store all the instances of Dollars. The natural and human way to store the Dollars into a wallet. We have the following:
1. public static void main(String [] args){
2. Dollar d1 = new Dollar(1,"Washington","K75719608D",2013);
3. Dollar d2 = new Dollar(5,"ML12944865C",2002);
4. Dollar d3 = new Dollar(10,"B19811820K",2005);
5. Dollar d4 = new Dollar(100,"C98798451S",1999);
6. Dollar[] wallet = {d1,d2,d3,d4};
7. }
Notice that from Line 2 to 5, we define 4 different instances of Dollars. Now we will store them in a wallet (by defining an array of Dollars) as shown in Line 6.
d1 d2 d3 d4
0 1 2 3
Now that we know how to store Dollar objects into an array, we can now create a method called getTotal(). The method will take as an argument an array of Dollars and it will calculate the total amount of dollars we have in the wallet. The first attempt to create the method looks as follows:
1. public static int getTotal(Dollar[] array){
2. int total = 0; // variable to accumulate
3. for(int i = 0; i < array.length; i++){// BFF of arrays
4. total += array[i]; // add each value into sum
5. }
6. return total; // report back the sum
7. }
If we attempt to run the previous code, it will generate the following syntax error as:
1 error found:
File: ...\DepOfTreasury.java [line: 4]
Error: bad operand types for binary operator '+'
first type: int
second type: Dollar
The error is suggesting that on Line 4 we are trying to perform an operation with an object and an int. Which is not possible. Remember that the array wallet contains Dollar objects inside of each subscript. Therefore, we need to check the denomination in each subscript from the array. Then we can use the power of the getters to extract specific information, i.e., getDenomination().
The resulting method now looks as follows:
1. public static int getTotal(Dollar[] array){
2. int total = 0; // variable to accumulate
3. for(int i = 0; i < array.length; i++){// BFF of arrays
4. total += array[i].getDenomination();// add each value into sum
5. }
6. return total; // report back the sum
7. }
Textual Representation of the Dollar Object.
Let us try to write a method called getWalletInfo(). This method will take an array of Dollars as a parameter, and it will print the content of each Dollar that is in the wallet. The representation that we would like to have for each dollar object is:
"$"+denomination face serial #, year and is ## years old!";
Let us try to print what is inside the wallet just by using the simple for loop.
public static void getWalletInfo (Dollar[] array){
for(int i = 0; i < array.length; i++){
System.out.println(array[i]);
}
}
If we attempt to run the program, we will get:
Dollar@6759e5b2
Dollar@16d86ead
Dollar@957f5c9
Dollar@3f40b923
This is the hexadecimal memory address location for each object Dollar. We have seen this when you try to print an array.
Solution: We can use the method printInfo() that we implemented in Dollar. However, there is a more natural way to solve this situation. This is by overwriting the toString() method.
The toString() method is one of the eleven methods that is inherited from the ancestor Object in Java. Inheritance is another feature of OOP that will be discussed in the next section. The toString() method provides the textual representation of your object.[7]
From the above list, we recognize the equals method, when we compare two Strings (i.e., two objects). By overwriting the method toString() in the class, will allows to provide our own textual representation for the specific object we are implementing. In our case, Dollar.
Therefore, we can now go to the Dollar and overwrite the method inherited from the class Object as follows:
public String toString(){
String s = "$"+denomination+" Face: "+
face+" #"+serial+" Y:"+year+" and is "+
getAge()+" years old!";
return s;
}
Since we overwrote the method toString() in the class Dollar, now we have the following result:
$1 Face: Washington Y: 2006 and is years old!
$20 Face: Jackson Y: 2013 and is years old!
$10 Face: Hamilton Y: 2011 and is years old!
$5 Face: Franklin Y: 1998 and is years old!
Help us to identify members from a specific class. It also helps us to avoid conflict of variable names. The book calls it shadowing. It also helps to exercise the power of method overloading (a.k.a. polymorphism).
Variable Names. Recall the setters. We used a different name for the parameter that belongs to the fields.
E.g.,
public void setDenomination(int denomination){
denomination = denomination;
}
The left side of the = signs refers to the field. The right side of the = refers to the parameter. In order to avoid any confusion in identifying which variable is which, we use the word this to identify the field that belongs to THIS class.
public void setDenomination(int denomination){
this.denomination = denomination;
}
Method/Constructor overloading
Notice that we have several constructors. All of them basically do the same functionality – i.e., initialize all the fields of the class. We noticed that there is a redundancy of coding in the constructors. For example, let us examine customized constructor #1 and customized constructor #2.
// customized constructor #1
public Dollar(int d, String f, String s, int y){
denomination = d;
face = f;
serial = s;
year = y;
counter++;
}
// customized constructor #2
public Dollar(int d, String s, int y){
denomination = d;
serial = s;
year = y;
if(d == 1){
face = "Washington";
// … code omitted for space …
counter++;
}
}
There is a lot of redundancy between both constructors. We can avoid redundancy by using the keyword this. The keyword “this” will identify/map inside of THIS class a method/constructor that satisfies the number of parameters you are passing through.
For example, in customized constructor #1
// customized constructor #1
public Dollar(int newDenomination, String newFace, String newSerial, int newYear){
this(newDenomination, newSerial, newYear);
}
Now that we understand the power of creating objects and invoking the methods that belong to each object, we can explore the notion of inheritance. In real life, we understand by inheritance as “something that we receive from an old/previous generation”. Some examples of inheritance can be:
Money/Land
Features in our face
Values
debt
Personality
Genetics
Diseases
In Java, we use the power of inheritance from an ancestor called Object. As we described in previous section, in Java we inherited 11 methods from this class (this including the .toString() and .equals()).
One the features why inheritance was introduced in programming, was to incorporate the notion of reusability. Since we already spend an enormous amount of time designing a specific object, might as well re-use it in different form.
The inheritance relationship that is established in java is called the is-a relationship. This means that the objects that we define are a sub-form of a super object as follows:
The relationship allows us to identify the hierarchy of what object belongs to other objects. In programming, the class Object is the root of all objects, also called the super class. We will keep with the same example of Dollar, being the super class, and now with a new object called CanadianDollar, to be the subclass.
The setup that allows a class to inherit attributes to the super class is by using the keyword extends after the definition of the class. This allows to communicate with the super class Dollar and inherit the fields, and methods and allows to communicate with constructors to initialize the fields.
public class CanadianDollar extends Dollar{
}
In addition, we need to add the remaining fields that are not part of Dollar, however, they are needed in CanadianDollar. In our case, it is only color, and it should be represented as a String.
String color;
All fields in java, since they represent the uniqueness of the object, should be private. We will introduce another encapsulation type called protected. The protected type is useful in the inheritance stage since we are creating objects that communicate with other objects that share the same fields or methods.
protected String color;
and in a UML diagram is denoted with the symbol “#” (if you are old school, we called this symbol pound if you are new school, you may call it hashtag).
Let us define a customize constructor that takes the same number of parameters as the number of fields that we have in Dollar, i.e., int denomination, String face, String serial, int year, String color.
The way we communicate the subclass with the super class, is through the keyword super. The keyword super must be used as the first line in the constructor to call the super class constructor. So it looks as follows:
This means that we will call in our super calls Dollar a customized constructor that takes four parameters in the order of int, String, String, and int.
public CanadianDollar(int denomination, String face, String serial, int year, String color){
super(denomination, face, serial, year);
this.color = color;
}
Let us save the CanadianDollar under the same directory where Dollar is saved
In the tester, i.e., DepOfTreasury, we will incorporate the following Canadian Dollars as part of our inventory of Dollars.
CanadianDollar c1 = new CanadianDollar(5,"Laurier", "C123456D", 2000,"blue");
CanadianDollar c2 = new CanadianDollar(10,"Macdonald","C789456D",1999,"purple");
CanadianDollar c3 = new CanadianDollar(20,"ElizabethII","C212121D", 1998,"green");
System.out.println(c1);
By compiling and running the above code, we expect the hexadecimal address location when we attempt to print the object c1, however, we obtained:
$5 Face: Laurier #C123456D Y:2000 and is 17 years old!
Since CanadianDollar extends the class Dollar, we inherited the toString() method from Dollar.
Notice that the Canadian dollar has two attributes in addition to the regular Dollar object.
It has a ‘C’ before the $ sign, and
It has a color.
It will be nice to have textual representation under CanadianDollar that provided us with these attributes. We can make that happen by overwriting the toString() method inherited from Dollar.
In the class CanadianDollar, we can incorporate the following method.
public String toString(){
return “C”+super.toString()+color;
}
By running the same program from the tester, we got the following for c1
C$5 Face: Laurier #C123456D Y:2000 and is 17 years old! Color: blue
If we use the Javadoc to visualize the information in the class CanadianDollar, we have
Notice that the toString() now appears under the method for CanadianDollar, and it was removed from Dollar.
Occasionally, we can prevent overwriting for future methods. The way to avoid method-overwriting is by incorporating the modifier final in the method. For example,
public final String toString(){
String s = "$"+denomination+" Face: "+face+" #"+serial+" Y:"+year+" and is "+getAge()+" years old!";
return s;
}
When we attempt to run this program; we will have the following syntax error at CanadianDollar
1 error found:
File: C:\...\CanadianDollar.java [line: 11]
Error: toString() in CanadianDollar cannot override toString() in Dollar
overridden method is final