尝试通过将 4 年添加到既定日期来创建新日期时,两个日期都会更改 (GregorianCalendar)

When trying to create a new date by adding 4 years to an established date, both dates are changed (GregorianCalendar)

完全公开,这是家庭作业,但只是作业的一小部分,我无法弄清楚为什么它不起作用。这是作业文本:

Create a CollegeStudent class. This class will contain data fields that hold a student's first name, last name, enrollment date, and projected graduation date, using the GregorianCalendar class for each date. Provide get() and set() methods for each field. Also provide a constructor that requires first and last names and enrollment date, and sets the project graduation date to exactly four years after enrolment. Save the class as CollegeStudent.java.

Create an interactive application that prompts the user for data for two CollegeStudent objects. Prompt the user for first name, last name, enrollment month, enrollment day, and enrollment year for each CollegeStudent, and then instantiate the objects. Display all the values, including the projected graduation dates. Save the application as TestCollegeStudent.java

我已经能够完成它要求的所有事情,除了当我尝试计算毕业日期时,它还会将注册日期值增加 4 年。这是我的 CollegeStudent class:

import java.util.GregorianCalendar;

public class CollegeStudent {

    String firstName;
    String lastName;
    GregorianCalendar enrollmentDate;
    GregorianCalendar graduationDate;
    
    
    // Constructor requiring first name, last name, and enrollment date
    public CollegeStudent(String first, String last, GregorianCalendar enrollment) {
        firstName = first;
        lastName = last;
        enrollmentDate = enrollment;
        
        // Set graduationDate to enrollmentDate, then add 4 years
        graduationDate = enrollment;
        graduationDate.add(GregorianCalendar.YEAR, 4);
    }
    // Get and Set firstName
    public void setFirstName(String first) {
        firstName = first;
    }
    public String getFirstName() {
        return firstName;
    }
    
    // Get and Set lastName
    public void setLastName(String last) {
        lastName = last;
    }
    public String getLastName() {
        return lastName;
    }
    
    // Get and Set enrollmentDate
    public void setEnrollmentDate(GregorianCalendar enrollment) {
        enrollmentDate = enrollment;
    }
    public GregorianCalendar getEnrollmentDate(){
        return enrollmentDate;
    }
    
    // Get and Set graduationDate
    public void setGraduationDate(GregorianCalendar graduation) {
        graduationDate = graduation;
    }
    public GregorianCalendar getGraduationDate() {
        return graduationDate;
    }

}

我的 TestCollegeStudent.java 显示注册日期和毕业日期,它们都显示用户输入的初始注册日期 4 年后的同一日期。如果我删除将 4 年添加到 graduationDate 的行,它们都显示为初始注册日期。我觉得我遗漏了一些非常简单的语法问题,但我一直无法弄清楚它是什么。

问题是当你这样做时 graduationDate = enrollment graduationDate 现在指向注册对象,你对其中任何一个所做的每一个更改都会反映到另一个,因为它们是 the相同 对象。

您真正想要的是创建一个基于另一个日历的 new 日历,应该是 graduationDate = (GregorianCalendar)enrollment.clone();

这是由于构造函数中的以下赋值而发生的:

graduationDate = enrollment;

此赋值使 graduationDate 引用 Calendar 的同一实例,而 Calendar 正被 enrollment 引用,因此通过任何引用带来的实例更改将导致两个参考文献的结果相同。

你可以通过下面的例子来理解:

import java.util.Calendar;
import java.util.GregorianCalendar;

public class Main {
    public static void main(String[] args) {
        Calendar enrollment = Calendar.getInstance();
        Calendar graduationDate = enrollment;
        System.out.println(enrollment.getTime());
        System.out.println(graduationDate.getTime());

        graduationDate.add(GregorianCalendar.YEAR, 4);
        System.out.println(enrollment.getTime());
        System.out.println(graduationDate.getTime());
    }
}

输出:

Sat Jan 16 15:31:29 GMT 2021
Sat Jan 16 15:31:29 GMT 2021
Thu Jan 16 15:31:29 GMT 2025
Thu Jan 16 15:31:29 GMT 2025

为了处理这种情况,graduationDate 需要引用被引用实例的副本(克隆)enrollment,以便通过引用带来的任何更改,graduationDate将仅适用于克隆。

import java.util.Calendar;
import java.util.GregorianCalendar;

public class Main {
    public static void main(String[] args) {
        Calendar enrollment = Calendar.getInstance();
        Calendar graduationDate = (Calendar)enrollment.clone();// You have to do this
        System.out.println(enrollment.getTime());
        System.out.println(graduationDate.getTime());

        graduationDate.add(GregorianCalendar.YEAR, 4);
        System.out.println(enrollment.getTime());
        System.out.println(graduationDate.getTime());
    }
}

输出:

Sat Jan 16 15:30:34 GMT 2021
Sat Jan 16 15:30:34 GMT 2021
Sat Jan 16 15:30:34 GMT 2021
Thu Jan 16 15:30:34 GMT 2025

顺便说一下,java.util 的日期时间 API 及其格式 API、SimpleDateFormat 已经过时且容易出错。建议完全停止使用它们并切换到 modern date-time API.

  • 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它向后移植了大部分 java.time Java 6 和 7 的功能。
  • 如果您正在为 Android 项目工作,并且您的 Android API 水平仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring and

查看 Trail: Date Time 了解使用 java.time 的分步教程和示例 API(现代日期时间 API)。

此线程中已经有一个很好的答案。我正在添加我的贡献。问题的主要来源是 graduationDate 指的是 enrollment

这一行

graduationDate = enrollment

你也可以试试这个:

// Set graduationDate to enrollmentDate, then add 4 years
graduationDate = new GregorianCalendar(); // New object
graduationDate.setTime(enrollmentDate.getTime()); // Update time
graduationDate.add(GregorianCalendar.YEAR, 4); // add years

java.time

对于你的作业,它可能不会完成,但其他人应该使用 java.time,现代 Java 日期和时间 API,用于日期工作。 并且! 使用 java.time 几乎不可能出现您所询问的错误。

    LocalDate enrollmentDate = LocalDate.of(2021, Month.JANUARY, 4);
    LocalDate graduationDate = enrollmentDate.plusYears(4);
    
    System.out.format("Enrolled: %s. Projected graduation: %s.%n", enrollmentDate, graduationDate);

输出为:

Enrolled: 2021-01-04. Projected graduation: 2025-01-04.

去到要求的边界

如果你想教你的老师应该如何做到这一点,并且仍然保持字段具有类型 GregorianCalendar 的要求......(1)我不能说这在你的学校是否可取。 (2) 会折衷,即没有理想的解决方案,优点是java.time,缺点是API混合,无谓的转换。这里是:

public class CollegeStudent {

    String firstName;
    String lastName;
    GregorianCalendar enrollmentDate;
    GregorianCalendar graduationDate;
    
    // Constructor requiring first name, last name, and enrollment date
    public CollegeStudent(String first, String last,
            int enrollmentYear, int enrollmentMonth, int enrollmentDay) {
        firstName = first;
        lastName = last;
        ZonedDateTime enrollmentZdt
                = LocalDate.of(enrollmentYear, enrollmentMonth, enrollmentDay)
                        .atStartOfDay(ZoneId.systemDefault());
        enrollmentDate = GregorianCalendar.from(enrollmentZdt);
        
        // Add 4 years to enrollmentZdt, then convert so it can be assigned to graduationDate
        ZonedDateTime graduationZdt = enrollmentZdt.plusYears(4);
        graduationDate = GregorianCalendar.from(graduationZdt);
    }
    
    // Getters and setters …
}

尝试一下:

    CollegeStudent student = new CollegeStudent("Wes", "Hampton", 2021, 1, 16);
    System.out.println("Enrolled:      " + student.getEnrollmentDate().toZonedDateTime());
    System.out.println("Will graduate: " + student.getGraduationDate().toZonedDateTime());

我所在时区的输出:

Enrolled:      2021-01-16T00:00+01:00[Europe/Copenhagen]
Will graduate: 2025-01-16T00:00+01:00[Europe/Copenhagen]

链接