Skip to content
Learn Netverks
0

(Another) Self-referential enum with immutable final fields

asked 2 hours ago by @qa-q85uhzrkyo83dlssloek 0 rep · 41 views

java enums field

I found this old thread (Self referential enum with immutable parameters) while trying to construct some Enum design for myself. After a couple mistakes, I seem to have something that works. But I wonder if it's too contrived or fragile to rely on.

I know one suggestion may be to not use fields. But as an exercise, I'd like to keep that for this question. The issues that I run into are (1) Assigning an enum value as a field member of another enum value, during instantiation of the enum, such that the second one, referenced, is still null during the first one's construction. And (2) referencing the enum value being constructed in its constructor with an if or switch, which is apparently just impermissible due to its not yet being instantiated I guess.

It seems I am able to get around (1) with the double indirection of a Supplier lambda and a valueOf method call. And around (2) by switching on Enum.name() method instead of the enum value itself (another idea was to send the enum name as a string param to the enum constructor to switch on).

Does this seem a bridge too far?`

import java.util.function.Supplier;

enum Level {
  LOW(),
  MEDIUM(),
  HIGH(),
  ;
    public final Supplier<Level> one;
    public final Supplier<Level> two;
    public final Supplier<Level> three;

    Level() {

    switch (this.name()) {
          case "LOW" -> {
                one = () -> Level.valueOf("LOW");
                two = () -> Level.valueOf("HIGH");
                three = () -> Level.valueOf("MEDIUM");
            }
            case "MEDIUM" -> {
                one = () -> Level.valueOf("HIGH");
                two = () -> Level.valueOf("MEDIUM");
                three = () -> Level.valueOf("LOW");
            }
            case "HIGH" -> {
                one = () -> Level.valueOf("LOW");
                two = () -> Level.valueOf("MEDIUM");
                three = () -> Level.valueOf("HIGH");
            } 
            default -> {
                one = () -> null;
                two = () -> null;
                three = () -> null;
            }
         }
    }
}

public class Main { 
  public static void main(String[] args) { 
    Level myVar = Level.LOW; 
    System.out.println(myVar); 
    System.out.println(myVar.one.get()); 
    System.out.println(myVar.two.get()); 
    System.out.println(myVar.three.get()); 
    myVar = Level.MEDIUM; 
    System.out.println(myVar); 
    System.out.println(myVar.one.get()); 
    System.out.println(myVar.two.get()); 
    System.out.println(myVar.three.get()); 
    myVar = Level.HIGH; 
    System.out.println(myVar); 
    System.out.println(myVar.one.get()); 
    System.out.println(myVar.two.get()); 
    System.out.println(myVar.three.get()); 
  } 
}



output: 
LOW
LOW
HIGH
MEDIUM
MEDIUM
HIGH
MEDIUM
LOW
HIGH
LOW
MEDIUM
HIGH

I also found I can simplify my solution to problem (1) if I push logic down to an inner class. There the Supplier lambda can reference the enum value (and not use valueOf).

`

import java.util.function.Supplier;

enum Level {
  LOW(),
  MEDIUM(),
  HIGH(),
  ;
  public Properties props;
  
    Level() {
        props = new Properties();
    }

  class Properties {
    public final Supplier<Level> one;
    public final Supplier<Level> two;
    public final Supplier<Level> three;
  
    public Properties() {
    
    switch (Level.this.name()) {
          case "LOW" -> {
                one = () -> HIGH;
                two = () -> LOW;
                three = () -> MEDIUM;
            }
          case "MEDIUM" -> {
                one = () -> HIGH;
                two = () -> MEDIUM;
                three = () -> LOW;
            }
          case "HIGH" -> {
                one = () -> LOW;
                two = () -> MEDIUM;
                three = () -> HIGH;
            } 
          default -> {
                one = () -> null;
                two = () -> null;
                three = () -> null;
            }
        }
        
    }
  }
}

public class Main { 
  public static void main(String[] args) { 
    Level myVar = Level.LOW; 
    System.out.println(myVar); 
    System.out.println(myVar.props.one.get()); 
    System.out.println(myVar.props.two.get()); 
    System.out.println(myVar.props.three.get()); 
    myVar = Level.MEDIUM; 
    System.out.println(myVar); 
    System.out.println(myVar.props.one.get()); 
    System.out.println(myVar.props.two.get()); 
    System.out.println(myVar.props.three.get()); 
    myVar = Level.HIGH; 
    System.out.println(myVar); 
    System.out.println(myVar.props.one.get()); 
    System.out.println(myVar.props.two.get()); 
    System.out.println(myVar.props.three.get()); 
  } 
}

output: 
LOW
HIGH
LOW
MEDIUM
MEDIUM
HIGH
MEDIUM
LOW
HIGH
LOW
MEDIUM
HIGH

Note that there will never be circular/cyclic references between enum values, not in one step or multiple steps.

edit: An answer may be that if it works, it works. But I wonder if the rule is "DO NOT reference enum values in enum constructors", and I'm sneaking around that rule, then there may be some other unintended consequences, or maybe even a compiler update could break the code (and happily say "we told you that's not supported...").

edit2: Stepping back now, I think I'd say that referencing the enem values in the switch for the property assignments is really the bigger concern for me. I'd definitely prefer the inner class to get rid of those string references in the switch.

Comments on this question (0)

Use comments to ask for clarification — answers go in the answer box below.

Log in to comment on this question.

0 answers

Your answer