我该如何否定我创建的Java Fraction类?

3
这是我的分数类代码,包含多个方法。我的要求是将分子和分母保留为int类型:
/**
 * @author GKsiazek
 * Reference: https://github.com/kiprobinson/BigFraction/blob/master/com/github/kiprobinson/util/BigFraction.java
 * Reference: https://github.com/kiprobinson/BigFraction/blob/master/com/github/kiprobinson/junit/BigFractionTest.java
 */
package Fraction;
import java.math.*;
public class Fraction {

    private int numerator;
    private int denominator;


    /**
     * Constructor with two int parameters
     * @param num is numerator
     * @param den is denominator
     */
    public Fraction()
    {}

    public Fraction(int num, int den) {
        if (den==0){//denominator cannot be zero
            System.out.println("Denominator cannot be zero");
            return;
            }
        this.numerator = num;
        this.denominator = den;
        this.normalize();
    }

    /**
     * Constructor with one int parameter
     * @param num is numerator
     * @param den is assumed one
     */

    public Fraction(int num){
        this.numerator = 1;
        this.denominator = num ;
    }

    /**
     * Constructor with String parameter
     * @param str  
     * Only String in a following format "numerator/denominator" allowed
     * If the String consists of one int it is considered denominator
     * Numerator will be considered 1
     * Denominator cannot be zero
     */

    public Fraction(String str)
    {
        if(str.isEmpty())
        {
            System.out.println("The str (String) parameter cannot be empty!");
        return;
        }

             String[] data = str.split("/");
             if(data.length==0)
                 System.out.println("The str (String) parameter cannot be empty");
             try
             {
                 this.numerator = Integer.parseInt(data[0]); 
             }
             catch (Exception ex)
             {
                 System.out.println(ex.toString());
             }
             try
             {

                 this.denominator = Integer.parseInt(data[1]);
                 if(this.denominator==0) throw new Exception("Denominator can't be 0");
             }
             catch (Exception ex)
             {
                 System.out.println(ex.toString());
             }
             this.normalize();
    }



    /**
     * the method is applied within the constructors
     * normalize method takes care of fraction normalization
     * 1.Converts the numerator and denominator into BigInteger 
     * 2.Finds the GCD of both
     * 3.if the GCD is larger than 1 it divides numerator and denominator by GCD
     * @param numerator
     * @param denominator
     */
    private void normalize()//int numerator, int denominator)
    {
        BigInteger gcd;
        BigInteger num = BigInteger.valueOf(this.numerator);
        BigInteger den = BigInteger.valueOf(this.denominator);
        gcd = num.gcd(den);
        if (gcd.intValue() > 1)
        {
            this.numerator = numerator / gcd.intValue();
            this.denominator = denominator / gcd.intValue();
        }
    }
    public Fraction abs() {
        return null;
    }
    public int getNumerator()
    {
        return this.numerator;
    }

    public int getDenominator()
    {
        return this.denominator;
    }
    /*
     * a/b + c/d is (ad + bc)/bd
     */
    public Fraction add(Fraction g)
    {

        int numerator = this.numerator * g.denominator + this.denominator * g.numerator;
        int denominator = this.denominator * g.denominator;

        return new Fraction(numerator,denominator);
    }
    /**
     * subtract method
     * a/b - c/d is (ad - bc)/bd
     * calls the normalize method to make sure that the Fraction h
     * is in the normalized  form
     */

    public Fraction substract (Fraction g)
    {

        int num = this.numerator * g.denominator - this.denominator * g.numerator;
        int den = this.denominator*g.denominator;
        return new Fraction(num,den);
    }
    /**
     * equals method
     * public boolean equals(Object o)
     */
    public boolean equals (Object o){
       if(o == null)return false;
        return o.equals(this);

    }
    /**
     * Multiplication
     * (a/b) * (c/d) is (a*c)/(b*d)
     * @param g
     * @return
     */
    public Fraction multiply(Fraction g)
    {
        int num = this.numerator * g.numerator;
        int den = this.denominator * g.denominator;
        return new Fraction(num,den);
    }
    /**
     * Division
     * (a/b) / (c/d) is (a*d)/(b*c)
     * @param g
     * @return
     */
    public Fraction divide(Fraction g)
    {
        int num = this.numerator * g.denominator;
        int den = this.denominator * g.numerator;
        return new Fraction(num,den);

    }
    /**
     * Negation
     * -(a/b) is -a/b
     */
    public Fraction negate()
    {
        int num = Math.abs(this.numerator) * -1;
        int den = this.denominator;
        return new Fraction(num,den);
    }
    /**
     * Inverse of a/b is b/a
     */
    public Fraction inverse()
    {
        int num = this.denominator;
        int den = this.numerator;
        return new Fraction(num,den);
    }
    /**
     * a/b > c/d if ad > bc
     * @return
     */
    public boolean greaterThan(Fraction g)
    {
        if(this.numerator * g.denominator > this.denominator * g.numerator)
        {
            return true;//use the subtract() but how to
        }
        else return false;
    }

    /**
     * lessThan method
     * a/b < c/d if c/d > a/b
     * @param g
     * @return
     */
    public boolean lessThan(Fraction g)
    {
        if (this.greaterThan(g)==false)
        {
            return true;
        }
        else return false;
    }
    @Override
    public String toString()
    {
        return this.getNumerator()+"/"+this.getDenominator();
    }
}

这是我的测试类,除了 negate 以外所有测试都成功了。我尝试过几种方法,比如只是乘以 -1 或者用 - 直接取反,但都没有成功。

package Fraction;
import static org.junit.Assert.*;

import java.lang.reflect.Method;

import org.junit.Test;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class FractionsTest {

    /**
     * test methods checking the constructors 
     */
    @Test
    public void testFractionNum_Den() 
    {
        Fraction f = new Fraction(2,6);
        assertEquals(1, f.getNumerator());
        assertEquals(3, f.getDenominator());
    }
    @Test
    public void testFractionNum() 
    {
        Fraction f = new Fraction(5);
        assertEquals(1, f.getNumerator());
        assertEquals(5, f.getDenominator());
    }
    @Test
    public void testFractionStr()
    {
        Fraction f = new Fraction("1/5");
        assertEquals(1, f.getNumerator());
        assertEquals(5, f.getDenominator());

    }
    @Test
    public void testNormalize()
    {
        Fraction f = new Fraction(2,4);
        assertEquals(1, f.getNumerator());
        assertEquals(2,f.getDenominator());
    }
    /**
     * Method m=Dummy.class.getDeclaredMethod("foo");
     * m.setAccessible(true);//Abracadabra 
        m.invoke(d);
     */
    @Test
    public void testAdd()
    {
        Fraction g = new Fraction(1,3);
        Fraction toTest = g.add(new Fraction(1,3));
        assertEquals(2, toTest.getNumerator());
        assertEquals(3, toTest.getDenominator());
    }
    @Test
    public void testSubtract()
    {
        Fraction g = new Fraction (4,6);
        Fraction toTest = g.substract(new Fraction(2,6));
        assertEquals(1, toTest.getNumerator());
        assertEquals(3, toTest.getDenominator());
    }
    @Test
    public void testMultiply()
    {
        Fraction g = new Fraction (2,3);
        Fraction toTest = g.multiply(new Fraction(1,2));
        assertEquals(1, toTest.getNumerator());
        assertEquals(3, toTest.getDenominator());
    }
    @Test
    public void testDivide()
    {
        Fraction g = new Fraction (2,3);
        Fraction toTest = g.divide(new Fraction(1,3));
        assertEquals(2, toTest.getNumerator());
        assertEquals(1, toTest.getDenominator());
    }
    @Test
    public void testNegate()
    {
        Fraction g = new Fraction(1,3);
        g.negate();
        assertEquals(-1, g.getNumerator());
        assertEquals(3, g.getDenominator());
    }
    @Test
    public void testgreaterThan()
    {
        Fraction g = new Fraction(1,3);
        assertEquals(false, g.greaterThan(new Fraction(2,3)));
        assertEquals(true, g.greaterThan(new Fraction(1,5)));
    }
    @Test
    public void testlessThan()
    {
        Fraction g = new Fraction(2,3);
        assertEquals(false, g.lessThan(new Fraction(1,3)));
        assertEquals(true, g.lessThan(new Fraction(4,5)));

    }

    @Test
    public void testtoString()
    {
        Fraction g = new Fraction(1,3);
        String f = g.toString();
        assertEquals(true, f.contentEquals("1/3"));
    }

    }

嘿,顺便说一下,我写了你在顶部引用的代码。 :) 如果你感兴趣的话,最新和最棒的现在在Github上:https://github.com/kiprobinson/BigFraction - Kip
2个回答

6
问题在于您的negate方法返回一个新的Fraction对象,而您从未将其重新分配给g变量:
public void testNegate()
{
    Fraction g = new Fraction(1,3);
    g = g.negate(); //added "g ="
    assertEquals(-1, g.getNumerator());
    assertEquals(3, g.getDenominator());
}

作为一则附注,你的“negate”方法存在一个问题,即由于将分子的绝对值(始终为正)与-1相乘,因此分子始终为负数。只需从中删除“Math#abs”用法即可:
public Fraction negate()
{
    int num = this.numerator * -1;
    int den = this.denominator;
    return new Fraction(num,den);
}

一个让你的类成为不可变类的例子:
public class Fraction {
    //marking the fields as final in order to only be initialized
    //in class constructor
    private final int numerator;
    private final int denominator;

    public Fraction() {
        //always initialize the fields in constructor
        numerator = 0;
        denominator = 1; //because it cannot be zero
    }

    /**
     * Constructor with two int parameters
     * @param num is numerator
     * @param den is denominator
     */
    public Fraction(int num, int den) {
        if (den==0) {
            //denominator cannot be zero
            //it is better to throw an exception than just returning
            throw new IllegalArgumentException("Denominator cannot be zero");
        }
        int[] fractionData = this.normalize(num, den);
        //always initialize the fields in constructor
        this.numerator = fractionData[0];
        this.denominator = fractionData[1];
    }

    private int[] normalize(int numParam, int denParam) {
        int[] fractionData = new int[2];
        fractionData[0] = numParam;
        fractionData[1] = denParam;
        BigInteger gcd;
        BigInteger num = BigInteger.valueOf(numParam);
        BigInteger den = BigInteger.valueOf(denParam);
        gcd = num.gcd(den);
        if (gcd.intValue() > 1) {
            fractionData[0] = numParam / gcd.intValue();
            fractionData[1] = denParam / gcd.intValue();
        }
        return fractionData;
    }

    //leaving the rest of the implementation up to you...
}

@user3274207,根据你的Fraction类的实现细节,它看起来像是不可变的,就像String一样(除了你忘记将numeratordenominator字段标记为final)。因此,在实现每个操作时,您最终会检索到一个完全新的Fraction实例。在Peter的答案中,他的实现打破了你的类的不可变性,这就是为什么我只限制我的答案删除Math#abs并返回一个完全新的Fraction实例。请参阅此处的不可变性 - Luiggi Mendoza
@user3274207 关于这个评论“创建新实例始终很昂贵”:对于可能占用大量内存(RAM)的对象,例如从Apache POI创建一个工作簿来读取大小为10MB(或更大)的Excel文件。 对于这种情况,可以忽略创建新Fraction对象实例的成本,但请注意“过度优化是万恶之源”的原则:http://programmers.stackexchange.com/q/80084 https://dev59.com/LHRC5IYBdhLWcg3wK96V http://ubiquity.acm.org/article.cfm?id=1513451 - Luiggi Mendoza
嗨Luiggi,感谢您的评论。事实上,正如您所提到的,Peter的答案破坏了不可变性,而我的分数应该是不可变的。所以现在我采用了您的答案!同样的,成本方面,创建一个int对象并不需要太多的内存。非常感谢您跟进并向我解释这个问题。 - user3274207
我在初始化分子和分母字段时遇到了问题,当我将它们声明为final时,Eclipse会在每个方法中抱怨:“空白的final字段numerator可能尚未初始化”或“无法分配最终字段Fraction.Numerator(或Denominator)”,并建议将其改回去。 - user3274207
@user3274207 当您将字段声明为“final”时,只能在类构造函数中分配它们一次,但是您的“normalize”方法也尝试更改它们的值。 - Luiggi Mendoza
显示剩余4条评论

1

除非确实需要,否则我不会创建一个新的Fraction实例。

public Fraction negate()
{
    this.numerator *= -1;
    return this;
}

此外,我认为您在normalize()中不需要使用BigInteger。我会像这样实现它。
private void normalize()
{
    int gcd = gcd(this.numerator, this.denominator);
    if (gcd > 1)
    {
        this.numerator = this.numerator / gcd;
        this.denominator = this.denominator / gcd;
    }
    if (this.denominator < 0){
        this.numerator *= -1;
        this.denominator *= -1;
    }
}

谢谢Peter,创建新实例总是很耗费资源的。 - user3274207

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接