2017-09-10

1: Where to Use Enums and Where to Use Constants Groups

<The previous article in this series | The table of contents of this series | The next article in this series>

Main Body START

To Know Where to Use Enums and Where to Use Constants Groups

About: Java Programming Language

We Can't Extend Enums, and This Is Why

-Hypothesizer

Ah, we can't extend Enums. That is so . . .

-Rebutter

Is that a problem?

-Hypothesizer

For my plan, yes. I thought, I would create a base Enum, 'Test1BaseEnum', and extended Enums, 'Test1ExtendedEnum1' and 'Test1ExtendedEnum2' like this.

@Java Source Code
public enum Test1BaseEnum {
 Value1 ("Value1"),
 Value2 ("Value2");
 
 String value = null;
 
 Test1BaseEnum (String p_value) {
  value = p_value;
 }
 
 public String getValue () {
  return value;
 }
}

public enum Test1ExtendedEnum1 extends Test1BaseEnum {
 Value3 ("Value3"),
 Value4 ("Value4");
 
 Test1ExtendedEnum1 (String p_value) {
  super (p_value)
 }
}

public enum Test1ExtendedEnum2 extends Test1BaseEnum {
 Value3 ("Value3"),
 Value5 ("Value5");

 Test1ExtendedEnum2  (String p_value) {
  super (p_value)
 }
}

However, that doesn't work.

-Rebutter

Why?

-Hypothesizer

Inheritance of Enums is prohibited.

-Rebutter

I guess, there is a reason why it is prohibited. They didn't prohibit it from ill will or stinginess, did they?

-Hypothesizer

It seems that the keyword 'enum' means that the class inherits the Enum class. As multiple inheritance isn't allowed in Java, we can't specify another super class.

-Rebutter

That is a matter of the enum specification of Java. What if you create your own enum classes?

-Hypothesizer

Well, let's think like this.

@Java Source Code
public class Test2BaseEnum {
 String value = null;
 public static final Test2BaseEnum Value1 = new Test2BaseEnum ("Value1");
 public static final Test2BaseEnum Value2 = new Test2BaseEnum ("Value2");
 
 Test2BaseEnum (String p_value) {
  value = p_value;
 }
 
 public String getValue () {
  return value;
 }
}

public final class Test2ExtendedEnum1 extends Test2BaseEnum {
 public static final Test2ExtendedEnum1 Value3 = new Test2ExtendedEnum1 ("Value3");
 public static final Test2ExtendedEnum1 Value4 = new Test2ExtendedEnum1 ("Value4");
 
 private Test2ExtendedEnum1 (String p_value) {
  super (p_value);
 }
}

public final class Test2ExtendedEnum2 extends Test2BaseEnum {
 public static final Test2ExtendedEnum2 Value3 = new Test2ExtendedEnum2 ("Value3");
 public static final Test2ExtendedEnum2 Value5 = new Test2ExtendedEnum2 ("Value5");
 
 private Test2ExtendedEnum2 (String p_value) {
  super (p_value);
 }
}

That can be compiled successfully. . . . Well, but, I understand now why inheritance isn't allowed for Enums. That code doesn't work.

-Rebutter

How doesn't that work?

-Hypothesizer

Let's suppose we receive a value of 'Test2ExtendedEnum1' as an argument of a method like this.

@Java Source Code
 public static void testTest2 (Test2ExtendedEnum1 p_enumValue) {
  System.out.println (p_enumValue.getValue ());
 }

We can't pass 'Test2ExtendedEnum1.Value1' into the method because 'Test2ExtendedEnum1.Value1' is an instance of 'Test2BaseEnum', not of 'Test2ExtendedEnum1' or any sub class of it.

-Rebutter

So, that is the reason why we can't extend Enums.

The Essence of Enums

-Rebutter

Although we will be able to pass 'Test2ExtendedEnum1.Value1' into the method if we make the argument type 'Test2BaseEnum', but . . .

-Hypothesizer

. . . that will be meaningless.

The essence of enums is to restrict an argument of a method to a set of predetermined values, in this case, values contained in 'Test2ExtendedEnum1'. If we make the argument type 'Test2BaseEnum', we will be able to pass 'Test2ExtendedEnum2.Value5', which is what we need to prohibit.

-Rebutter

That restriction is the exact reason why we intend to use enums.

-Hypothesizer

If we just want to list some constants without that necessity, an interface with member variables will do, like this.

@Java Source Code
interface Test3BaseConstantsGroup {
 String Value1 = "Value1";
 String Value2 = "Value2";
}

interface Test3ExtendedConstantsGroup1 extends Test3BaseConstantsGroup {
 String Value3 = "Value3";
 String Value4 = "Value4";
}

interface Test3ExtendedConstantsGroup2 extends Test3BaseConstantsGroup {
 String Value3 = "Value3";
 String Value5 = "Value5";
}

That way, we can extend constants groups as we like, and there is no necessity to call the method 'getValue'.

However, our test method will have to be like this.

@Java Source Code
 public static void testTest3 (String p_constant) {
  System.out.println (p_constant);
 }
-Rebutter

We will be able to pass any String instance.

-Hypothesizer

Of course, we can check passed values in the method at run time, but that way, we can find out violations only at run time by doing intensive tests.

-Rebutter

We may be able to find out them by scanning source files, but anyway, that would be cumbersome.

-Hypothesizer

With enums, the compiler just detects violations, which is a huge difference.

-Rebutter

And IDEs may give us some help, if we define an argument of a method as an enum.

-Hypothesizer

Yes. IDEs may detect and recommend us what we have to pass into the argument, which is a big help.

-Rebutter

And there is another benefit of enums: we can enumerate defined values.

-Hypothesizer

Ah, . . . but about that, I think, I can implement such a function also into constants groups.

Isn't There a Perfect Solution?

-Hypothesizer

Isn't there a solution in which we can restrict passed values at compile time and also can maintain a hierarchy of allowed values groups?

-Rebutter

Do you have any idea?

-Hypothesizer

Well, . . . how about this?

@Java Source Code
public class Test4BaseEnum {
 public static final Test4BaseEnumValue Value1 = new Test4BaseEnumValue ("Value1");
 public static final Test4BaseEnumValue Value2 = new Test4BaseEnumValue ("Value2");
 
 public static class Test4BaseEnumValue extends Test4ExtendedEnum1.Test4ExtendedEnum1Value {
  Test4BaseEnumValue (String p_value) {
   super (p_value);
  }
 }
}

public class Test4ExtendedEnum1 extends Test4BaseEnum {
 public static final Test4ExtendedEnum1Value Value3 = new Test4ExtendedEnum1Value ("Value3");
 public static final Test4ExtendedEnum1Value Value4 = new Test4ExtendedEnum1Value ("Value4");
 
 public static class Test4ExtendedEnum1Value {
  String value = null;
  
  Test4ExtendedEnum1Value (String p_value) {
   value = p_value;
  }
  
  public String getValue () {
   return value;
  }
 }
}

 public static void testTest4 (Test4ExtendedEnum1.Test4ExtendedEnum1Value p_enumValue) {
  System.out.println (p_enumValue.getValue ());
 }
-Rebutter

Ah, you created value classes hierarchy in the reverse direction, but . . . that doesn't work, as you can see easily.

-Hypothesizer

Can I see easily? . . . Well, how?

-Rebutter

Just try to create 'Test4ExtendedEnum2' too.

-Hypothesizer

Well, . . . I see. 'Test4BaseEnumValue' can't extend two classes. . . . Then, what if we use interfaces like this?

@Java Source Code
interface BaseEnumValueInterface {
 String getValue ();
}

abstract public class BaseEnumValue implements BaseEnumValueInterface {
 String value = null;
 
 BaseEnumValue (String p_value) {
  value = p_value;
 }
 
 @Override
 public String getValue () {
  return value;
 }
}

public class Test5BaseEnum {
 public static final Test5BaseEnumValue Value1 = new Test5BaseEnumValue ("Value1");
 public static final Test5BaseEnumValue Value2 = new Test5BaseEnumValue ("Value2");
 
 public interface Test5BaseEnumValueInterface extends Test5ExtendedEnum1.Test5ExtendedEnum1ValueInterface {
 }
 
 public static class Test5BaseEnumValue extends BaseEnumValue implements Test5BaseEnumValueInterface {
  private Test5BaseEnumValue (String p_value) {
   super (p_value);
  }
 }
}

public class Test5ExtendedEnum1 extends Test5BaseEnum {
 public static final Test5ExtendedEnum1Value Value3 = new Test5ExtendedEnum1Value ("Value3");
 public static final Test5ExtendedEnum1Value Value4 = new Test5ExtendedEnum1Value ("Value4");
 
 public interface Test5ExtendedEnum1ValueInterface extends BaseEnumValueInterface {
 }
 
 public static final class Test5ExtendedEnum1Value extends BaseEnumValue implements Test5ExtendedEnum1ValueInterface {
  private Test5ExtendedEnum1Value (String p_value) {
   super (p_value);
  }
 }
}

 public static void testTest5 (Test5ExtendedEnum1.Test5ExtendedEnum1ValueInterface p_enumValue) {
  System.out.println (p_enumValue.getValue ());
 }
-Rebutter

Ah, that won't do, you know.

-Hypothesizer

I know? . . . Well, I don't seem to know. In fact, that seems to work . . .

-Rebutter

That may not give any error at compile time or run time, but does that realize the essence of enums we discussed above?

-Hypothesizer

What do you mean?

-Rebutter

Someone will be able to do like this.

@Java Source Code
  testTest5 (new Test5ExtendedEnum1.Test5ExtendedEnum1ValueInterface () {public String getValue () {return "Well, your restriction is flawed . . .";}});
-Hypothesizer

No! Don't do that! It's prohibited!

-Rebutter

Being said "It's prohibited," it isn't prohibited on the code.

-Hypothesizer

Please, don't do that, I ask . . .

-Rebutter

If asking solves the problem, you could have just asked from the beginning.

-Hypothesizer

Well, to think of it, we can't also expect help from IDEs for the code, for 'Test5ExtendedEnum1.Test5ExtendedEnum1ValueInterface' doesn't give options for values.

-Rebutter

Fundamentally, the type of the argument of the test method has to be a final class for the restriction to work.

-Hypothesizer

Otherwise, we can evade the restriction by creating an arbitrary class that extends the argument class or implements the argument interface.

-Rebutter

In fact, 'test4' was flawed in that meaning, even if multiple inheritance is allowed in Java.

-Hypothesizer

So, as a conclusion, we can't use values of any super class or values of any sub class: we have to define values as instances of the argument class itself. That seems to mean that we can't create extendable enums based on the present specifications of Java.

Conclusion

-Hypothesizer

If we don't need to enforce any restriction of possible values, we can just use constants groups. If we need enumeration, we will implement the enumeration (we will do so in a future article). That way, we can build any hierarchy of those groups.

If we need to enforce a restriction of possible values, we can't help but to use enums. In that case, we will have to copy definitions of values to some enums if those values are shared among those enums. If we want to eliminate such crude copying of definitions, we can't do it inside Java, and will have to create a preprocessor to create Java Enums. It won't be difficult to create a preprocessor that creates Enum Java sources from a XML file that defines the hierarchy of those Enums.

Main Body END

<The previous article in this series | The table of contents of this series | The next article in this series>