Skip to main content

Section 6.4 Methods

Now we come to one of the major differences between Java and Python. The Python class definition used the special methods for addition and comparison that have the effect of redefining how the standard operators behave: in Python, __add__ and __lt__ change the behavior of + and <, respectively. In Java there is no operator overloading. So we will have to write the method for addition a little differently.
A point of terminology: Python has both “functions” (def outside a class) and “methods” (def inside a class). Since Java requires all code to be inside classes, it only has “methods.” Those from a C++ background might refer to methods as “member functions.”
Before we dive into the add method, it’s important to understand how Java passes arguments to methods, as this is a common point of confusion for programmers coming from Python. The terminology is different, but the practical result for objects is effectively identical.
  • Java is strictly pass-by-value. For primitive types (like int), a copy of the value is passed. For object types (like our Fraction), a copy of the reference(Namely, the memory address) is passed.
  • Python is pass-by-assignment (or pass-by-object-reference). Since everything in Python is an object, the rule is consistent: a copy of the reference to the object is passed.
What does this mean in practice? For objects, the behavior is the same in both languages. When you pass a Fraction object to the add method, both the original variable outside the method and the parameter inside the method (otherFrac) refer to the exact same object in memory. This allows the method to use the object’s getters to read its state. If you were to call a setter method on otherFrac, the change would be reflected in the original object.
However, if you reassign the parameter to a completely new object inside the method (e.g., otherFrac = new Fraction(0,1);), it would not affect the original variable outside the method, because you are only changing the local copy of the reference.
Listing 6.4.1 shows the first part of the Fraction class definition.
Listing 6.4.1.
public Fraction add(Fraction otherFrac) { 
    Integer newNum = otherFrac.getDenominator() * this.numerator +
                             this.denominator * otherFrac.getNumerator(); // notice the use of this. 
    Integer newDen = this.denominator * otherFrac.getDenominator(); // find the new denominator
    Integer common = gcd(newNum, newDen); // find the greatest common divisor
    return new Fraction(newNum/common, newDen/common);
}
First you will notice that the add method is declared as public Fraction The public part means that any other method may call the add method. The Fraction part means that add will return a fraction as its result.
Second, you will notice that the method makes use of the this variable. In this method, this is not necessary, because there is no ambiguity about the numerator and denominator variables. Listing 6.4.2 is an equivalent version of Listing 6.4.1.
Listing 6.4.2.
public Fraction add(Fraction otherFrac) {
    Integer newNum = otherFrac.getDenominator() * numerator +
                             denominator * otherFrac.getNumerator(); // notice the absence of this. 
    Integer newDen = denominator * otherFrac.getDenominator();
    Integer common = gcd(newNum, newDen);
    return new Fraction(newNum/common, newDen/common);
}
The addition takes place by multiplying each numerator by the opposite denominator before adding. This procedure ensures that we are adding two fractions with common denominators. Using this approach the denominator is computed by multiplying the two denominators. The greatest common divisor method, gcd, is used to find a common divisor to simplify the numerator and denominator in the result.
Finally on line 6 a new Fraction is returned as the result of the computation. The value that is returned by the return statement must match the value that is specified as part of the declaration. So, in this case the return value on line 8 must match the declared value on line 1.

Subsection 6.4.1 Method Signatures and Overloading

Our specification for this project said that we need to be able to add a Fraction to an Integer. In Python we can do this by checking the type of the parameter using the isinstance function at runtime. Recall that isinstance(1,int) returns True to indicate that 1 is indeed an instance of the int class. See the __add__ and toFract methods in the Python version of the Fraction class to see how our Python implementation fulfills this requirement.
In Java we can do runtime type checking, but the compiler will not allow us to pass an Integer to the add method since the parameter has been declared to be a Fraction. The way that we solve this problem is by writing another add method with a different set of parameters. In Java this practice is legal and common we call this practice method overloading.
This idea of method overloading raises a very important difference between Python and Java. In Python a method is known by its name only. In Java a method is known by its signature. The signature of a method includes its name, and the types of all of its parameters. The name and the types of the parameters are enough information for the Java compiler to decide which method to call at runtime.
To solve the problem of adding an Integer and a Fraction in Java we will overload both the constructor and the add method. We will overload the constructor so that if it only receives a single Integer it will convert the Integer into a Fraction. We will also overload the add method so that if it receives an Integer as a parameter it will first construct a Fraction from that integer and then add the two Fractions together. Listing 6.4.3 shows the new methods that accomplish this task.
Listing 6.4.3.
public Fraction(Integer num) { 
    this.numerator = num; // set the numerator to the Integer
    this.denominator = 1;
}
public Fraction add(Integer other) { // overload the add method when the parameter is an Integer
    return add(new Fraction(other)); 
}
Notice that the overloading approach can provide us with a certain elegance to our code. Rather than utilizing if statements to check the types of parameters we just overload methods ahead of time which allows us to call the method we want and allow the compiler to make the decisions for us. This way of thinking about programming takes some practice.
Our full Fraction class to this point would look Listing 6.4.4. You should compile and run the program to see what happens.
Listing 6.4.4.
If you ran Listing 6.4.4, you probably noticed that the output is not very satisfying. Chances are your output looked something like Listing 6.4.5.
Listing 6.4.5.
Fraction@6ff3c5b5
The reason is that we have not yet provided a friendly string representation for our Fraction objects. Just like in Python, whenever an object is printed by the println method it must be converted to string format. In Python you can control how that looks by writing an __str__ method for your class. If you do not then you will get the default, which looks something like Listing 6.4.5. We will see how to provide a friendly string representation for our Fraction class in Section 6.5.

Checkpoint 6.4.6.

Rearrange the blocks to create a Printer class with two overloaded printData methods—one that accepts an int and another that accepts a String.
You have attempted of activities on this page.