12

I'm trying to implement something in a semi-efficient manner. In my program, I have an enumerator representing metrics that are used to pass information around in the program.

I have a class that lets me "compose" these metrics into a single object so I can essentially create a "sentence" to give me complex information without having to do any complex parsing of information. They're essentially bit flags, but since I'm using an enum, I can have more than just 64 of them.

The problem is that if I'm instantiating instances of this container class a lot, and I go to create an array like so:

metrics = new boolean[Metrics.values().length];

I feel like creating an array of the enumerated values so often just to request its size is wasteful. I'm wondering if it's possible to just define the size of the enumerated values() in a static context so it's a constant value I don't have to recalculate:

private static final int METRIC_COUNT = Metrics.values().length;

Can I do this? Or would the compiler not have defined the enumerated values before it declares this static variable? I know this isn't the best explanation of my question, but I'm not sure how else to word it.

I know the static variable will be determined at runtime (unless the value assigned to it were a literal value), so would the program be able to request the members of the Metrics enum at this point in execution?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Darin Beaudreau
  • 375
  • 7
  • 30
  • 2
    You can certainly cache the length statically. Though I don't see where the array comes in. If you want to mimic a bit field, consider `EnumSet`. – shmosel Feb 26 '19 at 01:20
  • 2
    https://stackoverflow.com/a/32354397/2970947 – Elliott Frisch Feb 26 '19 at 01:34
  • 1
    Further to @ElliotFrisch's link, calling `values()` does create a copy of the enum's array (as opposed to exposing the Enum's own private one, which would open it to being modified). – racraman Feb 26 '19 at 01:41
  • FYI, the `enum` facility in Java (based on [`Enum`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html) class) is usually just called “enum” or “enum type” to avoid confusion with the interface [`java.util.Enumeration`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Enumeration.html). – Basil Bourque Feb 26 '19 at 01:49

3 Answers3

10

EnumSet

"compose" these metrics into a single object

essentially bit flags

Sounds like you need a bit-array, with a bit flipped for the presence/absence of each predefined enum value.

If so, no need to roll your own. Use EnumSet or EnumMap. These are special implementations of the Set and Map interfaces. These classes are extremely efficient because of their nature handling enums, taking very little memory and being very fast to execute.

Take for example the built-in DayOfWeek enum. Defines seven objects, one for each day of the week per ISO 8601 calendar.

Set< DayOfWeek > weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;

Use the convenient methods of Set such as contains.

boolean isTodayWeekend = weekend.contains( LocalDate.now().getDayOfWeek() ) ;

If you loop the elements of the set, they are promised to be provided in the order in which they are defined within the enum (their “natural” order). So, logically, an EnumSet should have been marked as a SortedSet, but mysteriously was not so marked. Nevertheless, you know the order. For example, looping EnumSet.allOf( DayOfWeek.class ) renders DayOfWeek.MONDAY first and DayOfWeek.SUNDAY last (per the ISO 8601 standard).

When Are Enum Values Defined?

The elements of an enum are defined at compile-time, and cannot be modified at runtime (unless you pull tricks with reflection). Each named variable is populated with an instance when the class is loaded. See Section 8.9, Enum Types of Java Language Specification.

If you define an enum Pet with DOG, CAT, and BIRD, then you know you will have exactly three instances at all times during runtime.

You can count the number of elements defined in an enum in at least two ways:

  • Calling the values method generated by the compiler for any enum, where you can ask the size of the resulting array, as you show in your Question.
    int countDows = DayOfWeek.values().length() ;
  • Calling Set::size after creating an EnumSet of all enum instances.
    int countDows = EnumSet.allOf( DayOfWeek.class ).size() ;
Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    @DarinBeaudreau You will find `EnumSet` (and `EnumMap`) quite useful. Notice the methods to copy, invert (`complementOf`), empty (`noneOf` & `removeAll`), and quickly define a subset of a large number of elements (`range`). – Basil Bourque Feb 26 '19 at 02:21
  • FWIW: It's possible to define new enum elements by using reflection in hacky ways. – user253751 Feb 26 '19 at 04:20
  • @immibis I almost added a mention of that originally. Your comment prompted me to do so now. – Basil Bourque Feb 26 '19 at 06:18
6
private static final int METRIC_COUNT = Metrics.values().length;

Can I do this?

Yes, you can. This is exactly the right way to cache the length and ensure it's only computed once.

I know the static variable will be determined at runtime (unless the value assigned to it were a literal value), so would the program be able to request the members of the Metrics enum at this point in execution?

Yep. As defined, METRIC_COUNT is also computed at runtime.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
-3

I don't know if you need to do this in this case, but I think I understand the more general problem of not being able to compute a constant early in your execution. I've had this problem. The solution I've used is something like this...compute METRICS_COUNT later on, when you're ready to use it and are sure that the Metrics object is defined, but still only once:

private static int METRIC_COUNT = -1;

public static int getMetricsCount() {
    if (METRIC_COUNT < 0)
        METRIC_COUNT = Metrics.values().length;
    return METRIC_COUNT;
}

Note that I'm not addressing the Enum problem specifically, as others are in the comments. I'm just proposing a way of putting off setting of a global value until just when it is needed.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44