-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());
    }
}
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
rickyk
  • 19
  • 2
  • 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](https://www.oracle.com/technical-resources/articles/java/jf14-date-time.html). Moreover, [Date(int year, int month, int date, int hrs, int min)](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html#Date-int-int-int-int-int-) has been deprecated since JDK 1.1 (which was released 24 years ago). – Arvind Kumar Avinash Jan 30 '21 at 23:12
  • 3
    You have not marked `name` as `final`, why that? `Planet` is supposed to be immutable. – akuzminykh Jan 30 '21 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). – Ole V.V. Jan 31 '21 at 10:14

2 Answers2

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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
-1

Planet is immutable but field name should be private.

Dr. Bright
  • 19
  • 7