Enums: Properties vs. Methods

R

Robert Klemme

All,

I am just musing about the pros and cons of using boolean properties
in enum classes vs. custom methods. So far I found

pro Properties:
- less classes
- when adding enum values to an enum you cannot forget to define
properties

pro Methods:
- smaller memory footprint per instance

Considering that there are always only so many instances it seems the
properties approach wins. It seems, custom methods in enum instances
are most useful if enums do actually do something. Then different
enums can have differing implementations of the method and we have an
instance of Strategy / State pattern.

Do you have more items for the lists? Did I overlook something?

Kind regards

robert

Example
/** We use boolean properties. */
public enum Prop {

A(true, true), B(true, false), C(false, true);

private final boolean a;

private final boolean b;

Prop(boolean a, boolean b) {
this.a = a;
this.b = b;
}

public boolean isA() {
return a;
}

public boolean isB() {
return b;
}
}

/** We use custom methods. */
public enum Meth {
A, B {
@Override
public boolean isB() {
return false;
}
},
C {
@Override
public boolean isA() {
return false;
}
};

public boolean isA() {
return true;
}

public boolean isB() {
return true;
}
}

Full toy class https://gist.github.com/892503#file_prop_vs_meth.java
 
L

Lew

All,

I am just musing about the pros and cons of using boolean properties
in enum classes vs. custom methods. So far I found

pro Properties:
- less classes

How is that a pro?
- when adding enum values to an enum you cannot forget to define
properties
pro Methods:
- smaller memory footprint per instance

You actually don't know what the footprint will be once Hotspot takes over.
With your "toy" example, those booleans might all optimize away and both cases
take the same memory at runtime.
Considering that there are always only so many instances it seems the
properties approach wins. It seems, custom methods in enum instances
are most useful if enums do actually do something. Then different
enums can have differing implementations of the method and we have an
instance of Strategy / State pattern.

Do you have more items for the lists? Did I overlook something?

Your example didn't make a good case for why you'd want do do such a thing.

Your "methods" example is confusing and the purpose behind the logic deeply
obscured by the idioms. That alone is enough to kill it. Your "properties"
example was clear and concise and easy to follow.

No-brainer.
 
M

markspace

/** We use custom methods. */
public enum Meth {


I agree with Lew that this second example was pretty confusing and hard
to follow. I also think that it's difficult to predict memory usage
without careful analysis.

I'd like to see a "motivating" example. What do you actually intend to
use this for? I can't see that a general case is too interesting,
except to disqualify the method pattern.

Then different
enums can have differing implementations of the method and we have an
instance of Strategy / State pattern.


I don't think State works this way. Strategy ... maybe, but the use of
enums for Strategy seems confining. I'd just use regular inheritance
and classes. The "method pattern" for Strategy would be fine I think if
it weren't shoe-horned into an enum.



I'll check it out.
 
D

Daniele Futtorovic

All,

I am just musing about the pros and cons of using boolean properties
in enum classes vs. custom methods.
- when adding enum values to an enum you cannot forget to define
properties

enum E {
;
public *abstract* abstractMethod();
}
 
L

Lew

markspace said:
I agree with Lew that this second example was pretty confusing and hard to
follow. I also think that it's difficult to predict memory usage without
careful analysis.

In the approach with final variables, they will be as constants to the
algorithm so there's really likely to be no memory waste there. 'return
false;' requires 'false' to live somewhere, be it in the 'return' statement
directly or optimized in as a constant from the 'final' variable.
I'd like to see a "motivating" example. What do you actually intend to use
this for? I can't see that a general case is too interesting, except to
disqualify the method pattern.

Maybe overridable methods in enums are suitable only for behaviors, not
attributes.
I don't think State works this way. Strategy ... maybe, but the use of enums
for Strategy seems confining. I'd just use regular inheritance and classes.
The "method pattern" for Strategy would be fine I think if it weren't
shoe-horned into an enum.

Don't get him wrong - there are plenty of use cases for overridable methods
that are ('final'ly) implemented in each enum instance.
 
R

Roedy Green

Prop(boolean a, boolean b) {
this.a = a;
this.b = b;
}

public boolean isA() {
return a;
}

I see what you mean. You would never use custom methods when you
could use getters and setters. It is a general principle you use the
smallest hammer that gets the job done. Using custom methods just
invites unintended divergence.
 
D

Daniele Futtorovic

I see what you mean.

Do you.
You would never use custom methods when you
could use getters and setters.

Wat? Getters and setters are methods, "custom" methods. How can a method
not be custom anyway? This is Java.
It is a general principle you use the
smallest hammer that gets the job done.

Wat? Which job? Which hammer? There's no difference between the
alternatives with respects to what their methods are like. Leaving
convenience and maintenance aside, the only difference between both is
that in the first one (without fields), the instructions are probably
easier to inline.
Using custom methods just
invites unintended divergence.

"Custom methods" ... "customs methods" ... "divergence" ... AHA!

Contraband!
 
L

Lew

Daniele said:
Wat? Which job? Which hammer? There's no difference between the alternatives
with respects to what their methods are like. Leaving convenience and
maintenance aside, the only difference between both is that in the first one
(without fields), the instructions are probably easier to inline.

That's confusing. The first example presented was with fields, and the second
one was without fields.

Regardless, the instructions are just as easy to inline either way. One
returns a constant, the other a constant variable.
 
D

Daniele Futtorovic

That's confusing. The first example presented was with fields, and the
second one was without fields.

Shit, sorry. s/first/second/
Regardless, the instructions are just as easy to inline either way. One
returns a constant, the other a constant variable.

For all purposes, yeah. I would however expect the one with the constant
to be marginally easier, as it requires no analysis of the field.
 
L

Lew

Daniele said:
For all purposes, yeah. I would however expect the one with the constant to be
marginally easier, as it requires no analysis of the field.

Which of the two are you calling "the one with the constant"?
 
L

Lew

Which of the two are you calling "the one with the constant"?

The reason for my confusion:

From the JLS, §17.5.3:
"If a final field is initialized to a compile-time constant ... uses of that
final field are replaced at compile time with the compile-time constant."

Details in §15.28:
"A compile-time constant expression is an expression denoting a value of
primitive type or a String that does not complete abruptly and is composed
using only the following:
. . .

o Simple names that refer to constant variables (§4.12.4).

§4.12.4:
"We call a variable, of primitive type or type String, that is final and
initialized with a compile-time constant expression (§15.28) a /constant
variable/."

<sscce type="com.lewscanon.eegee.Finality">

package com.lewscanon.eegee;

import java.util.Random;

/**
* Finality.
*/
public class Finality implements Runnable
{
final Random randy = new Random();

final int kount = 3;

@Override
public void run()
{
int value = randy.nextInt( kount + 1 );
System.out.print( " value = "+ value +": " );

switch ( value )
{
default:
System.out.println( "MISS" );
break;

case kount:
System.out.println( "HIT!" );
break;
}
}

/**
* main.
* @param args String [].
*/
public static void main( String [] args )
{
Finality fina = new Finality();

for ( int ix = 0; ix < 10; ++ix )
{
System.out.print( ix + "." );
fina.run();
}
}
}
</sscce>
 
D

Daniele Futtorovic

Which of the two are you calling "the one with the constant"?

The same as you when you said:
"One returns a constant, the other a constant variable"

That is to say, the solution in which the OP overrode the methods in the
enum instances.

Apologies for not making myself sufficiently clear.
 
L

Lew

The same as you when you said:
"One returns a constant, the other a constant variable"

You completely missed my point, which is that BOTH are constants.
That is to say, the solution in which the OP overrode the methods in the
enum instances.

How would that one be easier for anything? Please elucidate.

Both approaches have constants in the compiled code.
 
R

Robert Klemme

How is that a pro?

The reasoning was that then less space in Perm is needed.
You actually don't know what the footprint will be once Hotspot takes
over. With your "toy" example, those booleans might all optimize away
and both cases take the same memory at runtime.

Just so I understand it properly: are you saying that with hotspot the
compiler might remove members of the instances? I am not sure how that
would work since then hotspot would need to create several different
versions of the property getter methods (one per instance). I believe
this is not what hotspot can do.
Your example didn't make a good case for why you'd want do do such a thing.
Right.

Your "methods" example is confusing and the purpose behind the logic
deeply obscured by the idioms. That alone is enough to kill it. Your
"properties" example was clear and concise and easy to follow.

No-brainer.

Well, roughly speaking the idea was this:
https://gist.github.com/892503#file_valve.java

Here there are boolean properties which derive from the enum, i.e.
whether there is traffic allowed or not and whether the traffic can flow
unlimited.

Now code which uses this enum need not create switches based on concrete
enum instances but can use boolean properties in control flow (e.g.
print a warning if no traffic at all is allowed). One might later want
to add another enum value BROKEN(false, false) which is used to describe
the state of a broken medium. If this is done the code that uses those
properties to make decisions does not need to be extended.

Cheers

robert
 
R

Robert Klemme

I agree with Lew that this second example was pretty confusing and hard
to follow. I also think that it's difficult to predict memory usage
without careful analysis.

I'd like to see a "motivating" example. What do you actually intend to
use this for? I can't see that a general case is too interesting, except
to disqualify the method pattern.

See https://gist.github.com/892503#file_valve.java
I don't think State works this way. Strategy ... maybe, but the use of
enums for Strategy seems confining. I'd just use regular inheritance and
classes. The "method pattern" for Strategy would be fine I think if it
weren't shoe-horned into an enum.

Actually I always have a hard time distinguishing those two patterns:
IMHO they are pretty much identical at the core, the main feature is
that I delegate functionality to another instance which might be from
different classes related via inheritance (at least implementing the
same interface). The only real difference that I can spot is the point
in time when the delegate is changed: in one case it might be never
changed during the lifetime of an instance (strateg) and in the other
case it is changed whenever a state change occurs (state). I find that
only a marginal difference between the two compared to the crucial point
that those patterns both use an exchangeable delegate.

http://en.wikipedia.org/wiki/State_pattern
http://en.wikipedia.org/wiki/Strategy_pattern
I'll check it out.

Thanks.

Kind regards

robert
 
D

Daniele Futtorovic

You completely missed my point, which is that BOTH are constants.

If I were so inclined, I would argue that no, in the first case
"constant" is a noun, in the second an adjective, and that in the
clear-cut language of the JLS the noun 'constant' has a stringent
definition and the adjective 'constant', in the context of variables,
too, but that these definitions don't match.

But I won't, because I consider this whole issue a complete waste of
time, and only had responded to correct my inaccuracies, not to drive
any point.
 
M

markspace

Actually I always have a hard time distinguishing those two patterns:
IMHO they are pretty much identical at the core,


They're very different. State is for implementing state machines.
Strategy is for extensibility.

The biggest difference, to me, is that if I were implementing a State
pattern, I'd treat the State class as an implementation detail and keep
it and its children private. Whereas for Strategy having a public
Strategy interface/class is the whole point.



This really isn't either State or Strategy. It's just an enum with some
properties. I think you mean it to be a State, so let's start there.

State has defined transitions from one state to another. That each
state also might have a defined properties is almost incidental. Let me
try to find a better example, something that's a well known state
machine: opening up my TCP book (Comer) I see that he defines a TCP
connection to have the following states:

LISTEN, SYNSENT, SYNRCVD, ESTABLISHED, FINWAIT1, FINWAIT2, LASTACK,
CLOSEWAIT, TIMEWAIT, CLOSING, CLOSED, and FREE. These state transition
depending on whether an SYN, RESET or FIN has been received, whether the
user calls close(), and some internal timers.

I don't want to do the whole state machine (it's complicated) so let's
just try part of it. The first bit is that the internal state of a TCP
connection is an implementation detail and should not be public. This
is very different from Strategy!

So starting from ESTABLISHED, if a fin is recieved, it goes to the
CLOSEWAIT state. If a syn is received, it's an error and we send a
reset and abort the connection. If a reset is received, we abort the
connection. Here I'm just going straight to the CLOSED state after
aborting the connection, although Comer doesn't mention this.

From CLOSEWAIT, we wait for the application to close the connection,
then send a fin and go to LASTACK. Normally I think there's some
sending of final data here too.

From LASTACK, if we get a syn, we send a reset and abort the
connection. If we get a reset, we abort the connection. It seems to me
we should be waiting for an ack to our fin, but Comer doesn't mention it.

From CLOSED, we are quiescent, although if we get any actual data on a
closed channel we should sent a reset (not shown).

Note that Comer doesn't always explicitly define each state transition.
Those that I couldn't find I just let them throw an error. This might
be wrong, but I felt was safest.

Note also that the State pattern "Context" here is called TcpConnectionTest.

Each state is discreet and does its own thing. It's not affected by
other states, the code is nicely encapsulated, and it's easy to extend
by adding more states and more transitions. Each state here has a
reference to its context (TcpConnectionTest). The state drives the
processing on the context, and also sets the next state when a
transition is called for. The context itself doesn't really know how
states progress, but it does provide methods for the states to call when
they need something done. This is normal for the State pattern, afaik.

Also, I'm using inner classes here, but that's only for a usenet
example. I could have used an implicit point to the enclosing class
(TcpConnectionTest) but that's not part of the State pattern. So I use
static inner classes and I pass a reference to the context via each
constructor, which is part of the State pattern.

I'd suggest you get a good introductory book to design patterns, such as
Head First Design Patterns.

/*
Copyright 2011 Brenden Towey. All rights reserved.
*/

package test;

/**
*
* @author Brenden Towey
*/
public class TcpConnectionTest {

private TcpState state;

public TcpConnectionTest() {
state = new Established( this );
}

public void reset() {
state.reset();
}

public void close() {
state.close();
}

public void fin() {
state.fin();
}

private void sendReset() {
System.out.println("Reset");
}

private void doAbort() {
System.out.println("Abort");
}

private void sendFin() {
System.out.println("Fin");
}

private static abstract class TcpState {
final TcpConnectionTest connection;

public TcpState(TcpConnectionTest connection) {
this.connection = connection;
}
abstract void fin();
abstract void syn();
abstract void close();
abstract void reset();
}

private static class Established extends TcpState {

public Established( TcpConnectionTest con ) {
super( con );
}


@Override
void fin() {
connection.state = new CloseWait( connection );
}

@Override
void syn() {
connection.sendReset();
connection.doAbort();
connection.state = new Closed( connection );
}

@Override
void close() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
void reset() {
connection.doAbort();
connection.state = new Closed( connection );
}
}

private static class CloseWait extends TcpState {

public CloseWait(TcpConnectionTest connection) {
super( connection );
}

@Override
void fin() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
void syn() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
void close() {
connection.sendFin();
connection.state = new LastAck( connection );
}

@Override
void reset() {
throw new UnsupportedOperationException("Not supported yet.");
}

}

private static class LastAck extends TcpState {

public LastAck(TcpConnectionTest connection) {
super( connection );
}

@Override
void fin() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
void syn() {
connection.sendReset();
connection.doAbort();
connection.state = new Closed( connection );
}

@Override
void close() {
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
void reset() {
connection.doAbort();
connection.state = new Closed( connection );
}

}

private static class Closed extends TcpState {

public Closed(TcpConnectionTest connection) {
super(connection);
}

@Override
void fin() {
}

@Override
void syn() {
}

@Override
void close() {
}

@Override
void reset() {
}
}
}
 
L

Lew

Robert said:
Just so I understand it properly: are you saying that with hotspot the
compiler might remove members of the instances?  I am not sure how that

More the other way around: if it sees an opportunity to enregister
members it will remove the instance from the members.
would work since then hotspot would need to create several different
versions of the property getter methods (one per instance).  I believe
this is not what hotspot can do.

Yes. So? that's what HotSpot does, except it's not one per instance,
it's N >= 1 per instance, potentially. HotSpot optimizes for
individual hot spots in the code, hence the name.

Yes, it wins, but that has nothing to do with memory or performance in
the JVM.

For the toy example given I would expect no difference, since the
final variables ocmpile to constants anyway.
Well, roughly speaking the idea was this:https://gist.github.com/892503#file_valve.java

Side channel should not be necessary to make the point.
Here there are boolean properties which derive from the enum, i.e.
whether there is traffic allowed or not and whether the traffic can flow
unlimited.

Now code which uses this enum need not create switches based on concrete
enum instances but can use boolean properties in control flow (e.g.
print a warning if no traffic at all is allowed).  One might later want

THe use of values in a 'switch' doesn't seem relevant to the decision
between your approaches at all.
to add another enum value BROKEN(false, false) which is used to describe
the state of a broken medium.  If this is done the code that uses those
properties to make decisions does not need to be extended.

Enums are not the most amenable to that kind of refactoring
regardless. But the so-called "properties" approach sure is easier
for that situation than the so-called "methods" approach, and for the
same reason I like it in the first place.

As for JVM effects, I would expect the two approaches to be
indistinguishable. Constant variables are compiled into constants in
the code, so they get treated identically with constants (being, after
all, constants in truth) at run time.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top