Using Enumerated Types as Array Indexes


K

KevinSimonson

Ada, the programming language mandated for the military for a time,
was a wonderful language that didn't deserve to die. But it did, and
now Java is alive and well and nobody seems to be doing anything at
all with Ada.

Java is a pretty handy language in its own right. But in Ada one
could define arrays to be indexed by enumerated types. Can Java do
that? If not, why not?

I wrote a piece of code that implements an array of <String>s, indexed
by objects of class <Coord>, an enumerated type, that I'm including
below. Obviously the functionality of this class could be copied to
make it possible to create _virtual_ arrays of _any_ element class,
indexed by _any_ enumerated type. Isn't this precisely the thing
generics were designed for? But how does one write a generic class to
implement an array indexed by enumerated types?

I've also included below three attempts to create a generic type that
implements an array indexed by enumerated types, but none of them
compile. Can anyone give me some pointers on this?

Kevin Simonson



Script started on Tue Aug 16 06:47:31 2011
sh-4.1$ ls
ArrayEnum.class Coord.class EnumArray2.java Offsetable.java
ArrayEnum.java EnumArray1.java EnumArray3.java
sh-4.1$ : First the Java file that _does_ work.
sh-4.1$ cat ArrayEnum.java
enum Coord { X_LFT, Y_LFT, X_RHT, Y_RHT }

public class ArrayEnum
{
String[] arrEnm;

public ArrayEnum ()
{
arrEnm = new String[ Coord.values().length];
}

public void set ( Coord index
, String element)
{
arrEnm[ index.ordinal()] = element;
}

public String get ( Coord index)
{
return arrEnm[ index.ordinal()];
}

public static void main ( String[] arguments)
{
if (0 < arguments.length && arguments.length % 2 == 0)
{ ArrayEnum demo = new ArrayEnum();
Coord[] allCoords = Coord.values();
Coord chosen;
int index;
for (Coord enm : Coord.values())
{ demo.set( enm, enm + "_orig_value");
}
System.out.println( "Before setting values:");
for (Coord enm : Coord.values())
{ System.out.println
( "demo.get( " + enm + ") == \"" + demo.get( enm) + "\".");
}
for (int arg = 0; arg < arguments.length; arg += 2)
{ index = -1;
for (;;)
{ if (++index == allCoords.length)
{ chosen = null;
break;
}
if (arguments[ arg].toUpperCase().equals( "" +
allCoords[ index]))
{ chosen = allCoords[ index];
break;
}
}
if (chosen != null)
{ System.out.println
( "demo.set( " + chosen + ", \"" + arguments[ arg + 1] +
");");
demo.set( chosen, arguments[ arg + 1]);
}
else
{ System.out.println
( "Couldn't match argument \"" + arguments[ arg]
+ "\" with a <Coord>
value!");
}
}
System.out.println( "After setting values:");
for (Coord enm : Coord.values())
{ System.out.println
( "demo.get( " + enm + ") == \"" + demo.get( enm) + "\".");
}
}
else
{ System.out.println( "Usage is");
System.out.println( " java ArrayEnum (<coord> <accompanying-
string>)+");
}
}
}
sh-4.1$ : I compile it.
sh-4.1$ javac ArrayEnum.java
sh-4.1$ : It compiles without error messages, so I run it with some
values.
sh-4.1$ java ArrayEnum x_rht Kevin x_lft Sandy y_rht Joshua
Before setting values:
demo.get( X_LFT) == "X_LFT_orig_value".
demo.get( Y_LFT) == "Y_LFT_orig_value".
demo.get( X_RHT) == "X_RHT_orig_value".
demo.get( Y_RHT) == "Y_RHT_orig_value".
demo.set( X_RHT, "Kevin);
demo.set( X_LFT, "Sandy);
demo.set( Y_RHT, "Joshua);
After setting values:
demo.get( X_LFT) == "Sandy".
demo.get( Y_LFT) == "Y_LFT_orig_value".
demo.get( X_RHT) == "Kevin".
demo.get( Y_RHT) == "Joshua".
sh-4.1$ : Then the three attempts at generic implementations, with the
sh-4.1$ : compilation error messages that accompanied them.
sh-4.1$ cat EnumArray1.java
import java.util.Iterator;

public class EnumArray1< Ty, En extends Enum< En>> implements Iterator
{
Ty[] enumArray;
En nextToRead;

public EnumArray1 ()
{
En[] vlues = En.values();
enumArray = new Ty[ vlues.length];
nextToRead = 0 < vlues.length ? vlues[ 0] : null;
}

public Ty get ( En index)
{
return enumArray[ index.ordinal()];
}

public void set ( En index
, Ty vlue)
{
enumArray[ index.ordinal()] = vlue;
}

public int size ()
{
return enumArray.length;
}

public boolean last ( En enm)
{
return enm.ordinal() + 1 == enumArray.length;
}

public boolean first ( En enm)
{
return enm.ordinal() == 0;
}

public En succ ( En enm)
{
int index = enm.ordinal() + 1;
return index < enumArray.length ? En.values()[ index] : null;
}

public En pred ( En enm)
{
int index = enm.ordinal() - 1;
return 0 <= index ? En.values()[ index] : null;
}

public boolean hasNext()
{
return nextToRead != null;
}

public Object next ()
{
Object nxtEnm = (Object) nextToRead;
nextToRead = next( nextToRead);
return nxtEnm;
}

public void remove ()
{
}
}
sh-4.1$ javac EnumArray1.java
EnumArray1.java:10: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
En[] vlues = En.values();
^
EnumArray1.java:11: generic array creation
enumArray = new Ty[ vlues.length];
^
EnumArray1.java:44: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
return index < enumArray.length ? En.values()[ index] : null;
^
EnumArray1.java:50: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
return 0 <= index ? En.values()[ index] : null;
^
EnumArray1.java:61: next() in EnumArray1<Ty,En> cannot be applied to
(En)
nextToRead = next( nextToRead);
^
5 errors
sh-4.1$ cat EnumArray2.java
import java.util.Iterator;

public class EnumArray2< Ty, En extends Enum< En>> implements Iterator
{
Object[] enumArray;
En nextToRead;

public EnumArray2 ()
{
En[] vlues = En.values();
enumArray = new Object[ vlues.length];
nextToRead = 0 < vlues.length ? vlues[ 0] : null;
}

public Ty get ( En index)
{
return (Ty) enumArray[ index.ordinal()];
}

public void set ( En index
, Ty vlue)
{
enumArray[ index.ordinal()] = (Object) vlue;
}

public int size ()
{
return enumArray.length;
}

public boolean last ( En enm)
{
return enm.ordinal() + 1 == enumArray.length;
}

public boolean first ( En enm)
{
return enm.ordinal() == 0;
}

public En succ ( En enm)
{
int index = enm.ordinal() + 1;
return index < enumArray.length ? En.values()[ index] : null;
}

public En pred ( En enm)
{
int index = enm.ordinal() - 1;
return 0 <= index ? En.values()[ index] : null;
}

public boolean hasNext()
{
return nextToRead != null;
}

public Object next ()
{
Object nxtEnm = (Object) nextToRead;
nextToRead = next( nextToRead);
return nxtEnm;
}

public void remove ()
{
}
}
sh-4.1$ javac EnumArray2.java
EnumArray2.java:10: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
En[] vlues = En.values();
^
EnumArray2.java:44: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
return index < enumArray.length ? En.values()[ index] : null;
^
EnumArray2.java:50: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
return 0 <= index ? En.values()[ index] : null;
^
EnumArray2.java:61: next() in EnumArray2<Ty,En> cannot be applied to
(En)
nextToRead = next( nextToRead);
^
Note: EnumArray2.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
4 errors
sh-4.1$ cat EnumArray3.java
import java.util.Iterator;

public class EnumArray3< Ty, En extends Offsetable, Enum> implements
Iterator
{
private static class Associated
{
Offsetable value;
Object element;

Associated ( Offsetable enm)
{ value = enm;
}
}

final En INIT_VALUE;
Associated[] enumArray;
En nextToRead;

private void fillValues ( Offsetable enm
, int index)
{
try
{ fillValues( enm.offsetBy( 1), index + 1);
}
catch (ArrayIndexOutOfBoundsException excptn)
{ enumArray = new Associated[ index + 1];
}
enumArray[ index] = new Associated( enm);
}

public EnumArray3 ( En initialValue)
{
INIT_VALUE = initialValue;
nextToRead = initialValue;
fillValues( (Offsetable) initialValue, 0);
}

public Ty get ( En index)
{
return (Ty) enumArray[ index.ordinal()].element;
}

public void set ( En index
, Ty vlue)
{
enumArray[ index.ordinal()].element = (Object) vlue;
}

public int size ()
{
return enumArray.length;
}

public boolean last ( En enm)
{
return enm.ordinal() + 1 == enumArray.length;
}

public boolean first ( En enm)
{
return enm.ordinal() == 0;
}

public boolean hasNext()
{
return nextToRead != null;
}

public Object next ()
{
Object nxtEnm = (Object) nextToRead;
nextToRead = nextToRead.offsetBy( 1);
return nxtEnm;
}

public void remove ()
{
}
}
sh-4.1$ javac EnumArray3.java
EnumArray3.java:40: cannot find symbol
symbol : method ordinal()
location: interface Offsetable
return (Ty) enumArray[ index.ordinal()].element;
^
EnumArray3.java:46: cannot find symbol
symbol : method ordinal()
location: interface Offsetable
enumArray[ index.ordinal()].element = (Object) vlue;
^
EnumArray3.java:56: cannot find symbol
symbol : method ordinal()
location: interface Offsetable
return enm.ordinal() + 1 == enumArray.length;
^
EnumArray3.java:56: operator + cannot be applied to
Offsetable.ordinal,int
return enm.ordinal() + 1 == enumArray.length;
^
EnumArray3.java:56: incomparable types: <nulltype> and int
return enm.ordinal() + 1 == enumArray.length;
^
EnumArray3.java:61: cannot find symbol
symbol : method ordinal()
location: interface Offsetable
return enm.ordinal() == 0;
^
EnumArray3.java:72: incompatible types
found : Offsetable
required: En
nextToRead = nextToRead.offsetBy( 1);
^
Note: EnumArray3.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
7 errors
sh-4.1$ exit
exit

Script done on Tue Aug 16 06:50:57 2011
 
Ad

Advertisements

T

Tom Anderson

Java is a pretty handy language in its own right. But in Ada one could
define arrays to be indexed by enumerated types. Can Java do that?
No.

If not, why not?

Because in Java, arrays are indexed by integers, and values of enumerated
types are objects (which are not integers).

As Patricia has pointed out, there is EnumMap. And your code below is not
too far off doing the same thing.
sh-4.1$ javac EnumArray1.java
EnumArray1.java:10: cannot find symbol
symbol : method values()
location: class java.lang.Enum<En>
En[] vlues = En.values();

Every enum type has a static values() method, but you can't call static
methods polymorphically like this. In fact, i don't think you can call
static methods on type variables at all, although i could be wrong.

However, the Class class does have a getEnumConstants() methods which does
what you want. If the constructor takes a suitable instance of Class, it
can use that method.
EnumArray1.java:11: generic array creation
enumArray = new Ty[ vlues.length];
^

An annoying consequence of Java's approach to generics (type erasure) is
that you can't create arrays of a generic type. But if you have a Class
object for the component type, you can use java.lang.reflect.Array's
newInstance method to create one.
EnumArray1.java:61: next() in EnumArray1<Ty,En> cannot be applied to
(En)
nextToRead = next( nextToRead);
^

There is no single-argument method called next in your code.

Here's an implementation of the constructor which might work (barring a
bit of try-catch around the reflection):

public EnumArray1(Class<En> type) {
En[] values = type.getEnumConstants();
enumArray = Array.newInstance(type, values.length);
nextToRead = values[0];
}

I've dispensed with the length check, because if someone passes in an
empty enum, then as far as i'm concerned, they deserve the
ArrayIndexOutOfBoundsException they'll get.

I haven't gone over your other classes, because hopefully this should
solve all your problems.

Note that the constructor i suggest is used like this:

EnumArray1<RockPaperScissors> rps = new EnumArray1<RockPaperScissors>(RockPaperScissors.class);

Which is a bit annoying. If you are very annoyed, and if you are prepared
to stomach some reflective witchcraft, you can add another, more magical,
constructor:

protected EnumArray1() {
this((Class<En>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}

Which lets you then write a more magical but briefer construction
expression:

EnumArray1<RockPaperScissors> rps = new EnumArray1<RockPaperScissors>() {};

I would suggest, though, that you don't.

tom
 
G

Gene Wirchenko

Ada, the programming language mandated for the military for a time,
was a wonderful language that didn't deserve to die. But it did, and
now Java is alive and well and nobody seems to be doing anything at
all with Ada.

Java is a pretty handy language in its own right. But in Ada one
could define arrays to be indexed by enumerated types. Can Java do
that? If not, why not?

I do not know if this is relevant for you, but C# apparently can.
It involves redefinition of []. I coded a simple case of "one",
"two", and "three" each setting/getting the respective array element
and anything else getting/getting element 0. I assume that an enum
value would work, too.

[snip]

Sincerely,

Gene Wirchenko
 
A

Arne Vajhøj

Ada, the programming language mandated for the military for a time,
was a wonderful language that didn't deserve to die. But it did, and
now Java is alive and well and nobody seems to be doing anything at
all with Ada.

Java is a pretty handy language in its own right. But in Ada one
could define arrays to be indexed by enumerated types. Can Java do
that? If not, why not?

Java is a simpler language than Ada. In Java array indexes are
int and you have byte/short/int/long types and that is it.

You will need to either use values 0..n-1 as array indexes or
switch to one of the Map data types in java.util.

Arne
 
A

Arne Vajhøj

Ada, the programming language mandated for the military for a time,
was a wonderful language that didn't deserve to die. But it did, and
now Java is alive and well and nobody seems to be doing anything at
all with Ada.

Java is a pretty handy language in its own right. But in Ada one
could define arrays to be indexed by enumerated types. Can Java do
that? If not, why not?

I do not know if this is relevant for you, but C# apparently can.
It involves redefinition of []. I coded a simple case of "one",
"two", and "three" each setting/getting the respective array element
and anything else getting/getting element 0. I assume that an enum
value would work, too.

[] in C# can be either array index or using a so called
indexer of a class.

But it is either one or the other or not defined - it is
never redefined.

Arne
 
A

Arne Vajhøj

Er, all primitives. I.e., add char/float/double and references to that.

Sorry.

byte/short/int/long integer types

Point being that you don't have 1900..2011 and similar.

Arne
 
Ad

Advertisements

M

markspace

byte/short/int/long integer types

OK, you mean for indexes? char works too, and long really doesn't -- it
has to be cast to an int. For longs, Java recognizes the loss of
precision using long and requires that you acknowledge that the upper
bits will simply be truncated.
 
L

Lew

OK, you mean for indexes? char works too, and long really doesn't -- it
has to be cast to an int. For longs, Java recognizes the loss of
precision using long and requires that you acknowledge that the upper
bits will simply be truncated.

JLS 10:
"The variables contained in an array have no names; instead they are referenced by array access expressions that use nonnegative integer index values."

IOW, you never index arrays with a byte, char or short. If you try, you get a widening conversion first, and if that results in a negative int value,you got problems.
 
N

Niklas Holsti

KevinSimonson said:
Ada, the programming language mandated for the military for a time,
was a wonderful language that didn't deserve to die. But it did,

No it didn't. The Ada language has been extended and updated regularly;
a new standard is due to appear in 2012. Several compilers are
available, one of the best being gnat, which is a part of gcc and is
freely available on many platforms. See libre.adacore.com and
http://en.wikipedia.org/wiki/Ada_(programming_language).
and now Java is alive and well
Agreed.

and nobody seems to be doing anything at all with Ada.

The number of people working with Ada is certainly smaller than for
Java, but far from zero. Even the USENET group comp.lang.ada is quite
active.
 
A

Arved Sandstrom

JLS 10:
"The variables contained in an array have no names; instead they are referenced by array access expressions that use nonnegative integer index values."

IOW, you never index arrays with a byte, char or short. If you try, you get a widening conversion first, and if that results in a negative int value, you got problems.
I'm feeling slow this morning: how do we get problems with byte->int,
short->int, or char->int again? These are all integral widening
primitive conversions, respectively

byte->int: 8-bit signed 2's complement (2C) -> 32-bit signed 2C,
short->int: 16-bit signed 2C -> 32-bit signed 2C
char->int: 16-bit unsigned -> 32-bit signed 2C

According to common sense and the JLS, all three of these conversions
_exactly_ preserve the original value. How could they not?

AHS
 
D

David Lamb

Java is a simpler language than Ada. In Java array indexes are
int and you have byte/short/int/long types and that is it.

Sure -- but if Enums had been in the language from the beginning, then
it might have occurred to people that array indexes could be "any type
with a method 'int ordinal()'" (with the appropriate caveat about being
in range 0..length-1)
 
Ad

Advertisements

A

Andreas Leitgeb

Arved Sandstrom said:
On 8/16/2011 6:56 PM, Arne Vajh�j wrote:
byte/short/int/long integer types
OK, you mean for indexes? char works too, and long really doesn't -- it
has to be cast to an int. [...]
JLS 10: [...]
IOW, you never index arrays with a byte, char or short. [...]
I'm feeling slow this morning: how do we get problems with byte->int,
short->int, or char->int again?

array-indexing takes integers. That you can also throw byte/short/char
values at them without cast doesn't mean that arrays "take" those, but
only that such values get automatically converted to int before being
taken by the array for indexing.

An example for something really "taking" byte-values:
foo(byte b) { ... }
 
R

Robert Klemme

Sure -- but if Enums had been in the language from the beginning, then
it might have occurred to people that array indexes could be "any type
with a method 'int ordinal()'" (with the appropriate caveat about being
in range 0..length-1)

Or we had an interface Countable { int ordinal(); } which Enum would
implement and which could be used for array indexing. Or... I don't
find it worthwhile to speculate about what could have happened if
something else happened.

And please also notice that there is still EnumMap as Patricia pointed
out - type safe, efficient and all. Just because Ada does it this way
doesn't mean that it's the best way or that other languages must follow
suit.

Kind regards

robert
 
L

Lew

Andreas said:
Arved Sandstrom said:
Lew said:
JLS 10: [...]
IOW, you never index arrays with a byte, char or short. [...]
I'm feeling slow this morning: how do we get problems with byte->int,
short->int, or char->int again?

One gets problems with byte->int and short->int with array indexes the way one gets into trouble with those widening conversions generally. The usualsuspect is the lack of unsigned versions, so widening a (byte)0xA0, for example, would result in a negative index.

One gets into trouble generally in programming when one thinks one thing isgoing on ("index takes a byte") whilst ignoring what's really going on (there's a widening conversion involved). You might get away with it most of the time, but occasionally such things trip you up.

Why think about it imprecisely? Tell us the advantage of that, please.
 
R

Robert Klemme

Andreas said:
Arved Sandstrom said:
Lew wrote:
JLS 10: [...] IOW, you never index arrays with a byte, char or
short. [...]
I'm feeling slow this morning: how do we get problems with
byte->int, short->int, or char->int again?

One gets problems with byte->int and short->int with array indexes
the way one gets into trouble with those widening conversions
generally. The usual suspect is the lack of unsigned versions, so
widening a (byte)0xA0, for example, would result in a negative
index.

One gets into trouble generally in programming when one thinks one
thing is going on ("index takes a byte") whilst ignoring what's
really going on (there's a widening conversion involved). You might
get away with it most of the time, but occasionally such things trip
you up.

Why had a nice issue recently which exactly fits this bill: after over
two months of uninterrupted, completely error free production usage the
application suddenly stopped working giving weird error messages (there
was an issue with the error reporting as well, but let's ignore that for
the moment). Turns out this was the setup:

1. There was an AtomicInteger initialized at startup with 0 which for
every request coming into that system was incremented in a thread safe
manner (incrementAndGet()).

2. The result was used to index into an array with the quite obvious
"arr[n % arr.length]".

3. (left as exercise for the reader)

Anybody who now thinks all is fine should stop coding Java immediately
and go reading the language spec.
Why think about it imprecisely? Tell us the advantage of that,
please.

Some may reason: Someone else might have to pay for the laziness. But
then again, the risk is it might come right back haunting the
originator. Well, no risk, no fun. :)

Cheers

robert
 
M

markspace

1. There was an AtomicInteger initialized at startup with 0 which for
every request coming into that system was incremented in a thread safe
manner (incrementAndGet()).


I'd be shocked if an int actually overflowed when counting requests like
this, but that's all I can think of. 2^31 is such a huge number, I
didn't think you could get there in practical cases.

2. The result was used to index into an array with the quite obvious
"arr[n % arr.length]".

3. (left as exercise for the reader)


Assuming that the AtomicInteger did overflow, then n % y produces
negative numbers if n is negative, a clear AIOOB.

Try : arr[ Math.abs( n ) % arr.length ];
 
Ad

Advertisements

A

Andreas Leitgeb

markspace said:
I'd be shocked if an int actually overflowed when counting requests like
this, but that's all I can think of. 2^31 is such a huge number, I
didn't think you could get there in practical cases.

Depends of course on the average number of requests per second:
at about 800, you'll get first overflow roughly within a month.
2. The result was used to index into an array with the quite obvious
"arr[n % arr.length]".
3. (left as exercise for the reader)

Assuming that the AtomicInteger did overflow, then n % y produces
negative numbers if n is negative, a clear AIOOB.
Try : arr[ Math.abs( n ) % arr.length ];

What was Math.abs(-2147483648) again? ;-)

Rather: arr[ (n%arr.length + arr.length) % arr.length ]
 
E

Eric Sosman

Andreas said:
Lew wrote:
JLS 10: [...] IOW, you never index arrays with a byte, char or
short. [...]
I'm feeling slow this morning: how do we get problems with
byte->int, short->int, or char->int again?

One gets problems with byte->int and short->int with array indexes
the way one gets into trouble with those widening conversions
generally. The usual suspect is the lack of unsigned versions, so
widening a (byte)0xA0, for example, would result in a negative
index.

One gets into trouble generally in programming when one thinks one
thing is going on ("index takes a byte") whilst ignoring what's
really going on (there's a widening conversion involved). You might
get away with it most of the time, but occasionally such things trip
you up.

Why had a nice issue recently which exactly fits this bill: after over
two months of uninterrupted, completely error free production usage the
application suddenly stopped working giving weird error messages (there
was an issue with the error reporting as well, but let's ignore that for
the moment). Turns out this was the setup:

1. There was an AtomicInteger initialized at startup with 0 which for
every request coming into that system was incremented in a thread safe
manner (incrementAndGet()).

2. The result was used to index into an array with the quite obvious
"arr[n % arr.length]".

3. (left as exercise for the reader)

Anybody who now thinks all is fine should stop coding Java immediately
and go reading the language spec.

Yes, but what has this error to do with widening conversions,
or conversions of any kind? It's GIGO, pure and simple.
 
Ad

Advertisements


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

Top