8
votes

I've been using a lot Perl hashes due to super flexibility and convenient. for instance, in Perl I can do the following:

$hash{AREA_CODE}->{PHONE}->{STREET_ADDR}

I wondering how can I accomplish the same thing with Java, I guess it has something to do with HashMap?

Thanks,

8
Now if you described what that Perl code actually does, people who know Java but not Perl could help you directly without having to guess... - Michael Borgwardt
Sidenote: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR} is fine as $hash{AREA_CODE}{PHONE}{STREET_ADDR}. - Ashley
@Ashley - I would argue that the former (using full ->) is actually more readable/maintainable than the shorthand version omitting the arrows, but I know this opinion is not widely shared. - DVK
@Michael - I agree it's a valid point in general when asking such a type of question; so +1 for the comment :). However, there's a sufficient amount of Perl developers who're very proficient in Java that it is not likely to lead to dearth of answers (as evidenced by the answers submitted within 2 hours on a weekend :) ) - DVK

8 Answers

18
votes

I've been using a lot Perl hashes due to super flexibility and convenient. for instance, in Perl I can do the following: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR} I wondering how can I accomplish the same thing with Java, I guess it has something to do with HashMap?

The Java code which approximates the following Perl code:

my %hash;
$hash{AREA_CODE}{PHONE}{STREET_ADDR} = "221B Baker Street";
printf "Street address is %s\n", $hash{AREA_CODE}{PHONE}{STREET_ADDR};

is

HashMap<String, HashMap<String, HashMap<String, String>>> hash =
    new HashMap<String, HashMap<String, HashMap<String, String>>>();

hash.put("AREA_CODE", new HashMap<String, HashMap<String, String>>());
hash.get("AREA_CODE").put("PHONE", new HashMap<String, String>());
hash.get("AREA_CODE").get("PHONE").put("STREET_ADDR", "221B Baker Street");

System.out.printf("Street address is %s\n",
    hash.get("AREA_CODE").get("PHONE").get("STREET_ADDR"));

Isn’t that special? :)

I say ‘approximates’ for many reasons. One of these is that in Java you’ll be frustrated to the point of extreme apoplexy merely for wanting to then do on the next line of Java the equivalent of this perfectly straightforward Perl code:

$hash{AREA_CODE}{PREFIX} = 800;

If you want Perl’s flexibility and convenience in things like this, Java simply isn’t going to give it to you. Even worse, its partisans will often berate you for even expressing such a desire.

6
votes

First of all, your specific example ($hash{AREA_CODE}->{PHONE}->{STREET_ADDR}), with hard-coded strings as hash keys, is not really a useful data structure in Java as Michael Carman pointed out - it should be stored as a class with attributes (and to be honest it's a bad data structure in concept - data like this is more likely to be an array of phones, not hash of phones).

Second, assuming you actually meant $hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR}, it looks like everyone's Java code so far was NOT implementing a generic equivalent code - the code all assumed that the Java hash is newly initialized for storing example OR fully populated for retrieval example (in other words, as leonbloy's answer noted, is missing autovivification feature).

The correct code mimiquing autovivification is:

// This method will ensure that hash-of-hash-of-hashes structure exists of a given set of 3 keys.
public HashMap<String, HashMap<String, HashMap<String, Object>>>
 autovivification_3rd_level (
           HashMap<String, HashMap<String, HashMap<String, Object>>> hash
         , String AREA_CODE, String PHONE, String STREET_ADDR) {
    if (hash == null) {
        hash = new HashMap<String, HashMap<String, HashMap<String, Object>>>();
    }
    if (!hash.contains(AREA_CODE) || hash.get(AREA_CODE) == null) {
        hash.put(new HashMap<String, HashMap<String, Object>>());
    }
    HashMap<String, HashMap<String, Object>> AREA_CODE_hash
         = (HashMap<String, HashMap<String, Object>>) hash.get(AREA_CODE);
    if (!AREA_CODE_hash.contains(PHONE) || AREA_CODE_hash.get(PHONE) == null) {
        AREA_CODE_hash.put(new HashMap<String, Object>());
    }
    return hash;
}

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR} = value;"
public Object put_3d_level_hash(
          HashMap<String, HashMap<String, HashMap<String, Object>>> hash
        , String AREA_CODE, String PHONE, String STREET_ADDR,
        , Object value) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).put(STREET_ADDR, value);
}
put_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR, obj);

////////////////////////////////////////////////////////////////////////////////////    

// Equivalent to Perl's "$var = $hash{$AREA_CODE}->{$PHONE}->{$STREET_ADDR}"
public Object get_3d_level_hash(HashMap<String, HashMap<String, HashMap<String, Object>>> hash
                       , String AREA_CODE, String PHONE, String STREET_ADDR) {
    hash = autovivification_3rd_level(hash, AREA_CODE, PHONE, STREET_ADDR);
    return hash.get(AREA_CODE).get(PHONE).get(STREET_ADDR);
}
Object obj = get_3d_level_hash(hash, AREA_CODE, PHONE, STREET_ADDR);
5
votes

See the Map interface and its implementations, specially HashMap.

Beware that Java doesn't have Perl's auto-vivification (handy but dangerous feature) so that

hash.get("areaCode").get("phone").get("streetAdr")

will throw an exception if, eg, get(phone) returns null. Beware also that you should not uses hashes for things that have fixed names ("properties"), you should define your own classes with its getters and setters.

2
votes

Java has hashes, but because of strong typing they're not quite as flexible as hashes in Perl. Multidimensional hashes are harder to work with. In Perl, you can just declare a hash and let autovivification create the nested hashes on demand.

my %hash;
$hash{a}{b} = 1;

In Java, you have to declare it to be a hash-of-hashes up-front.

Map<String,Map<String,Integer>> hash = new HashMap<String,HashMap<String,Integer>>();
hash.put("a", new HashMap<String, Integer>());
hash.get("a").put("b", new Integer(1));

For every extra dimension you need to add another nesting of Map<K,V> to the declaration. Aside from being tedious, this isn't very OO.

1
votes

If the hash keys are constant, why won't hash.getAreaCode().getPhone().getStreetAddr() do? Keep in mind that either your getters or your constructors will need to handle default value generation.

1
votes

You can easily subclass your hash to add a method that'll autovivify for you.

From: $hash{AREA_CODE}->{PHONE}->{STREET_ADDR}

To: hash.vivifyingGet(areaCode).put(phone, streetAddr).

Assuming I've created the hash with:

/**
  * A two-level autovivifying hashmap of X and Y to Z. Provides
  * a new method #vivifyingGet(X) which creates the next level of hash.
  */
Map<AreaCode, Map<Phone, StreetAddr>> hash =
    new HashMap<AreaCode, Map<Phone, StreetAddr>>() {
    /**
      * Convenience method to get or create the next level of hash.
      * @param key the first level key
      * @return the next level map
      */
    public Map<Phone, StreetAddr> vivifyingGet(Phone key) {
        if (containsKey(key)) {
            return get(key);
        } else {
            Map<Phone, StreetAddr> = hash = new HashMap<Phone, StreetAddr>();
            put(key, hash);
            return hash;
        }
    }
};
0
votes

I missed the perl hashes a lot in my work and made some ugly workarounds with hash classes.

Last week I had an idea to implement the whole thing in one PerlMap class which use delimiters to access objects and foremost the Lists zu access subsets.

It works fine with map.get(code:street:phone) and map.put(code:street:phone,"123456789"). To get a list of phonenumber you just use map.getList(code:street).

I've just started but use in my project now. It has no limitations of complexity :-) and you can choose the delimiter free. I put the whole stuff under http://www.jdeer.org. Have fun.

-4
votes

You're probably going to want to go with Groovy if you want this sort of flexibility but still run within the JVM. tchrist likes to ignore the point that Java is strong-typed as opposed to dynamic-typed languages like Perl or PHP - and also likes to ignore that Java is an order of magnitude faster at running, but that's just me being a "partisan", apparently.