1

Full disclosure, this is homework, but only a very small part of an assignment that I can't figure out why it's not working. Here is the assignment text:

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

I've been able to do everything it asks, except when I try to calculate the graduation date, it adds 4 years to the enrollment date value as well. Here is my 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;
    }

}

My TestCollegeStudent.java displays both the enrollmentDate and graduationDate and they both show the same date 4 years after the initial enrollmentDate entered by the user. If I remove the line where I added 4 years to the graduationDate, they both display as the initial enrollmentDate. I feel like I'm missing some very simple syntax issue, but I haven't been able to figure out what it is.

  • The `GregorianCalendar` class is poorly designed and for that reason was replaced by [java.time, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) with Java 8 nearly 7 years ago. Teachers who still ask homework assignments using `GregorianCaelendar` deserve to be sacked. – Ole V.V. Jan 16 '21 at 16:12

4 Answers4

3

It is happening because of the following assignment inside the constructor:

graduationDate = enrollment;

This assignment makes graduationDate reference to the same instance of Calendar which is being referenced by enrollment and therefore a change in the instance brought through any of the references will cause the result to be the same for both the references.

You can understand it with the following example:

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());
    }
}

Output:

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

In order to deal with this situation, graduationDate needs to reference a copy (clone) of the instance being referenced enrollment so that any changes brought through the reference, graduationDate will only be applicable to the clone.

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());
    }
}

Output:

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

By the way, the date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.

Check out Trail: Date Time for step-by-step tutorial and examples of using the java.time API (the modern date-time API).

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • Thank you, this solved the problem. I thought I was just passing the value of enrollment to graduationDate, not a reference to the instance. I still get confused with passing values vs passing references, but I understand what was happening now. Creating a clone of enrollmentDate fixed the issue. Thanks again for the thorough explanation! – Wes Hampton Jan 16 '21 at 17:04
  • When it comes to objects (including arrays), you are always assigning a reference, not the value. Only with primitives (like `int`) it’s always a value. – Ole V.V. Jan 16 '21 at 20:35
  • Thanks, @OleV.V. for putting additional information which will benefit the future visitors of this answer. I can also see that you have added yet another good answer using the modern date-time API. – Arvind Kumar Avinash Jan 16 '21 at 20:42
1

The problem is that when you do graduationDate = enrollment graduationDate is now pointing to the enrollment object, and every change you make on any of then will reflect on the other, since they are the same object.

What you really want is to create a new calendar that is based on another calendar, which should be graduationDate = (GregorianCalendar)enrollment.clone();

1

There is already a good answer present in this thread. I am adding my contribution. The main source of the problem is this line where graduationDate is referring to enrollment

graduationDate = enrollment

You can also try this:

// 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
silentsudo
  • 6,730
  • 6
  • 39
  • 81
1

java.time

For your assignment it probably won’t go, but everyone else should use java.time, the modern Java date and time API, for date work. And! With java.time it’s virtually impossible to make the error you are asking about.

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

Output is:

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

Going to the border of the requirements

If you want to teach your teacher how this should be done and still keep the requirement that the fields have type GregorianCalendar … (1) I can’t say whether this is advisable at your school. (2) It will be a compromise, that is, no ideal solution, with the advantages of java.time and the disadvantage of mixed APIs and unnecessary conversions. Here goes:

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 …
}

To try it out:

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

Output in my time zone:

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

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161