java
*
Chapter 7
Object-Oriented Programming – Additional Details
- Object Creation - a Detailed Analysis
- Assigning a Reference
- Testing Objects For Equality
- Passing References as Arguments
- Method-Call Chaining
- Overloaded Methods
- Constructors
- Overloaded Constructors
1
*
1. In this chapter, I demo these files:
No files
*
Object Creation - a Detailed Analysis
- Let's start the chapter with a behind-the-scenes detailed look at what happens when a program instantiates an object and stores its address in a reference variable.
- Code fragment:
1. Car car;
2. car = new Car();
3. car.year = 2008;
- Space is allocated in memory for the car reference variable. The car reference variable will hold the address of an object, but since there's no object created for it yet, it doesn't yet hold a legitimate address.
- Space is allocated in memory for a new Car object. The address of the allocated space is assigned to car.
- The car variable's value (the address of a Car object) is used to find the Car object in memory, and then 2008 can be stored in the Car object. Note that for this assignment to work, we're making the simplifying assumption that year is a public instance variable.
1
2
3
4
reference variable declaration
object instantiaton
Assign 2008 to car's year instance variable
*
1. Having a clear understanding will help when it comes time to understand other OOP operations, and it will help with some debugging efforts.
2. Notice that I'm using #'s instead of bullets for these next 3 items.
Each item is an in-depth explanation of its same-numbered line of code.
Let's see what happens for the first statement …
3. What's the default value for a reference variable?
It depends.
There are two places where variables can go.
If the reference variable is defined locally within a method (i.e., it's a local variable), then it gets garbage initially.
If it's defined at the top of a class, above all the method definitions (i.e., it's an instance variable), then it gets initialized to null.
Since this declaration doesn't have an access modifier (private), we can assume it's a local variable so car will contain garbage by default.
4. Normally, all instance variables, like the year instance variable, are private, and you have to access them with the help of a method.
But I'm trying to simplify things so I can show an animation on the next slide ...
*
Object Creation - a Detailed Analysis
- Code fragment:
1. Car car;
2. car = new Car();
3. car.year = 2008;
?
1
2
3
4
5
*
[1. In the slide show, initially, the memory block is shown by itself.]
2. This is the same code fragment as on the previous slide.
I'll now pictorially illustrate what happens for each of the three lines of code.
3. I click on the first line of code (or use a down arrow) to make the car reference variable appear.
I click on the second line of code to make the car object stuff appear.
I click on the third line of code to make the 2008 appear.
4. Note that the car reference variable is stored in memory, but I showed it outside of the memory block to make the picture clearer.
[5. All variables (including the car reference variable) are stored in a special part of memory.
Does anybody know where?
The stack!
Objects are stored in another special part of memory - where?
The heap!]
1062�
1062�
2008
*
Assigning a Reference
- The result of assigning one reference variable to another is that both reference variables then point to the same object.
- With both reference variables pointing to the same object, if the object is updated by one of the reference variables, then the other reference variable will notice that change when it attempts to access the object.
- That can be disconcerting!
1
2
*
1. Why do they point to the same object?
What thing is actually assigned during the assignment operation?
The address stored in the right-hand-side's reference variable is assigned into the left-hand-side's reference variable.
So the two reference variables then hold the same address and that means they point to the same object.
Why is that sometimes a problem?
I read the next bullet item ...
2. I'll now provide example code that shows how this can be annoying …
*
Assigning a Reference
- Suppose you want to create two Car objects that are the same except for their color. Your plan is to create the first car, copy the first car to the second car, and then update the second car's color instance variable. Will this code accomplish that?
Car stacyCar;
Car johnCar = new Car();
johnCar.setMake("Toyota");
johnCar.setYear(2008;
johnCar.setColor("silver");
stacyCar = johnCar;
stacyCar.setColor("peach");
1
*
1. Next slide …
*
Assigning a Reference
- The problem with the previous slide's code is that the stacyCar = johnCar; statement causes the two references to point to the same single Car object. Thus, johnCar's color becomes "peach" (and that was not intended).
johnCar = new Car();
...
stacyCar = johnCar;
stacyCar.setColor("peach");
1
*
1. I show how the code creates the picture.
*
Assigning a Reference
- If you want to make a copy of a reference variable, you should not assign the reference to another reference. Instead, you should instantiate a new object for the second reference and then assign the two objects' instance variables one at a time.
johnCar = new Car();
stacyCar = new Car();
<assign johnCar instance variables to stacyCar instance variables>
1
*
1. I show how the code creates the picture.
*
Assigning a Reference
- On the next slide, we make a copy of the johnCar reference variable by calling a makeCopy method.
- The makeCopy method implements the strategy outlined on the previous slide - it instantiates a new object and then copies instance variables into it one at a time. More specifically, the makeCopy method:
- Instantiates a local variable named car.
- Copies the calling object car's instance variables into the local variable car's instance variables.
- Returns the local variable car to the calling module.
2
1
*
1. I read the entire next slide and return here.
2. I reread these three implementation details while pointing to the associated code on the makeCopy method 2 slides away.
*
Assigning a Reference
public static void main(String[] args)
{
Car johnCar = new Car();
Car stacyCar;
johnCar.setMake("Toyota");
johnCar.setYear(2008);
johnCar.setColor("silver");
stacyCar = johnCar.makeCopy();
stacyCar.setColor("peach");
} // end main
1
*
1. Note the call to makeCopy.
makeCopy returns a copy of johnCar and it's assigned into stacyCar.
stacyCar and johnCar then point to two separate objects, so the stacyCar.setColor("peach") method call updates only the stacyCar reference, not the johnCar reference. (yeah!)
*
Assigning a Reference
public class Car
{
private String make;
private int year;
private String color;
...
public Car makeCopy()
{
Car car = new Car();
car.make = this.make;
car.year = this.year;
car.color = this.color;
return car;
} // end makeCarCopy
} // end class Car
*
*
Testing Objects for Equality
- Using the == operator:
- When comparing two reference variables with ==, you'd probably expect == to return true if the data in the two reference variables is the same. Unfortunately, that's not how things work. For example, this prints "different":
Car car1 = new Car();
car1.setColor("red");
Car car2 = new Car();
car2.setColor("red");
if (car1 == car2)
{
System.out.println("the same");
}
else
{
System.out.println("different");
}
The car1 == car2 expression returns false. Why?
1
*
1. Even though car1 and car2 have the same contents, they're not considered to be equal with the == operator because they are distinct objects with different storage locations in memory.
*
Testing Objects for Equality
- Using the == operator (continued):
- The == operator returns true if the two reference variables point to the same object; i.e., the two reference variables contain the same address. For example, what does this code fragment print?
Car car1 = new Car();
Car car2 = car1;
if (car1 == car2)
{
System.out.println("the same");
}
else
{
System.out.println("different");
}
1
*
1. output:
the same
*
Testing Objects for Equality
- Usually, the == operator is not good enough. You'll usually want to compare the contents of two objects rather than just whether two reference variables point to the same object.
- To do that, you'll need to have an equals method in the object's class definition that compares the contents of the two objects.
1
*
1. You'll probably want to create an equals method for most of your homemade classes because it's very common to want to test two objects to see whether they contain the same data.
We'll see an example on the next slide ...
*
Testing Objects for Equality
- Write an equals method for a Car2 class. Use this skeleton:
public class Car2
{
private String make;
private int year;
private String color;
<equals method goes here>
} // end class Car2
public class Car2Driver
{
public static void main(String[] args)
{
Car2 bradCar = new Car2();
Car2 beatriceCar = new Car2();
...
if (bradCar.equals(beatriceCar))
{
System.out.println("cars have identical features");
}
2
3
4
5
1
*
1. Note that we're using a Car2 class rather than a Car class so that you can store this program and the previous Car program on your computer in the same directory and there will be no confusion.
But in the real world, you'd just have one car program and it would use a class named Car, not Car2.
2. See where the equals method definition should go?
3. See the way that we call the equals method?
4. I write this on the board with students' help:
public boolean equals(Car2 otherCar)
{
<How do we access the calling object's make instance variable and also the otherCar parameter's make instance variable?>
if ((this.make.equals(otherCar.make)) &&
(this.year == otherCar.year)) &&
(this.color.equals(otherCar.color)))
{
return true;
}
else
{
return false;
}
} // end equals
5. Note that we're using the String class's equals method inside of our Car class equals method.
That's perfectly OK - the compiler is able to handle two different methods that happen to have the same name.
*
Testing Objects for Equality (hidden)
- Write an equals method for a Car2 class. Use this skeleton:
public class Car2
{
private String make;
private int year;
private String color;
<equals method goes here>
} // end class Car2
public class Car2Driver
{
public static void main(String[] args)
{
Car2 bradCar = new Car2();
Car2 beatriceCar = new Car2();
...
if (bradCar.equals(beatriceCar))
{
System.out.println("cars have identical features");
}
1
2
*
1. Suppose we wanted uppercase colors to be considered the same as lowercase colors.
If that's the case, what should we change in the code?
I write "equalsIgnoreCase" on the board with arrows pointing to the colors' equals.
2. Is there a shortcut alternative way to write the body of the equals method?
I write this on the board:
return (this.make.equals(car.make) &&
(this.year == car.year) &&
(this.color.equals(car.color));
*
Passing References as Arguments
- Suppose you pass a reference variable to a method, and inside the method you update the reference variable's instance variables. What happens? …
- Remember that a reference variable holds the address of an object, not the object itself.
- So in passing a reference variable argument to a method, a copy of the object's address (not a copy of the object itself) is passed to the method and stored in the method's parameter.
- Since the parameter and the argument hold the same address value, they point to the same object. Thus, if one of the parameter's instance variables is updated, then the update will simultaneously update the argument's instance variable in the calling module.
1
2
*
1. By now, you should be fairly comfortable with passing an argument to a method.
In the previous chapter, we covered all you need to know about passing primitive types to a method.
And in the previous example, we passed a reference type to a method.
More specifically, we passed a car reference variable to the equals method, and inside the method we read the reference variable's instance variables - make, year, and color.
There's one more thing you should know about passing an argument to a method…
2. Let's see if you understand all of this by putting it in the context of a complete program ...
*
Passing References as Arguments
public class PersonDriver
{
public static void main(String[] args)
{
Person person1 = new Person();
Person person2 = new Person();
person1.setName("Jonathan");
person2.setName("Benji");
System.out.println(person1.getName()
+ ", " + person2.getName());
person1.swapPerson(person2);
System.out.println(person1.getName()
+ ", " + person2.getName());
} // end main
} // end PersonDriver
1
2
3
4
*
1. This program demonstrates what happens when you use a reference variable as an argument, and you update the reference variable's instance variables.
2. Let's walk through the main method…
Two Person objects are instantiated (I'll show the Person class shortly).
We use the setName mutator method to assign Jonathan to the first person and Benji to the second person.
We then use the getName accessor method to print Jonathan and Benji.
3. The swapPerson method call attempts to swap the names in the two Person objects.
After the swap, we print the names for the two Person objects to see if the swap worked.
Assuming that the swap worked, what will the program output?
Benji, Jonathan
4. Before we look at the Person class, let's figure out the general-purpose swapping algorithm on the next slide…
*
Aside: Swapping algorithm
- Write a pseudocode fragment that swaps the contents of the x and y variables. More specifically, fill in the swap code below such that the output is "x=8, y=3".
x 3
y 8
<swap code goes here>
print "x=" + x + ", y=" + y
1
2
3
4
*
1. Having to swap two values is a very common thing to do in programming, so you should understand fully how this swapping algorithm works.
2. Will this work?
x y
y x
What would the output be?
x=8, y=8
3. The trick is to save the value of x before we wipe it out with y's value.
How do we save it?
4. I coax the students into writing this:
temp x
x y
y temp
*
Passing References as Arguments
public class Person
{
private String name;
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
public void swapPerson(Person otherPerson)
{
String temp;
temp = otherPerson.name;
otherPerson.name = this.name;
this.name = temp;
} // end swapPerson
} // end Person
1
2
*
1. Let's now walk through the Person class.
The setName and getName methods are standard mutator and accessor methods.
2. The swapPerson method receives an otherPerson parameter.
The goal is to swap otherPerson's name and the calling object's name.
Note how we access otherPerson's name with otherPerson.name and the calling object's name with this.name.
Note how we use a temp local variable as temporary storage for otherPerson.name.
*
Method-Call Chaining
- Up to this point, we've called methods one at a time. In an earlier example, we had a johnCar reference variable and we set its make and year like this:
johnCar.setMake("Toyota");
johnCar.setYear(2008);
- Let's now discuss how you can chain the two method calls together, like this:
johnCar.setMake("Toyota").setYear(2008);
- That's called method-call chaining. It's when you use a dot to concatenate a method call to the end of another method call.
1
*
1. Let's look at this in the context of a complete program….
*
Method-Call Chaining
public class Car3Driver
{
public static void main(String[] args)
{
Car3 car = new Car3();
car.setMake("Toyota").setYear(2008).printIt();
} // end main
} // end class Car3Driver
a method-call chain
1
2
*
1. Let's examine what happens in executing the method-call-chain code.
Left-to-right precedence applies, so car.setMake executes first.
The setMake method returns the calling object, which is the car object.
The returned car object is then used to call the setYear method.
The setYear method returns the calling object, which is the car object.
The returned car object is then used to call the printIt method.
2. In order for this method-call chaining behavior to work, we need to add some code to our previous setMake and setYear methods…
*
Method-Call Chaining
public class Car3
{
private String make;
private int year;
//*******************************************************
public Car3 setMake(String make)
{
this.make = make;
return this;
} // end setMake
public Car3 setYear(int year)
{
this.year = year;
return this;
} // end setYear
//*******************************************************
public void printIt()
{
System.out.println(make + ", " + year);
} // end printIt
} // end class Car2
1
2
The return type is the same as the class name.
Return the calling object.
*
1. I walk through the instance variable declarations and the printIt method.
2. In the setMake and setYear methods, note the green boxes.
Those are the things that are different from our previous setMake and setYear methods (in the Car program).
*
Method-Call Chaining
- In Car3's setMake and setYear methods, note how we enable method-call chaining. In each method definition:
- The last line in the method body returns the calling object:
return this;
- The method heading specifies the method's class name for the return type:
public Car3 setMake(String make);
- Method-call chaining is optional. So why bother with it?
1
2
3
*
1. What's the rationale for using the class name (Car3 in this example) for the return type?
Because we're returning this and this refers to the car calling object and car is defined as a Car3 reference variable.
2. On the Car3Driver class slide, I show the car calling object and car's declaration with Car3, and then return to this slide.
3. Method-call chaining often leads to more elegant code.
And why is that?
Because it's more compact and it can result in code that's easier to understand.
*
Overloaded Methods
- Suppose there's a need to perform the same sort of task on different sets of arguments. For example, suppose you want to find the average for these different sets of arguments:
- two integers
- three integers
- two doubles
- One solution is to write three methods with three different names. Here's how you might call those methods:
x = findAverageFor2Ints(20, 8);
y = findAverageFor3Ints(5, -3, 18);
z = findAverageFor2Doubles(1.2, 4.0);
- What's wrong with that solution?
1
*
1. The programmer has to remember and use three different names for the three different method calls.
Wouldn't it be easier to remember and use just one name, like findAverage?
Next slide….
*
Overloaded Methods
- The better solution is to use overloaded methods. That's where you have two or more methods with the same name and different parameters (different number of parameters or different types of parameters).
- For the find-the-average example, you could write three overloaded findAverage methods and call them like this:
x = findAverage(20, 8);
y = findAverage(5, -3, 18);
z = findAverage(1.2, 4.0);
2
1
3
*
[1. Note that the compiler distinguishes between overloaded methods by looking at the parameters, but not the return type.
Thus, if you define two methods with the same name, same arguments, and different return types, the compiler will think you've defined the exact same method twice.
That generates a "duplicate definition" compilation error.]
2. Aren't these three method calls better than the three on the previous slide?
It's more convenient to use just one name so you don't have to remember more than one name.
3. Let's put these concepts into practice in the context of a complete program….
*
Overloaded Methods
class Height
{
double height; // a person's height
String units; // unit of measurement (e.g., cm for centimeters)
public void setHeight(double height)
{
this.height = height;
this.units = "cm";
}
public void setHeight(double height, String units)
{
this.height = height;
this.units = units;
}
public void print()
{
System.out.println(this.height + " " + this.units);
}
} // end class Height
1
2
Note that the overloaded setHeight methods have different numbers of parameters.
*
1. Note the parameters in the two overloaded setHeight methods - they have different numbers of parameters.
2. Next slide….
*
Overloaded Methods
public class HeightDriver
{
public static void main(String[] args)
{
Height myHeight = new Height();
myHeight.setHeight(72.0, "in");
myHeight.print();
myHeight.setHeight(180.0);
myHeight.print();
} // end main
} // end class HeightDriver
- For each setHeight call, which method is called on the previous slide?
- What is the program's output?
1
2
3
*
1. The first setHeight call calls the second method definition because the 2 arguments in the method call (a double and a string) match the 2 parameter types in the second method's heading.
2. The second setHeight call calls the first method definition because the 1 argument in the method call (a double) matches the 1 parameter type in the first method's heading.
3. What is the program's output?
I write on the board:
72.0 in
180 cm
*
Overloaded Methods
- Suppose that you have overloaded methods and you're inside one of the methods. Note that it's OK to call one of the other overloaded methods.
- For example, you can replace the original one-parameter setHeight method with the following implementation, which calls the two-parameter setHeight method.
public void setHeight(double height)
{
setHeight(height, "cm");
}
No need for a reference variable dot prefix here.
1
2
*
1. The additional method call makes the program slightly less efficient, but some might consider it more elegant because it eliminates code redundancy.
In the original implementation, this.height = height; appears in both methods, and that's code redundancy; albeit trivial code redundancy.
2. Why is there no reference variable dot at the left of the setHeight method call?
Because if you're in an instance method (not main, which is a class method), if you call a method that's in the same class, the reference variable dot prefix is unnecessary.
And in this case, the two overloaded setHeight methods are indeed in the same class.
*
Constructors
- Up to this point, we have used mutators to assign values to the instance variables in newly instantiated objects. That works OK, but it requires having and calling one mutator for each instance variable.
- As an alternative, you could use a single method to initialize all of an object's instance variables as soon as possible after you create that object. For example, you could define a single initCar method to initialize Car objects and use it like this::
Car allexCar = new Car();
allexCar.initCar("Porsche", 2006, "beige");
- This code fragment uses one statement to allocate space for a new object, and it uses another statement to initialize that object's instance variables. Since the instantiation and initialization of an object is so common, wouldn't it be nice if there were a single statement that could handle both of these operations?
Car allexCar = new Car("Porsche", 2006, "beige");
1
2
*
1. This unifies the creation of an object and the initialization of its instance variables in just one call.
It guarantees that an object's instance variables are initialized as soon as the object is created.
2. Let's look at constructor details on the next slide….
*
Constructors
- A constructor lets you specify what happens to an object when it is instantiated with new.
- A constructor is called automatically when an object is instantiated.
- A constructor's name = the object's class name.
- Don't put a return type at the left of a constructor heading (because constructors never return anything).
1
2
3
*
1. The point here is that you don't need a separate statement for a constructor call.
You just instantiate an object with new and the constructor is called automatically for you.
2. Is a constructor's name lower or upper case?
The constructor's first letter should be uppercase since proper style dictates that a class's first letter should be uppercase.
3. Now let's see a constructor within the context of a complete program.…
*
Example Car Class with a Constructor
public class Car4Driver
{
public static void main(String[] args)
{
Car4 jobyCar = new Car4("Porsche", 2006, "beige");
Car4 primeCar = new Car4("Saturn", 2002, "red");
System.out.println(jobyCar.getMake());
} // end main
} // end class Car4Driver
constructor calls
1
2
*
1. This is the Car4Driver class.
The Car4 class is on the next slide.
2. In the main method, what happens when the new Car4 lines are executed?
Those are constructor calls.
The three arguments inside the parentheses are passed to the constructor.
Next slide….
*
Example Car Class with a Constructor
public class Car4
{
private String make; // car's make
private int year; // car's manufacturing year
private String color; // car's primary color
//****************************************************
public Car4(String m, int y, String c)
{
this.make = m;
this.year = y;
this.color = c;
} // end constructor
//****************************************************
public String getMake()
{
return this.make;
} // end getMake
} // end class Car4
constructor definition
Style requirement:
Put constructors above a class's methods.
1
2
3
*
1. I walk through the constructor code.
I read the green boxes.
2. I verify that the constructor follows the two syntax rules from 2 slides ago - constructor name, return type.
3. What does this program print?
I verbally trace the program …
Output:
Porsche
*
Constructors
- Any time you instantiate an object (with new), there must be a matching constructor. That is, the number and types of arguments in your constructor call must match the number and types of parameters in a defined constructor.
- Until recently, we've instantiated objects without any explicit constructor. So were those examples wrong?
- The Java compiler automatically provides an empty-bodied zero-parameter default constructor for a class if and only if the class contains no explicitly defined constructors.
- The Employee program on the next slide illustrates the use of Java's implicit zero-parameter default constructor.
1
2
*
1. I verify on the previous slides that main's Car4 constructor calls have three arguments that match up with the Car4 constructor definition heading.
2. No.
*
Will this program compile successfully?
Will the next slide’s program compile successfully?
import java.util.Scanner;
public class Employee
{
private String name;
public void readName()
{
Scanner stdIn = new Scanner(System.in);
System.out.print("Name: ");
this.name = stdIn.nextLine();
} // end readName
} // end class Employee
public class EmployeeDriver
{
public static void main(String[] args)
{
Employee emp = new Employee();
emp.readName();
} // end main
} // end class EmployeeDriver
1
2
*
1. I read the slide title.
2. Note how main's "new Employee()" calls a 0-parameter constructor.
There's no corresponding 0-parameter constructor shown – is that a problem?
No! – because there are no other constructors so the default 0-parameter constructor is automatically provided.
*
Will this program compile successfully?
import java.util.Scanner;
public class Employee2
{
private String name;
public Employee2(String n)
{
this.name = n;
} // end constructor
public void readName()
{
Scanner stdIn = new Scanner(System.in);
System.out.print("Name: ");
this.name = stdIn.nextLine();
} // end readName
} // end class Employee2
public class Employee2Driver
{
public static void main(String[] args)
{
Employee2 waiter = new Employee2(“Andrew");
Employee2 hostess = new Employee2();
hostess.readName();
} // end main
} // end class Employee2Driver
1
2
*
1. Will this second program compile OK?
Main's "new Employee2()" calls a 0-parameter constructor and there's no corresponding 0-parameter constructor shown - is that a problem?
Yes!
This time, there is another constructor so the default 0-parameter constructor is not provided.
That generates a compilation error.
2. What's the solution?
I coax students to write this on the board above the 1-parameter constructor:
public Employee2()
{ }
Proper style dictates putting curly braces with a space on one line.
*
Overloaded Constructors
- Constructor overloading occurs when there are two or more constructors with the same name and different parameters.
- To call an overloaded constructor from another overloaded constructor, use this syntax:
this(<arguments for target constructor>);
- A this(<arguments-for-target-constructor>) constructor call may appear only in a constructor definition, and it must appear as the very first statement in the constructor definition.
- See the example on the next slide.…
1
2
3
4
*
1. A constructor is really just a special form of a method, so overloading a constructor is the same as overloading a method.
2. Overloaded constructors are very common (more common than overloaded methods).
That's because you'll often want to be able to create objects with different amounts of initialization.
Sometimes you'll want to pass in initial values to the constructor.
At other times, you'll want to refrain from passing in initial values to the constructor, and rely on assigning values later on.
To do both of those things, you'll need overloaded constructors - one with parameters and one without paramaters.
We implemented overloaded constructors for the Employee class on the previous slide - one constructor with a name parameter and one dummy constructor with no parameters.
3. Suppose you have overloaded methods and you're inside one of the methods.
How can you call the other method?
Easy - just specify the method name and use appropriate arguments for the other method.
But suppose you have overloaded constructors and you're inside one of the constructors.
How can you call the other constructor?
Constructors are normally called without an explicit call statement - they're automatically called when you create an object with the new operator.
Thus, the Java designers came up with a special syntax for calling an overloaded constructor from within an overloaded constructor …
[Note - in C++, you cannot call an overloaded constructor from another overloaded constructor.]
4. That means you can't call a constructor from a regular method.
Also, it means you can only have 1 constructor call in a constructor definition (because only one call statement could be the "very first line in the constructor definition").
*
Overloaded Constructors
public class Fraction
{
private int numerator;
private int denominator;
private double quotient;
public Fraction(int n)
{
this(n, 1);
}
public Fraction(int n, int d)
{
this.numerator = n;
this.denominator = d;
this.quotient =
(double) this.numerator
/ this.denominator;
}
public void printIt()
{
System.out.println(
this.numerator +
" / " + this.denominator +
" = " + this.quotient;
} // end printIt
} // end Fraction class
public class FractionDriver
{
public static void main(String[] args)
{
Fraction a = new Fraction(3, 4);
Fraction b = new Fraction(3);
// Fraction c = new Fraction(); // error
a.printIt();
b.printIt();
} // end main
} // end class FractionDriver
1
2
3
4
5
6
3
*
1. This is a fraction class where we're storing a given fraction as 3 components - numerator, denominator, and quotient.
quotient is the floating-point result of dividing the numerator by the denominator.
See the 3 instance variable declarations for numerator, denominator, and quotient.
2. We've got overloaded constructors - the first one takes 1 parameter, the second one takes 2 parameters.
Normally, you'd instantiate a fraction object using the second constructor by passing in a numerator argument and a denominator argument.
3. Let's look at the 2-parameter constructor first -
a) Note the constructor call with the 3 and 4 arguments.
b) Note how the constructor assigns the passed-in parameters to the instance variables.
c) Note how the constructor divides the numerator by the denominator and puts the result in quotient.
4. For whole numbers like 3, we'd like to be able to create their Fraction objects by just using a 3, rather than needing a 3 for the numerator and a 1 for the denominator.
That's why I included the overloaded 1-parameter constructor.
5. Notice how the 1-parameter constructor has a hardcoded 1 for the denominator.
We pass the numerator n and the denominator 1 to the 2-parameter constructor by using this.
The 1-parameter constructor could have done the instance variable assignments, but it's better to not have redundant code.
So the preferred implementation is to have the 1-parameter constructor call the 2-parameter constructor and pass in a 1 as the default denominator value.
6. Note that the syntax for the constructor call does follow the previous slide's syntax.
*
Overloaded Constructors (hidden)
public class Fraction
{
private int numerator;
private int denominator;
private double quotient;
public Fraction(int n)
{
this(n, 1);
}
public Fraction(int n, int d)
{
this.numerator = n;
this.denominator = d;
this.quotient =
(double) this.numerator
/ this.denominator;
}
public void printIt()
{
System.out.println(
this.numerator +
" / " + this.denominator +
" = " + this.quotient;
} // end printIt
} // end Fraction class
public class FractionDriver
{
public static void main(String[] args)
{
Fraction a = new Fraction(3, 4);
Fraction b = new Fraction(3);
// Fraction c = new Fraction(); // error
a.printIt();
b.printIt();
} // end main
} // end class FractionDriver
1
3
4
2
*
1. Suppose you wanted to print a message from within the first constructor's body.
Where would I have to put the print statement?
You must put it below the this constructor call since the this constructor call must be the first statement in the constructor definition.
2. Why would the third line produce a compilation error if it weren't commented out?
Isn't a 0-parameter constructor always provided free of charge?
No - the default constructor is only provided if there are no other constructors.
Since there are other constructors, there's no 0-parameter constructor to handle that statement - thus, the compilation error.
3. What does this program output?
I write on the board:
3 / 4 = .75
3 / 1 = 3.0
[4. What happens if the denominator is 0?
Look at the second constructor's division stmt.
You are allowed to perform division by zero with floating point numbers (but not integer numbers).
I force floating point division by casting the numerator to a double.
After the cast, it's a mixed expression, so the denominator is then automatically promoted to a double.
With floating point division by zero, the result is positive or negative infinity.
When you print a positive or negative infinity value, it displays "Infinity" or "-Infinity."
Thus:
(new Fraction(5, 0)).printIt() 5 / 0 = Infinity
(new Fraction(-5, 0)).printIt() 5 / 0 = -Infinity
]
*
Chapter 7 - Quiz Questions
- A constructor is automatically called whenever
- a Boolean variable is used
- an object is instantiated with new
- a method calls another method
Overloaded methods are methods that:
have more parameters than the maximum number of parameters allowed
share memory space with tendril instance variables
have the same name and different parameters
- Java's garbage collector:
- manages the communications between the CPU and main memory
- searches for inaccessible objects and converts their space to "free space"
- prevents Java programs from connecting to unsafe Web sites
*
1. b
2. c
3. b
car
memory
1062
1062
2008