Java 2

Week 2

 

Software Development Life Cycle, Steps 4-5

Fraction Class

4. Development (Coding)

  • Using IntelliJ, create a new Java project called "Calculators".
  • Select a preferred location on your device.
  • Select to create a Git repository
  • Select Maven as the build system.
  • Select or download JDK 17 or above.
  • Select to add sample code.
  • Toggle open the advanced settings.
  • Set the GroupId to edu.kirkwood
  • Leave the ArtifactId as "Calculators".

IntelliJ Project Setup

  • IntelliJ has built-in AI tools that you can agree to their terms. You can remove it from the sidebar if you don't plan to use it.
    • For assignments, I only want you to submit code that was taught in class. If you use AI (Junie (GPT-5), GitHub Copilot, Gemini Code Assist, etc.), please write comments next to untaught lines explaining their purpose.
  • I prefer to add files to Git manually. When this prompt appears, I select "Don't ask again", and click "Cancel".
  • Note that file names in the project panel may be red. This red text does not mean the file has an error. It means that the file is uncommitted with Git.

pom.xml

  • The pom.xml (Project Object Model) file is the core configuration file in a Maven project. It contains all the essential information and instructions needed to build, manage, and deploy a project.
  • We will use this file regularly in later lessons.
  • Close the pom.xml file.

Main.java

  • A Main.java file is automatically created.
  • To run code, press Shift + F10 or click one of the play icons.
  • Run debug mode by pressing Shift + F9 or clicking the bug icon.
  • When text is highlighted yellow, press Alt + Enter with your mouse at the highlighted text to see suggested fixes.
  • You may delete the Main.java file after you know that it works.

Fraction.java

  • Right-click the "edu.kirkwood" package and create a new package called "model".
  • Right-click the "model" package and create a new Java class called Fraction.java.
  • Create two private int instance variables, called numerator and denominator.
  • Right-click the file and choose "Generate". Add a default constructor that assigns 1 to the numerator and denominator.
  • Create a toString method that returns a string representation of the fraction in the format "numerator/denominator"
public class Fraction {
    private int numerator;
    private int denominator;

    public Fraction() {
        numerator = 1;
        denominator = 1;
    }

    public String toString() {
        return numerator + "/" + denominator;
    }
}

Fraction.java

  • Generate a parameterized constructor that has two int parameters, called numerator and denominator. Assign both parameters to the instance variables.

  • Generate a getNumerator and getDenominator method.
  • Generate a setNumerator and setDenominator method.
public class Fraction {
    // code omitted

    public Fraction(int numerator, int denominator) {
        setNumerator(numerator);
        setDenominator(denominator);
    }

    public int getNumerator() {
        return numerator;
    }

    public void setNumerator(int numerator) {
        this.numerator = numerator;
    }

    public int getDenominator() {
        return denominator;
    }

    public void setDenominator(int denominator) {
        this.denominator = denominator;
    }


}

Fraction.java

package edu.kirkwood.model;

public class Fraction implements Comparable<Fraction> {
    // Code omitted

    @Override
    public int compareTo(Fraction o) {
        return 0;
    }

    public static int gcd(int a, int b) {
        return 0;
    }

    public static int lcm(int a, int b) {
        return 0;
    }

    public void simplify() {

    }

    public String toMixedNumber() {
        return "";
    }

    public Fraction add(Fraction other) {
        return null;
    }

    public Fraction subtract(Fraction other) {
        return null;
    }

    public Fraction multiply(Fraction other) {
        return null;
    }

    public Fraction divide(Fraction other) {
        return null;
    }
}

Static vs Non-Static Methods

  • Static methods are called like this:

    • Fraction.gcd(15, 6); // 3

    • Fraction.lcm(15, 6); // 30

    • lcm(15, 6) // "Fraction" can be omitted if called inside the Fraction class.

  • These methods are static because they are not related to a single Fraction object. Input parameters a and b do not refer to numerator and denominator
  • These methods could go in the Helpers class if you think you will use it for other applications.

Static vs Non-Static Methods

  • Non-static methods are called like this

    • Fraction f1 = new Fraction(15, 6);

    • f1.simplify(); // 5/2

    • f1.toMixedNumber(); // 2 1/2

    • f1.getNumerator(); // 5

    • f1.getDenominator(); // 2

    • Fraction f2 = new Fraction(1, 3);

    • f1.add(f2); // 2 5/6

Javadoc Comments

  • Add Javadoc comments to the class and each method. 
  • For your next assignment, I would appreciate if you hand-typed at least two Javadoc comments. You can then use AI to generate the remainder—but be sure to proofread them.
package edu.kirkwood.model;

/**
 * Represents a fraction with an integer numerator and denominator.
 * This class provides methods for fraction arithmetic, simplification,
 * and comparison.
 */
public class Fraction implements Comparable<Fraction> {
    private int numerator;
    private int denominator;

    /**
     * Default constructor.
     * Initializes a new fraction to 1/1.
     */
    public Fraction() {
        this.numerator = 1;
        this.denominator = 1;
    }

    /**
     * Constructs a fraction with a specified numerator and denominator.
     *
     * @param numerator   the numerator of the fraction
     * @param denominator the denominator of the fraction
     */
    public Fraction(int numerator, int denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
    }

    /**
     * Gets the numerator of the fraction.
     *
     * @return the numerator
     */
    public int getNumerator() {
        return numerator;
    }

    /**
     * Sets the numerator of the fraction.
     *
     * @param numerator the new numerator
     */
    public void setNumerator(int numerator) {
        this.numerator = numerator;
    }

    /**
     * Gets the denominator of the fraction.
     *
     * @return the denominator
     */
    public int getDenominator() {
        return denominator;
    }

    /**
     * Sets the denominator of the fraction.
     *
     * @param denominator the new denominator
     * @throws ArithmeticException if the denominator is zero
     */
    public void setDenominator(int denominator) {
        this.denominator = denominator;
    }

    /**
     * Returns a string representation of the fraction in the format "numerator/denominator".
     *
     * @return a string representation of the fraction
     */
    @Override
    public String toString() {
    	// Implementation needed
        return "";
    }

    /**
     * Compares this fraction to another fraction.
     *
     * @param o the other Fraction to be compared.
     * @return a negative integer, zero, or a positive integer.
     */
    @Override
    public int compareTo(Fraction o) {
        // Implementation needed
        return 0;
    }

    /**
     * Calculates the greatest common divisor (GCD) of two integers.
     *
     * @param a the first integer
     * @param b the second integer
     * @return the greatest common divisor of a and b
     */
    public static int gcd(int a, int b) {
        // Implementation needed
        return 0;
    }

    /**
     * Calculates the least common multiple (LCM) of two integers.
     *
     * @param a the first integer
     * @param b the second integer
     * @return the least common multiple of a and b
     */
    public static int lcm(int a, int b) {
        // Implementation needed
        return 0;
    }

    /**
     * Simplifies this fraction to its lowest terms by dividing the numerator
     * and denominator by their greatest common divisor.
     */
    public void simplify() {
        // Implementation needed
    }

    /**
     * Converts this fraction to a mixed number string representation (e.g., "1 2/3").
     * If the fraction is a proper fraction, it returns the fraction itself.
     *
     * @return a string representation of the fraction as a mixed number
     */
    public String toMixedNumber() {
        // Implementation needed
        return "";
    }

    /**
     * Adds another fraction to this fraction.
     *
     * @param other the fraction to add
     * @return a new Fraction object representing the sum
     */
    public Fraction add(Fraction other) {
        // Implementation needed
        return null;
    }

    /**
     * Subtracts another fraction from this fraction.
     *
     * @param other the fraction to subtract
     * @return a new Fraction object representing the difference
     */
    public Fraction subtract(Fraction other) {
        // Implementation needed
        return null;
    }

    /**
     * Multiplies this fraction by another fraction.
     *
     * @param other the fraction to multiply by
     * @return a new Fraction object representing the product
     */
    public Fraction multiply(Fraction other) {
        // Implementation needed
        return null;
    }

    /**
     * Divides this fraction by another fraction.
     *
     * @param other the fraction to divide by (the divisor)
     * @return a new Fraction object representing the quotient
     * @throws IllegalArgumentException if the divisor is zero
     */
    public Fraction divide(Fraction other) {
        // Implementation needed
        return null;
    }
}

Javadoc Comments

  • Click the IntelliJ menu icon, select the Tools menu, and select "Generate Javadoc..."
  • Set the Output directory to be a "docs" folder inside the project's root folder.
  • Click the blue "Generate" button.
  • An HTML file will open in your default browser. Click the "Fraction" class link to view the documentation.
  • When we push our code to GitHub, we will display the contents of the docs folder as a live webpage.

5. Testing

  • In IntelliJ, right-click the Fraction class title and choose "Show Context Options" then "Create Test".

  • JUnit 5 will be pre-selected, click the "Fix" button if shown.

  • Check the "setUp/@Before" box.

  • Click the check boxes next to all of the methods.

    • Click one method name, press Ctrl + A, press the spacebar

  • The FractionTest class is successfully added to the tests folder.

  • Fully-qualified annotations ("@org.junit.jupiter.api.BeforeEach" and "@org.junit.jupiter.api.Test") only need to be @BeforeEach and @Test.

    • If your annotations are fully-qualified press Ctrl + R to open the find and replace menu.

    • Replace all references of "@org.junit.jupiter.api." with "@".

    • Add an import statement for @BeforeEach and @Test.

BeforeEach Methods

  • Some Unit Tests require a default instance of the class.

  • The setUp method can be used to instantiate that object needed for each test.

  • Create two new Fraction objects as a private instance variables.

  • Instantiate the Fraction objects in the setUp method.

  • We want to keep our code DRY (Don't Repeat Yourself). If we don't use the setUp method we will have to instantiate a Fraction object inside of every single test method.

private Fraction f1;
private Fraction f2;

@BeforeEach
void setUp() {
    f1 = new Fraction();
    f2 = new Fraction(2, 3);
}

Run the tests to fail

  • Study the fail method from JUnit's Assertions class.

  • If you run the tests now, it should say all tests passed and you will see green checkmarks next to the method names in the bottom-left corner. We actually want all tests to fail by default.

  • Highlight a set of curly brackets on one of the tests.

    • Press Ctrl+Cmd+G (Mac) or Shift+Ctrl+Alt+J (Windows) to select all occurrences.

    • Use the arrow keys to position the cursors inside the curly brackets and type a fail method.
      fail();

      • This is a static method from the Assertions class of JUnit.

  • Run the FractionTest class again to see that all tests failed. You will see orange X's next to the method names.

Test the Getters and toString

  • Study the assertEquals methods from JUnit's Assertions class.

  • Write tests for all of the getters and toString.

    • You should test all getter methods before setter methods so you can safely use the getter methods to verify that the setter methods work correctly.

    • When using the assertEquals method the first argument is the expected value and the second argument is the actual value.

    • The actual value will always come from a getter method.

@Test
void getNumerator() {
    assertEquals(1, f1.getNumerator());
    assertEquals(2, f2.getNumerator());
}

@Test
void getDenominator() {
    assertEquals(1, f1.getDenominator());
    assertEquals(3, f2.getDenominator());
}

@Test
void testToString() {
    assertEquals("1/1", f1.toString());
    assertEquals("2/3", f2.toString());
}

Test the Setters

  • Next, we will write tests for all of the setters

  • For setNumerator, I am setting a positive, 0, and negative value. I am calling the getNumerator and toString methods to ensure equality.

  • For setDenominator, I am setting a positive, 0, and negative value. I am calling the getDenominator and toString methods to ensure equality.

  • An optional third parameter can be used for a message.

@Test
void setNumerator() {
    f1.setNumerator(3);
    assertEquals(3, f1.getNumerator());
    assertEquals("3/1", f1.toString());
    f1.setNumerator(0);
    assertEquals(0, f1.getNumerator());
    assertEquals("0/1", f1.toString());
    f1.setNumerator(-3);
    assertEquals(-3, f1.getNumerator());
    assertEquals("-3/1", f1.toString());
}

@Test
void setDenominator() {
    f1.setDenominator(3);
    assertEquals(3, f1.getDenominator());
    assertEquals("1/3", f1.toString());

    f1.setDenominator(-3);
    assertEquals(3, f1.getDenominator(), "Setting a negative denominator should change the numerator");
    assertEquals(-1, f1.getNumerator());
    assertEquals("-1/3", f1.toString(), "1/-3 should be -1/3");
    f1.setDenominator(-3);
    assertEquals("1/3", f1.toString(), "-1/-3 should be 1/3");
}

Test Exceptions

  • Because the denominator cannot be 0, we use assertThrows.

  • The first argument is the type of Exception you expect to be thrown, in this case ArithmeticException.

  • The second argument is a lambda expression that calls the abstract method from the Executable functional interface.

  • assertThrows returns a Throwable object of type T. In this case, a reasonable Throwable object to use is ArithmeticException. You can assert the Exception message.

@Test
void setDenominator() {
    f1.setDenominator(3);
    assertEquals(3, f1.getDenominator());
    assertEquals("1/3", f1.toString());
    
    assertThrows(ArithmeticException.class, () -> f1.setDenominator(0));

	ArithmeticException e = assertThrows(ArithmeticException.class, () -> f1.setDenominator(0));
	assertEquals("Denominator cannot be zero", e.getMessage());
    
    f1.setDenominator(-3);
    assertEquals(3, f1.getDenominator());
    assertEquals("-1/3", f1.toString()); // 1/-3 should be -1/3
    f1.setDenominator(-3);
    assertEquals("1/3", f1.toString()); // -1/-3 should be 1/3
}

2026

  • Add quiz question about Functional Interfaces and Lambda Expressions
  • https://docs.junit.org/5.0.1/api/org/junit/jupiter/api/Assertions.html
  • https://docs.junit.org/5.0.1/api/org/junit/jupiter/api/function/Executable.html

Write Code to Pass Tests

  • After writing the unit tests, go back to the regular class and implement the code that will get the test to pass.

public void setDenominator(int denominator) {
    if(denominator == 0){
        throw new ArithmeticException("Denominator cannot be zero");
    }
    if(denominator < 0 && numerator > 0 || denominator < 0 && numerator < 0) {
        numerator *= -1;
        denominator *= -1;
    }
    this.denominator = denominator;
}

Update Parameterized Constructor

  • Update the parameterized constructor to call the setter methods to validate the input, rather than potentially assigning invalid values to the attributes.

public Fraction(int numerator, int denominator) {
    setNumerator(numerator);
    setDenominator(denominator);
}

Test Static Methods

  • In the gcd test method, call the static gcd method from the Fraction class.
  • The following assertions seem logical, but they don't fully test it.

@Test
void gcd() {
	assertEquals(15, Fraction.gcd(75, 45));
    assertEquals(2, Fraction.gcd(2, 4));
    assertEquals(1, Fraction.gcd(5, 7));
    int result1 = Fraction.gcd(5, 7);
    int result2 = Fraction.gcd(-5, 7);
    int result3 = Fraction.gcd(5, -7);
    int result4 = Fraction.gcd(-5, -7);
    assertTrue(result1 == result2 && result2 == result3 && result3 == result4);
}
@Test
void gcd() {
    assertEquals(15, Fraction.gcd(75, 45));
    assertEquals(2, Fraction.gcd(2, 4));
    assertEquals(1, Fraction.gcd(5, 7));
}

Write Code to Pass Tests

  • The greatest common divisor of two numbers is the largest positive integer number that divides both the numbers without leaving any remainder. For example, the greatest common divisor of 30 and 45 is 15
  • We will use this solution.

  • If you run the unit test now, it will fail.

  • Update the Fraction class gcd method to return Math.abs(a).

public static int gcd(int a, int b) {
    if (b == 0) {
        return Math.abs(a);
    }
    return gcd(b,a % b);
}

Test Static Methods

  • To test the lcm method, we should consider several cases:

    • Two standard positive integers.

    • Two prime numbers.

    • Cases where one number is a multiple of the other.

    • Cases involving the number 1.

    • Edge cases, such as involving the number 0.

  • We can break each case into a separate @Test method.

  • We can add a @DisplayName annotation to explain its purpose.

  • The fundamental rule of unit testing is "one test tests one thing." In the previous examples, if the first assertEquals fails, the second test never runs, potentially hiding another bug.

Test Static Methods

@Test
@DisplayName("Test LCM with two positive integers")
void testLcmWithPositiveIntegers() {
    assertEquals(24, Fraction.lcm(6, 8));
}

@Test
@DisplayName("Test LCM where one number is a multiple of the other")
void testLcmWithMultiple() {
    assertEquals(12, Fraction.lcm(4, 12));
}

@Test
@DisplayName("Test LCM with two prime numbers")
void testLcmWithPrimes() {
    // The lcm of two prime numbers is their product.
    assertEquals(77, Fraction.lcm(7, 11));
}

@Test
@DisplayName("Test LCM with the number 1")
void testLcmWithOne() {
    assertEquals(9, Fraction.lcm(1, 9));
    assertEquals(9, Fraction.lcm(9, 1));
}

@Test
@DisplayName("Test LCM with identical numbers")
void testLcmWithIdenticalNumbers() {
    assertEquals(5, Fraction.lcm(5, 5));
}

@Test
@DisplayName("Test LCM where one of the inputs is zero")
void testLcmWithZero() {
    assertEquals(0, Fraction.lcm(10, 0));
    assertEquals(0, Fraction.lcm(0, 10));
    assertEquals(0, Fraction.lcm(0, 0));
}

Write Code to Pass Tests

  • We will use this solution.

  • The best approach is reduction by the GCD.

  • Add a simple if statement to get the test where one of the inputs is zero to pass.

    • ​Without it, the test will encounter an ArithmeticException because it cannot divide by 0.

public static int lcm(int a, int b) {
    if (a == 0 || b == 0) {
        return 0;
    }
    return a * (b / gcd(a, b));
}

Test simplify Method

  • Set the numerator to 75 and denominator to 45.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "5/3".
  • Set the numerator to 2 and denominator to 4.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "1/2".
  • Set the numerator to 5 and denominator to 7.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "5/7".
@Test
void simplify() {
    f1.setNumerator(75);
    f1.setDenominator(45);
    f1.simplify();
    assertEquals("5/3",f1.toString());

    f1.setNumerator(2);
    f1.setDenominator(4);
    f1.simplify();
    assertEquals("1/2",f1.toString());

    f1.setNumerator(5);
    f1.setDenominator(7);
    f1.simplify();
    assertEquals("5/7",f1.toString());
}

Test simplify Method

  • Set the numerator to -2 and denominator to 4.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "-1/2".
  • Set the numerator to 2 and denominator to -4.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "-1/2".
  • Set the numerator to -2 and denominator to -4.
    • Call the simplify method to return a new Fraction object.
    • The value returned should be "1/2".
@Test
void simplify() {
    f1.setNumerator(75);
    f1.setDenominator(45);
    f1.simplify();
    assertEquals("5/3",f1.toString());

    f1.setNumerator(2);
    f1.setDenominator(4);
    f1.simplify();
    assertEquals("1/2",f1.toString());

    f1.setNumerator(5);
    f1.setDenominator(7);
    f1.simplify();
    assertEquals("5/7",f1.toString());

    f1.setNumerator(-2);
    f1.setDenominator(4);
    f1.simplify();
    assertEquals("-1/2",f1.toString());

    f1.setNumerator(2);
    f1.setDenominator(-4);
    f1.simplify();
    assertEquals("-1/2",f1.toString());

    f1.setNumerator(-2);
    f1.setDenominator(-4);
    f1.simplify();
    assertEquals("1/2",f1.toString());
}

Write Code to Pass Tests

  • Find the greatest common divisor of the Fraction object's numerator and denominator.

  • Update the numerator to be the current numerator divided by the greatest common divisor

  • Update the denominator to be the current denominator divided by the greatest common divisor.

public void simplify() {
    int gcd = gcd(numerator, denominator);
    numerator /= gcd;
    denominator /= gcd;
}

Test toMixedNumber Method

  • Below is a set of assertions to test the toMixedNumber method.

@Test
void toMixedNumber() {
    assertEquals("1", f1.toMixedNumber(), "1/1 should be 1");
    assertEquals("2/3", f2.toMixedNumber(), "2/3 should be 2/3");
    f2.setNumerator(0);
    assertEquals("0", f2.toMixedNumber(), "0/3 should be 0");
    f1.setNumerator(7);
    f1.setDenominator(4);
    assertEquals("1 3/4", f1.toMixedNumber(), "7/4 should be 1 3/4");
    f1.setNumerator(-7);
    assertEquals("-1 3/4", f1.toMixedNumber(), "-7/4 should be -1 3/4");
}

Test toMixedNumber Method

  • Inside this method, call the simplify method.
  • If the simplified fraction's denominator is 1, return the simplified fraction's numerator, converted to a String.
    • For example, if the Fraction is 2/1, return "2".
  • Otherwise, return the simplified fraction as a String by calling the toString() method.
public String toMixedNumber() {
    simplify();
    if(denominator == 1) {
        return numerator + "";
    } else {
        return toString();
    }
}

Test toMixedNumber Method

  • If the simplified fraction's numerator is 0, return "0".
    • For example, if the Fraction is 0/1, return "0" 
  • If the simplified fraction's numerator is greater than the denominator, return the fraction as a mixed number.
    1. For example, 13/5 is 2 3/5.
      1. 13 / 5 is 2 (the whole number)
      2. 13 % 5 is 3 (the remainder)
  • The ternary operator is used If the simplified fraction's numerator is less than 0, return the fraction as a negative mixed number. 
public String toMixedNumber() {
    simplify();
    if(denominator == 1) {
        return numerator + "";
    } else if(Math.abs(numerator) > denominator) {
        int wholeNumber = Math.abs(numerator) / denominator;
        int remainder = Math.abs(numerator) % denominator;
        return (numerator < 0 ? "-" : "") + wholeNumber + " " + remainder + "/" + denominator;
    } else if(numerator == 0) {
        return "0";
    } else {
        return toString();
    }
}

Test add Method

  • The fundamental rule of unit testing is "one test tests one thing."
  • Each test should be completely independent and set up its own objects.
  •  Explicitly check if the result is simplified. For example, adding 1/4 + 1/4 should result in 1/2, not 2/8.
@Test
@DisplayName("Test 1/1 + 2/3 = 5/3")
void addWholeNumberToFraction() {
    Fraction result = f1.add(f2);
    assertEquals(5, result.getNumerator());
    assertEquals(3, result.getDenominator());
}

@Test
@DisplayName("Test -1/4 + 2/3 = 5/12")
void addNegativeFractionToPositive() {
    f1 = new Fraction(25, -100); // Represents -1/4
    f2 = new Fraction(-10, -15); // Represents 2/3
    Fraction result = f1.add(f2);
    assertEquals(5, result.getNumerator());
    assertEquals(12, result.getDenominator());
}

@Test
@DisplayName("Test 1/4 + 1/4 = 1/2")
void addFractionsThatNeedSimplification() {
    f1 = new Fraction(1, 4);
    f2 = new Fraction(1, 4);
    Fraction result = f1.add(f2);
    assertEquals(1, result.getNumerator());
    assertEquals(2, result.getDenominator());
}

Write Code to Pass Tests

  • Calculate the new numerator and denominator using the cross-multiplication formula.
  • the numerator is (a*d + c*b) and the denominator is (b*d).
    • a is this Fraction's numerator, b is this Fraction's denominator, c is the other Fraction's numerator, d is the other Fraction's denominator.
  • Create a new fraction with the result of the addition.
  • Simplify the new fraction before returning it.
public Fraction add(Fraction other) {
    int newNumerator = this.numerator * other.denominator + this.denominator * other.numerator;
    int newDenominator = this.denominator * other.denominator;
    Fraction result = new Fraction(newNumerator, newDenominator);
    result.simplify();
    return result;
}
flowchart TD
    A@{ shape: circle, label: "Start" } --> B@{ shape: lean-r, label: "Input Fraction 1 (n1/d1)<br>Input Fraction 2 (n2/d2)" };
    B --> C@{ shape: rect, label: "Set new numerator by cross multiplying<br>(n1 * d2 + d1 * n2)" };
    C --> D@{ shape: rect, label: "Set new denominator<br>(d1 * d2)" };
    D --> E@{ shape: rect, label: "Display result as a simplified mixed number" };
    E --> F@{ shape: circle, label: "End" };

Test compareTo Method

  • If object 1 is greater than object 2, the compareTo method will return a positive number.
  • If object 1 is less than object 2, the compareTo method will return a negative number.
  • If object 1 is equal to object 2, the compareTo method will return zero.
@Test
@DisplayName("Test 1/1 is greater than 2/3")
void compareToGreaterThan() {
    assertTrue(f1.compareTo(f2) > 0);
    assertTrue(f2.compareTo(f1) < 0);
}

@Test
@DisplayName("Test 2/3 and 4/6 are equal")
void compareToEquals() {
    f1 = new Fraction(4, 6);
    assertEquals(0, f1.compareTo(f2));
    assertEquals(0, f1.compareTo(f1), "A fraction compared to itself should be equal");
}

@Test
@DisplayName("Test 1/3 is less than 2/3")
void compareToLessThan() {
    f1 = new Fraction(1, 3);
    assertTrue(f1.compareTo(f2) < 0);
    assertTrue(f2.compareTo(f1) > 0);
}

@Test
@DisplayName("Test comparison with negative fractions (-1/2 vs 1/4)")
void compareToWithNegativeFractions() {
    Fraction f1 = new Fraction(-1, 2);
    Fraction f2 = new Fraction(1, 4);
    Fraction f3 = new Fraction(-3, 4);
    assertTrue(f1.compareTo(f2) < 0, "-1/2 is less than 1/4");
    assertTrue(f2.compareTo(f1) > 0, "1/4 is greater than -1/2");
    assertTrue(f3.compareTo(f1) < 0, "-3/4 is less than -1/2");
}

Write Code to Pass Tests

  • The most reliable way to compare two fractions (n1/d1 and n2/d2) is to use cross-multiplication. 
  • The products of the numerators and denominators could potentially exceed the maximum value of an int. By casting to a long before multiplication, we prevent this integer overflow.
  • The comparison n1/d1 vs n2/d2 is equivalent to comparing the integer results of n1 * d2 vs n2 * d1.
  •  Using Long.compare(a, b) is the standard way to compare two long values. It simply returns -1, 0, or 1 based on the comparison.
@Override
public int compareTo(Fraction o) {
    long thisNumerator = (long)this.numerator;
    long thisDenominator = (long)this.denominator;
    long otherNumerator = (long)o.numerator;
    long otherDenominator = (long)o.denominator;
    
    long a = thisNumerator * otherDenominator;
    long b = otherNumerator * thisDenominator;
    
    return Long.compare(a, b);
}

In-Class Group Activity

  • In groups of 2 to 3 students, write unit tests and implement the methods.
    • toMixedNumberString() :: String
    • subtract(Fraction other) :: Fraction
    • multiply(Fraction other) :: Fraction
    • divide(Fraction other) :: Fraction

Java 2 - Week 2

By Marc Hauschildt

Java 2 - Week 2

  • 199