Feature or bug: unable to override enum method with default implementation of UnsupportedOperationException

Viewed 86

I have an application that uses an enum for the options. Some of the options should be able to have some state: an int value or a String value. (I know that in general an enum should only have final attributes, but there are use cases in which the value of the attribute is only known at runtime, and you can't pass arguments for the constructor of an enum at runtime.) I would like the default implementation of the getters and setters for these options to be implemented by throwing a UnsupportedOperationException, like this (part of the original code):

public enum FileTreeOption1 {
  HUMAN_READABLE, DATE, DIRS_ONLY, FILES_ONLY, 
  DEPTH{
    @Override
    public void setIntValue(int value){
      this.intValue = value;
    }
    @Override
    public int getIntValue(){
      return intValue;
    }
  };

  private int intValue;

  public void setIntValue(int value) {
      throw new UnsupportedOperationException("setIntValue not available for " + this.name());
  }
  public int getIntValue() {
      throw new UnsupportedOperationException("getIntValue not available for " + this.name());
  }
}

However, when I try to compile this, I get these error messages (in java 11):

FileTreeOption1.java:7: error: intValue has private access in FileTreeOption1
      this.intValue = value;
          ^
FileTreeOption1.java:11: error: non-static variable intValue cannot be referenced from a static context
      return intValue;
             ^
2 errors

Especially the second error is a strange one: there is no static context.

As a work around, I have managed to get my code working as shown below. But my question is: shouldn't the code above be able to compile?

Work around:

public enum FileTreeOption2 {
  HUMAN_READABLE, DATE, DIRS_ONLY, FILES_ONLY, 
  DEPTH(true);

  private boolean useIntValue;
  private int intValue;

  FileTreeOption2( boolean useIntValue){
    this.useIntValue = useIntValue;
  }
  FileTreeOption2(){}


  public void setIntValue(int value) {
    if(useIntValue){
      this.intValue = value;
    } else {
      throw new UnsupportedOperationException("setIntValue not available for " + this.name());
    }
  }
  public int getIntValue() {
    if(useIntValue){
      return this.intValue;
    } else {
      throw new UnsupportedOperationException("getIntValue not available for " + this.name());
    }
  }
}
4 Answers

Thanks to the help of Nikolai, I have just found out that not only can you override methods in an enum constant, but you can add variables to them too. So this one actually works:

public enum FileTreeOption1 {
  HUMAN_READABLE, DATE, DIRS_ONLY, FILES_ONLY, 
  DEPTH{
     private int intValue; // attribute only available to DEPTH
    @Override
    public void setIntValue(int value){
      this.intValue = value;
    }
    @Override
    public int getIntValue(){
      return intValue;
    }
  };

  public void setIntValue(int value) {
      throw new UnsupportedOperationException("setIntValue not available for " + this.name());
  }
  public int getIntValue() {
      throw new UnsupportedOperationException("getIntValue not available for " + this.name());
  }
  
  public static void main(String args[]){
      FileTreeOption1 option = FileTreeOption1.DEPTH;
      option.setIntValue(3);
      System.out.println(option.getIntValue());  // 3
      option = FileTreeOption1.DATE;
      System.out.println(option.getIntValue());  // UnsupportedOperationException
  }
}

You must change intValue visibility from private to protected to make the code compile

For some more context: the code below does compile, so you are in fact allowed to have state in an enum, and to implement one or more of the methods in a different way for one or more of the constants:

public enum FileTreeOption1 {
  HUMAN_READABLE, DATE, DIRS_ONLY, FILES_ONLY, 
  DEPTH{
    @Override
    public void setIntValue(int value){
      throw new UnsupportedOperationException();
    }
    @Override
    public int getIntValue(){
      return 1;
    }
  } ;

  private int intValue;


  public void setIntValue(int value) {
      this.intValue = value;
  }
  public int getIntValue() {
      return intValue;
  }
}

WRONG: Short answer to your question - no. The code should not compile.

Explanation:

First of all - enum cannot be extended. When you're trying to define your DEPTH{ - you're actually creating anonymous class that extends your enum. In Java it's not possilbe. The same also is a reason to first error: private int intValue; is private field - thus it is not visible inside subclasses.

The code does not compile because private int intValue; has private modifier and when you create your DEPTH{ - you cannot access private field there. Only public or protected.

The static context - Java enums are public static final by default (You can see this in the outline pane in Eclipse).

Related