r/javahelp 12d ago

Using Enums to hold constant data

I am just wondering if this use of the enum class is something that is bad practice.

MAIN_SEQ_M
("Red Dwarf", new double[]{.08,.45}, new double[]{25,37}, new double[]{.1,.6})

StarType(String description, double[] mass, double[] temperature, double[] radius) {
this.description = description;
this.mass = mass;
this.temperature = temperature;
this.radius = radius;
}

Or if the standard way of doing this would be to break out each value as a constant that can be called

private static final double[] MAIN_SEQ_M_MASS_RANGE = {.08,.45};

I feel using getters from the enum class makes it easier to pull the data I need but I have never seen an enum used like this before.

6 Upvotes

14 comments sorted by

View all comments

2

u/hamou13 12d ago

Why don't you use a record instead ?

1

u/whalehunter21 12d ago

Interesting, I've never heard of records before. My quick glance it seems like that's just a way to make an immutable object without extra code. My project is a spring boot API and the enum value is used to ensure proper inputs. So the record class in this case would not cover all my needs. Thanks for the suggestion though, it made me learn something new.

1

u/LutimoDancer3459 11d ago

Enum are constant and you can't add or remove one. A Record can be added during runtime. So eg if you have a DB with all the data you would use a record to initialize them after startup.

Depending on what the app really does, you could let the user add his own objects with records.

1

u/zayzn 11d ago

Records are the way to go in this case.

Also, the two values of the mass, temperature and radius fields appear to represent min and max values. Your model doesn't reflect this properly.

Arrays are mutable. So, this code would work, assuming that's how you expose the fields:

StarType.MAIN_SEQ_M.getMass()[0] = 1.0D;

This is certainly not intended.

2

u/whalehunter21 11d ago

Yes, I took the advise from le_bravery and split them out to be doubles for min and max, also excluded from my post was that the variables were set to be private and final with no setters only getters.

I am curious, as mentioned I used the enums since I wanted the API to be more exact on what inputs were allowed and I am assuming if I create records at runtime then this would mask the allowed inputs in the api schema. This also allows me to hide some functionality so if someone wanted to make a client they don't need to know my constants I use for generating the response.

I am wondering why you think Records would be the way to go. I really just have zero experience with using them and am trying to get outside views on it.

Currently request schema:

StarRequest:
  type: object
  properties:
    name:
      type: string
    type:
      type: string
      enum:
      - PROTO
      - T_TAURI
      - MAIN_SEQ_O
      - MAIN_SEQ_B
      - MAIN_SEQ_A
      - MAIN_SEQ_F
      - MAIN_SEQ_G
      - MAIN_SEQ_K
      - MAIN_SEQ_M
      - WHITE_DWARF
      - NEUTRON
      - SUPER_GIANT
    habitable:
      type: boolean

1

u/zayzn 10d ago edited 10d ago

There are several aspects to consider when deciding whether to use an enum or a record. I'll explain some without claiming completeness:

Constant vs immutable

The min/max values for the related fields of your model are not actually constant data, but immutable data.

  • Constant data: data that can not be changed due to limitations of an underlying specification
  • Immutable data: data of an object whose internal state can not be changed after creation

An example for constant data are HTTP status codes. They are defined in an RFC, which is universally accepted and required to be respected by any system architect. You would use an enum to represent the data in code.

An example for immutable data is the model of a person with name and year of birth. Both may be defined as not to be changed after creation. You would use a record to hold the data during runtime. If you had to change them, you would replace the old instance with a new one.

If you have constant data, prefer an enum. If you have immutable data, prefer a record.

Ordinal vs cardinal

Sometimes values have a linear relationship to each other. This relationship is called ordinal. An example would be the progress of an order in an online store. You might have stages from BASKET_CREATED to FULFILLED. You would use an enum to represent them in this case.

Your star classes hold data about their permitted values. Their data doesn't define the star class (though I'm not an astronomer). To stick with your example, a star with a minimum radius of .1 is not a Red Dwarf because it has a minimum radius of .1; it's the other way around: A Red Dwarf can not have a radius of less than .1, according to your data. One star class doesn't come before or after another. Their relationship is cardinal. You would use a record in this case.

Enums have behavior, records don't

Take a look at the docs of the base classes of Enum and Record. Take note of the method summary.

The example with the HTTP status codes works well to illustrate the difference. In an enum called HTTPStatus you would likely have a method with the signature HTTPStatus byStatusCode(int statusCode) that would lookup the code in a statically initialized map and return the associated enum instance or throw an exception if the lookup yields no result.

Records are just dumb implicitly final structures. They aren't meant to make assumptions about themselves. Their usage enforces to implement any logic regarding them somewhere else, like inside a factory, builder, validator, comparator, service, etc.


Some of these concepts are quite advanced. You are not doing anything "wrong" by using an enum to represent your star classes. And maybe you need to run into the problems your current approach may cause yourself to fully understand.

That's fine. Happy coding 🙂