2

While I have been programming for years, I am new to Java. I have run into a problem with HashMaps that I'm not sure how to resolve. I thought I searched this site pretty well, but I must not be using the right terms, as I don't think I am the first person to run into this problem. I have to believe there is a simple solution I am just not seeing (forest for the trees).

I have a program that uses HashMaps to pass around a lot of variables back and forth between classes. This was done because we are interfacing various systems contain non-harmonious data that we are trying to process within the program.

What I discovered, after much hair pulling (not that I had much to begin with), is that when a HashMap is "returned" by a method in a class, the values are not returned to the calling class's procedure. Rather a pointer to HashMap in the "called" class procedure is returned. Subsequent modifications to a "copy" of HashMap in the "calling" class's method are reflected in the called class's HashMap even though the called class's HashMap is marked private (and the classes are in different packages!).

Likewise, any modifications to the "called class's HashMap" are also reflected in the calling class's HashMap.

Confused? So was I, so I boiled it all down to small bit of code that reproduces what I found. (It can probably be shorten even more but I was trying to preserve some of the levels I am using.)

  1. TMain.java - default package:

    import mainLogic.MainLogic;
    
    public class TMain {
        private static MainLogic  ml = new MainLogic();
    
        public static  void  main(String[] args) { 
            ml.performMainLogic();
        }   
    }
    
  2. MainLogic.java - the "calling class"

    package mainLogic;
    import java.util.HashMap;
    import subLogic.SubLogic;
    
       public class MainLogic {
    
           private HashMap<String,String> slValues;
    
       public MainLogic() {
         // do constructor stuff here
       }
    
       public void performMainLogic(){
           SubLogic sl = new SubLogic();
    
           sl.performSubLogic();
    
           // returns pointer rather than values?
           slValues = sl.getSLHashUser();
           showSLValues("MainLogic - after calling sl.getSLHashUSer");
           sl.showSLHashUser("MainLogic - calling sl.showSLHashUserafter calling sl.getSLHashUser");
    
           // affects both slValues AND slHash in the SubLogic Class!!!!!
           slValues.put(SubLogic.USER_FLD1,"NEWVALUE");
           showSLValues("MainLogic - slValues after put");
           sl.showSLHashUser("MainLogic - calling SubLogic showSLHashUser after slValues put");
    
           // modifies slHash in SubLogic Class  AND slValues here!!!!!
           sl.doSubLogic2();
           showSLValues("MainLogic - slValues after doSubLogic2");
           sl.showSLHashUser("MainLogic - calling SubLogic showSLHashUser after doSubLogic2");
    
        }       
    
        public void showSLValues(String title) {
            System.out.println(title);
            System.out.println(" slValues");
            System.out.println("  Field 1 :\t" + slValues.get(SubLogic.USER_FLD1)  );
            System.out.println("  Field 2 :\t" + slValues.get(SubLogic.USER_FLD2)  );
            System.out.println("");
        }
    }
    
  3. SubLogic - the "called class"

    package subLogic;
    import java.util.HashMap;
    
    public class SubLogic {
    
        private HashMap<String,String> slHashUser = new HashMap<String, String>();
    
        // field names slHashUser
        static public final String USER_FLD1  = "ONE";
        static public final String USER_FLD2  = "TWO";
    
        public SubLogic() {
            slHashUser.put(USER_FLD1,  "SubLogic1");
            slHashUser.put(USER_FLD2,  "SubLogic2");
            showSLHashUser("SubLogic - Constructor");
        }
    
        public void performSubLogic(){
            slHashUser.put(USER_FLD1,  "PSubLogic1");
            slHashUser.put(USER_FLD2,  "PSubLogic2");
            showSLHashUser("SubLogic - performSubLogic");
        }
    
        public void doSubLogic2(){
            slHashUser.put(USER_FLD1,  "modified");
            showSLHashUser("SubLogic - doSubLogic");
        }
    
        public HashMap<String,String> getSLHashUser() {
            showSLHashUser("SubLogic - getSubLogic");
            return slHashUser;
        }
    
        public void showSLHashUser(String title) {
            System.out.println(title);
            System.out.println(" slHash Values");
            System.out.println("  Field 1 :\t" + slHashUser.get(USER_FLD1)  );
            System.out.println("  Field 2 :\t" + slHashUser.get(USER_FLD2)  );
            System.out.println("");
       }
    }
    

My first thought is that I need do something different in MainLogic at the line:

    slValues = sl.getSLHashUser();

but I wasn't sure if the real problem was in SubLogic's getSLHashuser method where I tried to do the return:

    return slHashUser;

Since java is supposed to be all about "passing by value" and "pass by reference" (in the old school "C" sense), I just wasn't expecting this.

Your help will be appreciated, even if it is a pointer to the correct documentation that I must have overlooked.

Linus12
  • 43
  • 6
  • This is somehow the opposite of http://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value . Long story short: Imagine that every reference in Java (roughly) behaves like a "Pointer" in C. (`NullPointerException`, *wink*) – Marco13 May 17 '15 at 22:58

2 Answers2

2

"Confused?"

No. This is the way that mutable objects work in Java.

Since java is supposed to be all about "passing by value" and "pass by reference" (in the old school "C" sense), I just wasn't expecting this.

Java method arguments and results are exclusively pass-by-value.

However, there is a subtlety that a lot of newbies miss. When you pass or return an object it is the object reference that is passed and copied ... not the state of object itself.

But if you bear that subtle point in mind, what you observed is both explicable, and natural.

If your application requires that a copy is made when you return a HashMap, then you will need to do the copying explicitly. Fortunately, HashMap has a "copy constructor" which produces a new HashMap that is a shallow copy of the state of an existing Map.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • Thank you for the detailed explanation. All languages seem to have different subtleties that are often missed. And I missed this one. I would like to "upvote" this one, but my reputation doesn't seem to be good enough yet! – Linus12 May 17 '15 at 18:05
1

When a method returns a reference to a mutable object, the caller of that method can mutate that object by calling method of that reference, thus mutating the state of the object for which the method was called.

To prevent such modifications, you can return a copy of the HashMap :

public HashMap<String,String> getSLHashUser() {
    showSLHashUser("SubLogic - getSubLogic");
    return new HashMap<String,String>(slHashUser);
}

This way, changing the returned HashMap won't affect the content of the slHashUser member.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Thanks for the actual code snippet to address the issue. This too deserves an upvote, but again, my reputation doesn't seem to be good enough yet to allow me to do that. – Linus12 May 17 '15 at 18:06