Java 如何修复泄漏的访问器方法?

Java 如何修复泄漏的访问器方法?,java,junit,bluej,Java,Junit,Bluej,所以我很难弄清楚为什么我在JUnit中的测试失败了。我有一个Bill类、一个Money类和一个Date类。正在测试和行中创建一个新的票据对象 assertTrue( myBill.getAmount().getCents() == 0); 这是失败的。所以我知道它发生在哪里,但我不确定如何修复它。我尝试过改变我的变异方法,比如 return new Date(dueDate); 而不仅仅是 return dueDate; 但它在JUnit中仍然失败。请帮忙 测试代码: @Test p

所以我很难弄清楚为什么我在JUnit中的测试失败了。我有一个
Bill
类、一个
Money
类和一个
Date
类。正在测试和行中创建一个新的
票据
对象

assertTrue( myBill.getAmount().getCents() == 0); 
这是失败的。所以我知道它发生在哪里,但我不确定如何修复它。我尝试过改变我的变异方法,比如

return new Date(dueDate); 
而不仅仅是

return dueDate; 
但它在JUnit中仍然失败。请帮忙

测试代码:

@Test
public void testBillConstructorPrivacyLeak()
{
    Date date1 = new Date( 1, 1, 2020);
    Money money1 = new Money( 10);
    Bill myBill = new Bill( money1, date1, "sam");

    date1.setYear( 2021);
    money1.setMoney( 5, 10);

    //Now get values and make sure they have not changed
    assertTrue( myBill.getAmount().getCents() == 0);
    assertTrue( myBill.getDueDate().getYear() == 2020);
}
我的班级:

public class Bill
{
private Money amount;
private Date dueDate;
private Date paidDate;
private String originator;

//paidDate set to null
public Bill (Money amount, Date dueDate, String originator) {
    this.amount = amount;
    this.dueDate = dueDate;
    this.originator = originator;
    paidDate = null;
}

//copy constructor
public Bill (Bill toCopy) {
    this.amount = toCopy.amount;
    this.dueDate = toCopy.dueDate;
    this.paidDate = toCopy.paidDate;
    this.originator = toCopy.originator;
}

public Money getAmount () {
    return new Money(amount);
}

public Date getDueDate () {
    return new Date(dueDate);
}

public String getOriginator () {
    return originator;
}

//returns true if bill is paid, else false
public boolean isPaid () {
    return (paidDate != null);
}

//if datePaid is after the dueDate, the call does not update anything and returns false.
//Else updates the paidDate and returns true
//If already paid, we will attempt to change the paid date.
public boolean setPaid (Date datePaid) {
    if (datePaid.isAfter(dueDate)) {
        return false;
    }
    else {
        paidDate = new Date(datePaid);
        return true;
    }   
}

//Resets the due date – If the bill is already paid, this call fails and returns false. 
//Else it resets the due date and returns true.
public boolean setDueDate (Date newDueDate) {
    if (isPaid()) {
        return false;
    }
    else {
        dueDate = new Date(newDueDate);
        return true;
    }
}

//Change the amount owed.
//If already paid returns false and does not change the amount owed else changes 
//the amount and returns true.
public boolean setAmount (Money amount) {
   if (isPaid()) {
        return false;
    }
    else {
        amount = new Money(amount);
        return true;
    }
}

public void setOriginator (String originator) {
    this.originator = originator;
}

//Build a string that reports the amount, when due, to whom, if paid, and if paid 
//the date paid
public String toString () {
    return "Amount: " + amount + " Due date: " + dueDate + " To: " + "originator" + " Paid?" + isPaid() + "Paid date: " + paidDate; 
}

//Equality is defined as each field having the same value.
public boolean equals (Object toCompare) {
    if (toCompare instanceof Bill) {
        Bill that = (Bill) toCompare;
        return this.amount.equals(that.amount) && 
                this.dueDate.equals(that.dueDate) && 
                this.paidDate.equals(that.paidDate) && 
                this.originator.equals(that.originator);
    }
    return false;
}
}

公共类货币
{   
私人整数美元;
私人整数;
//构造函数,该构造函数设置美元金额,并将美分设置为0
//如果用户输入的金额小于0,您将抛出IllegalArgumentException
公款{
如果(dol<0){
抛出新的IllegalArgumentException(“必须大于0”);
}
这个.美元=dol;
美分=0;
}
//初始化美元和美分的构造函数。
//如果用户输入的金额小于0,您将抛出IllegalArgumentException
公款(整数美元,整数美分){
如果(dol<0 | |分<0){
抛出新的IllegalArgumentException(“必须大于0”);
}
这个.美元=dol;
这1.5美元+=美分/100;
这0.5美分=100美分;
}
//复制构造函数
公共资金(其他资金){
this.dollars=其他.dollars;
此0.5美分=其他0.5美分;
}
公共整数(美元){
归还美元;
}
公共整数分(){
退分;
}
//如果用户输入的金额小于0,您将抛出IllegalArgumentException
公共货币(整数美元,整数美分){
如果(美元<0 | |美分<0){
抛出新的IllegalArgumentException(“必须大于0”);
}
美元=美元;
这1.5美元+=美分/100;
这0.5美分=100美分;
}
//以双倍的形式获取货币金额
//例如,它可能返回5.75
公共双重货币(){
收益美元+(美分/100.0);
}
//如果用户输入的金额小于0,您将抛出IllegalArgumentException4
公共无效添加(整数美元){
如果(美元<0){
抛出新的IllegalArgumentException(“必须大于0”);
}
这.美元+=美元;
}
//如果用户输入的金额小于0,您将抛出IllegalArgumentException
公共无效添加(整数美元,整数美分){
如果(美元<0 | |美分<0){
抛出新的IllegalArgumentException(“必须大于0”);
}
这.美元+=美元;
这1.5美分+=美分;
这.美元+=这.美分/100;
this.cents=this.cents%100;
}
//将“其他”中的金额添加到我们的货币对象中–适当减少美分。
公共无效添加(其他货币){
this.dollars+=其他.dollars;
此0.5美分+=其他0.5美分;
这.美元+=这.美分/100;
this.cents=this.cents%100;
}
//如果两个货币对象对美元和美分的价值相同,则它们是相同的。
公共布尔等于(对象o){
如果(o货币实例){
返回this.dollars==(Money)o.美元和this.cents==(Money)o.美分;
}
返回false;
}
//将金额打印为字符串,即“$3.75”或“$4.00”,注意显示的位数(以美分为单位)。
//同样,出于测试和分级目的,请使用此精确的输出格式
公共字符串toString(){
字符串c=String.format(“%.02d”,美分);
返回“$”+美元+”+c;
}

}您的问题源于这样一个事实:在
Bill
的构造函数中,您存储了对
Money
Date
对象的引用。然后,当您在测试用例中修改这些对象时,您正在修改相同的对象

如果您不希望出现这种行为,您必须在
账单
构造函数中对
货币
日期
对象进行深度复制,即:

public Bill (Money amount, Date dueDate, String originator) {
    this.amount = new Money(amount);
    this.dueDate = new Date(dueDate);
    this.originator = originator;
    paidDate = null;
}

您不必为
发起人
执行此操作,因为字符串是不可变的。

您的问题是因为在
账单
的构造函数中存储了对
货币
日期
对象的引用。然后,当您在测试用例中修改这些对象时,您正在修改相同的对象

如果您不希望出现这种行为,您必须在
账单
构造函数中对
货币
日期
对象进行深度复制,即:

public Bill (Money amount, Date dueDate, String originator) {
    this.amount = new Money(amount);
    this.dueDate = new Date(dueDate);
    this.originator = originator;
    paidDate = null;
}
您不必为
originator
执行此操作,因为字符串是不可变的。

尽管您没有显示
Money
类的实现,但它具有
setMoney
方法这一事实表明它是可变的。在这种情况下,您的问题是
Bill
的构造函数没有复制它传入的对象,因此对
money1
的任何更改也会更改
myBill
的状态。类似的注释适用于
日期
对象

尝试按如下方式修改代码:

public Bill (Money amount, Date dueDate, String originator) {
    this.amount = new Money(amount);  // needs copy-constructor for Money
    this.dueDate = new Date(dueDate); // likewise for Date
    this.originator = originator;     // no copying needed as String is immutable
    paidDate = null;
}

//copy constructor
public Bill (Bill toCopy) {
    // Make copies also in the copy-constructor
    this.amount = new Money(toCopy.amount);
    this.dueDate = new Date(toCopy.dueDate);
    this.paidDate = (toCopy.paidDate == null) ? null : new Date(toCopy.paidDate);
    this.originator = toCopy.originator;
}
一般来说,将对象设计为可变意味着您必须在构造函数和其他地方进行防御性复制

另一方面,将对象设计为不可变的更好,因为这样可以避免此类问题(事实上,这是Joshua Bloch在其《有效Java》一书中给出的建议),但事实证明,Java在这些方面也帮不了你很多忙,你很可能要花相当长的时间才能把它们做好

我建议您使用这种设计方法来探索库,以获得更好的起点。

尽管您没有展示
Money
类的实现,但它具有
setMoney
方法这一事实表明它是可变的。在这种情况下,您的问题是
Bill
的构造函数没有复制它传入的对象,因此对
money1
的任何更改也会更改
myBill
的状态。类似的注释适用于
日期
对象

尝试按如下方式修改代码:

public Bill (Money amount, Date dueDate, String originator) {
    this.amount = new Money(amount);  // needs copy-constructor for Money
    this.dueDate = new Date(dueDate); // likewise for Date
    this.originator = originator;     // no copying needed as String is immutable
    paidDate = null;
}

//copy constructor
public Bill (Bill toCopy) {
    // Make copies also in the copy-constructor
    this.amount = new Money(toCopy.amount);
    this.dueDate = new Date(toCopy.dueDate);
    this.paidDate = (toCopy.paidDate == null) ? null : new Date(toCopy.paidDate);
    this.originator = toCopy.originator;
}
总的来说,设计y