-1

How to create immutable Planet so the name doesn't change? I am struggling as I think it is immutable project with mutable object. Correct me if I am wrong.

Every time I change name in the output also changes. Am I missing something?

I tried to do all fields private and final (not in this example) but I think I am missing some code to work.

I know java.util.Date is deprecated but this is just for example.

import java.util.Date;   

public final class Planet {  
    String name;                                                      
    private final Date discoveryDate;  

    public Planet (String name, Date discoveryDate) {               
        this.name = name;
        this.discoveryDate = new Date(discoveryDate.getTime());    
    }

    public String getName() 
        return name;
    }

    public Date getDiscoveryDate() {               
        return new Date(discoveryDate.getTime());     
    }

    public static void main(String [] args) {
        Planet Earth = new Planet("Earth Planet", new Date(2020,01,16,17,28));

        System.out.println("Earth");
        System.out.println("------------------------------------");
        System.out.println("Earth.getName: " + Earth.getName());
        System.out.println("Earth.getDiscoveryDate: " + Earth.getDiscoveryDate());
    }
}
3
  • 2
    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. Moreover, Date(int year, int month, int date, int hrs, int min) has been deprecated since JDK 1.1 (which was released 24 years ago). Commented Jan 30, 2021 at 23:12
  • 3
    You have not marked name as final, why that? Planet is supposed to be immutable. Commented Jan 30, 2021 at 23:26
  • The Date class is not only poorly designed and long outdated, as you say, it is also mutable! So a problematic choice for a field in an immutable class (there would be ways around it, but since the date-time classes of java.time are already immutable, the easy solution is to switch to one of those). Commented Jan 31, 2021 at 10:14

2 Answers 2

5

tl;dr

Either:

  • Make a record like this, in Java 16 and later:
    public record Planet( String name , LocalDate discovered ) {}
  • Or, before Java 16, make a class where you:
    • Mark all member fields final and private.
    • Make getter methods as needed, but no setter methods.

Record

Just use the new records feature in Java 16 (previewed in Java 15).

Define your class as a record when its main job is to transparently and immutably carry data. The compiler implicitly creates a constructor, the getters, hashCode & equals, and toString.

Notice that the getter methods implicitly defined in a record do not begin with the JavaBeans-style get… wording. The getter method is simply the name of member field as defined in the parentheses following the class name.

Of course, if your getter methods provide access to an object that is itself mutable, being contained in a record does nothing to stop the calling programmer from mutating the contained object. Notice in the example class next that both String and LocalDate classes are themselves immutable by design. So the mutability of a contained object is a non-issue here.

package org.example;

import java.time.LocalDate;

public record Planet( String name , LocalDate discovered )
{
}

Using that record.

Planet Earth = new Planet( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.name: " + Earth.name() );
System.out.println( "Earth.discovered: " + Earth.discovered() );

When run.

Earth
------------------------------------
Earth.name: Earth
Earth.discovered: 2020-01-16

Class

Without the records feature, to make sure a class is immutable you should:

  • Mark the member fields final. This means the field cannot be assigned a different object after the constructor has finished.
  • Mark the member fields private. This means objects of other classes will not have direct access to read or change those fields.
  • Provide getter methods, if needed, but no setter methods. By convention, the JavaBeans-style get… or is… naming is used.

You should also provide appropriate override implementations of hashCode, equals, and toString. Your IDE will help generate the source code for those.

package org.example;

import java.time.LocalDate;
import java.util.Objects;

public class Planète
{
    // Member fields
    final String name;
    final LocalDate discovered;

    // Constructors
    public Planète ( String name , LocalDate discovered )
    {
        Objects.requireNonNull( name );
        Objects.requireNonNull( discovered );
        this.name = name;
        this.discovered = discovered;
    }

    // Getters (read-only immutable class, no setters)
    public String getName ( ) { return this.name; }

    public LocalDate getDiscovered ( ) { return this.discovered; }

    // Object class overrides
    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        Planète planète = ( Planète ) o;
        return getName().equals( planète.getName() ) && getDiscovered().equals( planète.getDiscovered() );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash( getName() , getDiscovered() );
    }

    @Override
    public String toString ( )
    {
        return "Planète{ " +
                "name='" + name + '\'' +
                " | discovered=" + discovered +
                " }";
    }
}

Using that class.

Planète Earth = new Planète( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.getName: " + Earth.getName() );
System.out.println( "Earth.getDiscoveryDate: " + Earth.getDiscovered() );

Side issues

Do not start a decimal integer literal with 0. The leading zero makes the number octal rather decimal. So your code passing 2020,01,16 should be 2020,1,16.

Never use the Date class, nor Calendar or SimpleDateFormat. These terrible classes are now legacy, supplanted years ago by the modern java.time classes defined in JSR 310. In code above, we used java.time.LocalDate to represent a date-only value, without a time-of-day and without a time zone.

Sign up to request clarification or add additional context in comments.

Comments

-1

Planet is immutable but field name should be private.

2 Comments

How is a Planet object immutable if name is not private?
With private name it is, that's what I meant.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.