【问题标题】:the good way of returning a mutable object返回可变对象的好方法
【发布时间】:2011-06-28 07:38:33
【问题描述】:

假设我有一个 Comment 类,我有一个名为 commentDate 的私有字段,它是一个 java.util.Date 和一个名为 getCommentDate 的 getter。

为什么返回该日期的副本( return new Date(commentDate.getTime()) 比简单地返回该日期更好......

用户如何更改该 Date 的对象状态,因为它是一个 getter,而不是一个 setter?

【问题讨论】:

  • 谁告诉你最好退回一份?
  • 下面的答案很好,所以我不会进一步扩展机制。但我希望作为提出这个问题的一部分,您会看到不可变数据对象的好处之一——它不会受到这个问题的影响。
  • @Ginn Effective Java...

标签: java


【解决方案1】:

由于java.util.Date 实现了Cloneable,您可以轻松克隆日期,如:

public class DateTest {
    private Date date;

    public DateTest() {

    }

    public Date getDate() {
        return (Date) date.clone();
    }

    public void setDate(Date date) {
        this.date = (Date) date.clone();
    }       
}

【讨论】:

  • 如果日期是空值怎么办?
  • 在 JAVA 8 中,您可以执行以下操作: 对于 GET 方法:返回 Optional.ofNullable(date).map(Date::getTime).map(Date::new).orElse(null);对于 SET 方法:this.date = Optional.ofNullable(date).map(Date::getTime).map(Date::new).orElse(null);
  • 为什么不直接使用三元运算符呢?例如。使用获取:返回 yourDate == null ?空:新日期(yourDate.getTime());关于克隆:stackoverflow.com/questions/7082553/…
【解决方案2】:

首先,请尽量避免使用getter和setter。如果您将它们都用于同一领域,那么您几乎可以肯定做错了什么。我不在乎 Java 大师告诉你什么。他们不知道他们在说什么。这不是面向对象的工作方式。 OO 不是一个将字段访问转换为方法调用的临时项目。这实际上并没有封装任何东西。

也就是说:如果您返回日期本身,那么调用代码将引用您的日期对象,并且可以使用其完整接口。由于日期是可变对象,因此接口包含可以更改对象状态的内容。由于参考是您的日期,因此您的日期状态将发生变化。调用代码如何获得日期并不重要(即“使用 getter”)。

【讨论】:

  • 如何在没有 getter 和 setter 的情况下访问字段?最好打电话给他们?
  • 不要。这就是OO的重点。使用 Comment 实例的人不应该知道有 Date 字段。该人不应该需要知道这一点。 Comment 类应该公开一个接口,该接口由函数组成,这些函数使用 Comment 来做有用的事情
  • 是的。 printCommentDateWithGivenDateFormatToGivenFile()convertCommentDateToXml()compareToAnotherCommentAccordingToCommentDate() 等。非常方便。
  • 嗯,没有。您只需 write 评论,然后将日期格式作为参数传递或已经 DI 了。您不会将评论日期本身转换为 XML;您几乎可以肯定需要将整个注释转换为 XML。 compareToAnotherCommentAccordingToCommentDate(Comment other) 更自然地拼写为 predates(Comment other)。同样,调用代码不应将 Comment 视为具有日期字段的内容;因此,包含CommentDate 的方法名称的想法本身就是可疑的。
  • 所以注释类需要能够处理 XML 序列化、JSON 序列化、SQL 序列化,将自己显示为 Swing 小部件,将自己输出到 HTML 表格中,从 HTML 表单中解析其数据全部通过自己?
【解决方案3】:

用户如何更改对象状态 那个日期,因为它是一个吸气剂,而不是 二传手?

轻松:

Comment comment = new Comment();
comment.getCommentDate().setTime(0); // now it's January 1, 1970 00:00:00 GMT.

【讨论】:

  • +1。大多数其他“原始包装器”(字符串、整数)是不可变的。我想Date也应该是。数组也有同样的问题。
  • @Thilo 替代Date 的java.time 类确实是不可变的。
【解决方案4】:

按照 Tapas Bose 示例,我们可以使用 JAVA 8 来处理 NULL 情况:

public class DateTest {
private Date date;

public DateTest() {

}

public Date getDate() {
    return Optional.ofNullable(date).map(Date::getTime).map(Date::new).orElse(null);
}

public void setDate(Date inputDate) {
    this.date= Optional.ofNullable(inputDate).map(Date::getTime).map(Date::new).orElse(null);
}}

参考:Is there a way to copy Date object into another date Object without using a reference?(Nicolas Henneaux 的回答)

【讨论】:

    【解决方案5】:

    用户不能“替换”getCommentDate() 提供的实例。但是,用户可以调用 getCommentDate().setMonth(10) 从而修改日期。因此,如果这是一个问题,我建议您返回“原始”实例的副本。

    【讨论】:

      【解决方案6】:

      由于java.util.Date 是可变的,它可以通过getter 像这样改变:

      getCommentDate().setYear(2011)
      

      这将导致评论上的commentDate 更改为2011 年。Date 上的所有其他设置方法当然也可以调用,这只是一个示例。

      【讨论】:

        【解决方案7】:

        在 Java 中,您正在处理引用。当您有一个 getter 并返回您的 commentDate 时,您实际上是在返回对该对象的引用。这意味着它是 same 对象,就像在您的私有字段中一样,由于 getter 返回的引用,调用者可以对其进行操作。

        【讨论】:

        • 但是如果我将该日期设置为另一个日期(1970 年),然后我再次调用我的方法,返回日期现在是 1970 年?
        • 是的,当然。当它仍然是同一个对象时(即你刚刚调用了setYear(70)),那么每个人都在通过你的吸气剂看到 1970 之前得到这个日期。那是因为它们都引用了您操作的同一个对象。当任何人通过您的 getter 获取对象并对其进行操作时,它也会影响持有对该对象的引用的所有其他人(包括您的引用它的类)。这可能非常好,但有时您必须注意不希望其他人能够操纵“您的”对象的情况。
        • 然后不可变数据对象开始发挥作用,就像@Andrzej Doyle 在他上面的评论中所说的那样。这些类被设计为不可变的,因此您不必关心其他人操作这些类型的对象。
        【解决方案8】:

        注意:不要通过 getter 返回可变对象,例如。日期(在 Java 8 之前)。它总是可以被流氓程序员重置。假设您编写了一个程序,其中根据工作年限计算员工的社会保障福利。

        public class Employee {
        // instance fields
        private String name;
        private String nickName;
        private double salary;
        private Date hireDay;
        
        // constructor
        Employee(String name, String aNickName, double aSalary, int aYear,
                int aMonth, int aDay) {
            this.name = name;
            nickName = aNickName;
            salary = aSalary;
            GregorianCalendar cal = new GregorianCalendar(aYear, aMonth - 1, aDay);
            hireDay = cal.getTime();
        }
        //needs to be corrected or improved  because date is a mutable object
        public Date getHireDay() {
            return hireDay;
        }
        

        黑客/糟糕的程序员可以使用 setter 重置日期

        Employee john = new Employee("John", "Grant", 50000, 1989, 10, 1);
            Date d = john.getHireDay();
        
            // Original hire date is Oct 1, 1989
            System.out.println("Original hire date "+ d.getTime()));
        
            long tenYearsInMilliseconds = 10 * 365 * 24 * 60 * 60 * 1000L;
            long time = d.getTime();
        
            // Hire date after hacker modifies the code
            d.setTime(time - tenYearsInMilliseconds);
            System.out.println("Hacked hire date "+john.getHireDay().getTime()));
        }
        

        相反..返回 Java 7 的 date 方法的克隆或使用 Java 8 的 LocalDate 类

         // for Java 7
        public Date getHireDay() {
            return (Date)hireDay.clone();
        }
        
        //for Java 8
        public LocalDate getHireDay() {
            return hireDay;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-11
          • 1970-01-01
          • 1970-01-01
          • 2018-05-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多