Inner types in Java: how to reduce verbosity?

Member
Posts: 45
Joined: 2006.11
Post: #1
I'm still learning Java, and one of the problems I've been coming across is that, without being able to use a typedef as in C, I frequently have to type out long fully qualified type names. For example:

Code:
package myPackage;

public class OuterClass {
  public enum InnerEnum {THIS, THAT};
}
Code:
...
// in some other class' methods
OuterClass.InnerEnum something = OuterClass.InnerEnum.THIS;

This is just a made up example, but in practice this is making my code incredibly verbose as I have to type "OuterClass.InnerEnum." every time I want to check or set the value of an inner enum (and far worse if I am attempting to use an inner type of a generic class). I could always just promote InnerEnum as an independent type in myPackage but that moves code farther away from where it is used most, and would require a lot of separate files for each of a class's "helper types". And, it doesn't work at all for generics. Not very good design.

Is there any alternative that can, at least locally in a method, make the code less verbose while still using inner enums and types? That is, WITHOUT the use of the empty subclass trick.

-JeroMiya
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #2
does a static import help?
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #3
It seems to me that verbosity is the Java way.

The real irritant to me is typing out complicated generic type parameters all the time. HashMap<Integer, List<Integer>> see if you aren't grumpy having to type that 10 times, nearly in a row.

I should poke Andy, he's a java guy. He probably has something constructive to say.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Moderator
Posts: 452
Joined: 2008.04
Post: #4
Here I am, as summoned!

I've never made a enum as an inner class. I declare them in their own file, and that way, they're accessable as Enum.THIS or Enum.THAT. I'm not sure exactly how you're using them in your code, but usually an enum is used across multiple classes, so it is appropriate to declare them in their own file.

You mentioned a concern about moving the enum away from the place where it's used the most.. but I wouldn't worry about that. I've found the code structure to be better with them declared in their own class, and I haven't run into problems with them being away from where it's used the most. (Although I understand the sentiment). If they really were mostly used only in one spot, you'd only have to more fully qualify the name in a few cases.

You refered to them as "helper types", how many of these do you have? And if there are a lot, what is your situation that calls for so many?
Quote this message in a reply
Member
Posts: 45
Joined: 2006.11
Post: #5
AndyKorth Wrote:You refered to them as "helper types", how many of these do you have? And if there are a lot, what is your situation that calls for so many?

I use them all the time for various classes. Usually the enum enumerates an action you can do with a class. For example, I'm making a Tamagotchi game for my fiancee'. I have a Tama class, representing the little creature. Among the actions you can do are FEED, WATER, PLAY, MEDICINE, and more than I will be implementing later. I represent the action you can do with an enum inside the Tama class.

I could, in this case, write feed(), water(), play(), medicine() methods (in fact this is what I did in the beginning when I just had feed and play), however the code that drives the tama is a lot nicer after I converted all of those methods into a single doAction method, with the Action enum as the argument.

You see, in my simple GUI code, each of the gui buttons are assigned one of these actions, so I just retrieve the action enum value from the button that was clicked and send it to the tama. This is great because now if I want to add an action, all I have to do is:
1) add the new action to the enum.
2) assign the new action enum value to a new button
3) and implement it in a method that doAction calls when it receives the new action enum value.

Using separate methods for each action moves the switch statement from the doAction method to the code that drives the tama. I'd rather have it at the Tama level and keep my driver code clean.

Coincidently, I also have an inner Enum in Tama for the growth stage, though there is less of a need of that using an enum.

This kind of design has worked well for me in the past, and I've used it for inner types like callback objects, iterator-like inner classes, etc..

If I had to create a new file for each and every one of them, the number of files in any given project would go up by at least 25%. It would be less readable since someone looking at my code would see TamaActionEnum's and other inner types being used everywhere in the code, but defined in a separate file from where they have the most meaning.

That, and I would have namespace issues, since a particular design pattern (such as a callback object or listener).

Anyway I'm rambling. It sounds like this is something I'm just going to have to deal with. One of the tradeoffs of using a "safe" language.

Regards,
JeroMiya

PS: what is a static import?

edit: Ah, static import does indeed alleviate the problem, if used sparingly.
for those who are reading and don't know what static import is, here's an example:

Code:
import static java.lang.Math.cos;
import static java.lang.Math.PI;

public class Foo {

  static public double doSomething(double val) {
    // note "cos" and "PI" used without "java.Math."
    return cos(2 * PI * val);
  }
}

Here's a question google couldn't answer, is there a way to do the same thing but limit its scope locally to a method? For example, in C++ you can do a "using namespace std;" within a method to limit the promotion of those symbols (to the global namespace) within the method.
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #6
http://java.sun.com/j2se/1.5.0/docs/guid...mport.html

Sounds like reflection could do exactly what you use your enums for, with less code.
Quote this message in a reply
Member
Posts: 45
Joined: 2006.11
Post: #7
OneSadCookie Wrote:http://java.sun.com/j2se/1.5.0/docs/guid...mport.html

Sounds like reflection could do exactly what you use your enums for, with less code.

Could you elaborate? I'm not sure exactly how reflection could be used for that purpose. Also, from:
http://java.sun.com/docs/books/tutorial/...index.html

Quote:Drawbacks of Reflection
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.

Performance Overhead
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.

Security Restrictions
Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.

Exposure of Internals
Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.

From that it sounds like it would be a bit of overkill anyway.

-JeroMiya
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #8
Well, you want your button "Foo" to be tied to "doFoo()" on a particular object, so you turn "Foo" into "doFoo", look up the method, and call it. No enum jazz.

Reflection is used a lot, so performance has been tuned quite a bit. That said, it's still substantially more expensive than a regular method call. Not that that's an issue with a button-clicked handler Rasp There are third-party libraries that can improve the performance of reflection in certain cases if it turns out to be problematic (which is somewhat unlikely).

The security stuff doesn't seem to be a problem in practice, as long as you stick to calling methods via reflection that you'd normally be allowed to.

Exposure of internals only matters if you actually use it that way. Dynamically looking up a public method by name and calling it is perfectly safe.
Quote this message in a reply
Moderator
Posts: 452
Joined: 2008.04
Post: #9
Right now, I suspect your doAction() method looks something like this?

Code:
public void doAction(Action enum){
  switch(enum){
    case Action.FEED:
        feed(); //or whatever FEED would do...
        return;
    case Action.PLAY:
        play(); //or whatever PLAY would do...
        return;
}

}

One option, (which is probably what One Sad Cookie is directing you towards, although I guess my approach can do it with just polymorphism, so I might want to take that back...) is the command pattern. I've used this in a number of projects, but wherever a player can invoke a lot of different actions, it comes in handy.

I have a called Action (or Command or something similar). It's an abstract class, or even an interface that has one method, action(Tama critter).

Now, I extend that class with classes called "feed, play, water, throw" and whatever else you might do to your unsuspecting critter. Inside Feed.java, the action(Tama poorCreature) method is where it's actually fed.

At the startup of the game, you might want to fill a hashmap:
Code:
HashMap<String, Action> actions = new HashMap<String, Action>();
actions.add("Feed", new Feed());

Then when you create your buttons, (and other things that invoke Actions), you'll find an easy way to do it. The exact way you'd use something like this depends on your situation.

My description has been a little light on details and motivation, but I can explain more if you have questions about this. One advantage is that you can load all these commands at runtime, and add new ones at runtime. Now, when you want to add a new action, you just need to create a tiny class for it, and add a String to your GUI. (Or leave a command line, they can type the command and trigger it from there (Probably not suitable for your game, but hey, maybe for testing command or something...))

I can provide a lot of source if you're interested in more details.
Quote this message in a reply
Member
Posts: 45
Joined: 2006.11
Post: #10
AndyKorth Wrote:Right...

That's a pretty interesting design. I think I understand what you mean. I'd still be interested in seeing some of the real-world code you mentioned just for reference.

(just thinking out loud) One small problem with this is that these Actions are now external to the Tama class, and to apply some of these actions I need access to variables, either directly or indirectly through methods, that I'd rather keep encapsulated. I'm still experimenting with what these actions actually do gameplay wise, so I've added, removed, and changed the meaning of the variables a lot already. If I externalize these actions (making them use public methods to manipulate the variables) then it would frustrate experimentation, since I'd have to change public interface and code in various places (from the different action classes, each presumably in separate files).

One option, if I weren't concerned so much with run-time loading of new actions, would be to implement the actions as methods in Tama, but wrap them in Action objects that call those methods as you've shown.

What do you think? Am I putting too much responsibility on the Tama class? Should gameplay issues like what happens when you feed the Tama be external to the Tama class itself?

-JeroMiya
Quote this message in a reply
Moderator
Posts: 771
Joined: 2003.04
Post: #11
JeroMiya Wrote:[...] interested in seeing some [...] real-world code [...]

http://ask.slashdot.org/comments.pl?sid=...d=19862863

(sorry! Rasp)

[goes back to writing a custom ASTM parser to be used in a ridiculously huge Java project]
Quote this message in a reply
Moderator
Posts: 373
Joined: 2006.08
Post: #12
or what about this:
http://channel9.msdn.com/ShowPost.aspx?PostID=222250
Rasp

ok, ok, I'll get back on topic now...
I think that the actions should be outside of the Tama class, because they could modify variables if you felt like doing that sometime (like the amount of water/food left for you to give them, etc.)
-wyrmmage

Worlds at War (Current Project) - http://www.awkward-games.com/forum/
Quote this message in a reply
Moderator
Posts: 452
Joined: 2008.04
Post: #13
Yeah, I understand your feelings about exposing the Tama class, but I think you'll find that it will work out okay. This way, a private method in Tama is really and actually encapsulated inside Tama, and it can't be used by a new action. Expose only what you need for actions to interact with the Tama, and create easy to use and clear methods in Tama. Since the actions need to interact from outside, there should be less chance of an action "messing up" the fields in a Tama.

With the number of actions I have, it would be absolutely impossible to keep them all inside the Player class.

Does your Tama class implement any interfaces or extend any abstract classes?

Finally, I'll try to remember to post some examples when I get home tonight.
Quote this message in a reply
Moderator
Posts: 452
Joined: 2008.04
Post: #14
This is the code I use to dynamically load my commands:

Code:
private static HashMap<String, CommandIF> parseableCommands;
      
    public synchronized static void loadParser() {
      String[] names = getClassNames(getCommandsDir());
  
        parseableCommands = new HashMap<String, CommandIF>(names.length);


        for (String className : names) {
            try {
                parseableCommands.put(className.toLowerCase(), (CommandIF) createObject("eternalrealms.commands."
                        + className));
            } catch (ClassCastException e) {
                warn("Class <" + className
                        + " is not an command, because it does not implement CommandIF. Ignoring this class.");
            }
        }

    public static final String[] getClassNames(File directory) {
        //dynamically find acceptable commands:
        File[] files = directory.listFiles(filter);
        if (files == null) {
            critical("Dynamically loaded directory not found: " + directory);
        }

        String[] names = new String[files.length];

        for (int i = 0; i < files.length; i++) {
            names[i] = files[i].getName().split("\\.")[0];
        }
        return names;
    }

And then whenever a message is sent to the server, we dispatch the command:

Code:
/**
     * Invoke a command on a connection with a player.
     */
    public static synchronized void invokeParse(String commandName, Player p, Object[] args,
            ConnectionInterface connection) {
        commandName = commandName.toLowerCase();

        //maybe they entered a command
        CommandIF command = parseableCommands.get(commandName);
        if (command != null) {
            //execute the command unless it's a non-admin using an admin command
            if (p.isAdministrator() || (!command.isAdminOnly())) {
                command.execute(p, connection, args);
            } else {
                p.sendText("The " + commandName + " command is restricted to admin only.");
            }
            return;
        }

<<snip some irrelevant code>>

        //I guess they are just confused.
        p.sendText("No command named '" + commandName + "'.");
        warn("No command named <" + commandName + "> for player <" + p.getName() + ">");
    }

And just for fun, in case you were wondering, the interface I use for the commands is:
Code:
/**
* See chapter 13, page 151 of "Agile Software Development" by Martin
*/
public interface CommandIF {
    /**
     * Run the actual command
     * @param issuer - Mobile doing the action represented by this command.
     * @param issuerConnection - Connection of the player, null for Mobiles.
     * @param args
     */
    void execute(MobRef issuer, ConnectionInterface issuerConnection, Object[] args);

    /**
     * Is this command for admin only?
     */
    boolean isAdminOnly();

    int getCommandCategory();

    /**
     * @return Number of action points used by this command.
     */
    int getAP();

    /**
     * Is this considered a hostile command that will initiate combat?
     * @return
     */
    boolean startsCombat();
}

And for my convenience, there are a few more classes between there and the commands.. but here's an example command:
Code:
/**
* The mobile says something, nearby mobs may hear it.
*
* @author kortham
*/
public class Say extends FreeMove {

    /* (non-Javadoc)
     * @see commands.CommandIF#execute(src.Player, java.lang.Object[])
     */
    public void execute(MobRef issuer, ConnectionInterface issuerConnection, Object[] args) {

        String spoken;
        if (args == null || (spoken = (String) args[0]) == null) {
            issuer.sendText("What do you want to say?");
            return;
        }

        //Consider supporting multiple languages here.
        for (MobRef m : issuer.getNearbyMobiles(Config.instance.SAY_HEARING_DISTANCE)) {
            if (spoken.endsWith("?")) {
                m.sendText(issuer.getName() + " asks \"" + spoken + "\"");
            } else if (spoken.endsWith("!")) {
                m.sendText(issuer.getName() + " exclaims \"" + spoken + "\"");
            } else {
                m.sendText(issuer.getName() + " says \"" + spoken + "\"");
            }

        }
    }

    public boolean isAdminOnly() {
        return false;
    }

    public int getCommandCategory() {
        return Parseable.COMMAND_GENERAL;
    }
}

Sorry for the length of the post, hopefully it's helpful!
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Haskell Data Types and Functions wyrmmage 1 3,343 Mar 4, 2008 05:25 PM
Last Post: OneSadCookie