Why local value is changing after updating hashmap

I don’t understand why after getting value from hashmap and updating hashmap, the local value changing at updated value. I always thought that java works on pass by value not by reference.

@Component
@RequiredArgsConstructor
public class ParametersCompare {

    @NonNull
    private final ParameterRepository parameterRepository;

    public boolean isAnyChange(String object, List<Parameter> currentParameter) {
        Map<String, String> parameterHistory = parameterRepository.getHistoricalParameter(object);
        parameterRepository.updateParameters(currentParameter, object);
        return isAnyChange(parameterHistory, currentParameter);
    }


@Service
public class ParameterRepository  {

    private final Map<String, Map<String, String>> oldParameters = new TreeMap<>();

    public void updateParameters(List<Parameter> currentParameters, String object) {
        Map<String, String> oldParameters = this.oldParameters.computeIfAbsent(object, s -> new HashMap<>());
        updateParameters(currentParameters, oldParameters, object);
    }

    public Map<String, String> getHistoricalParameter(String object) {
        Map<String, String> currentParameters = this.oldParameters.get(object);
        if (object == null) {
            return Collections.emptyMap();
        } else {
            return currentParameters;
        }
    }


    private void updateParameters(List<Parameter> currentParameters, Map<String, String> oldParameters, String object) {
        currentParameters.forEach(parameter -> oldParameters.put(parameter.getName(), parameter.getValue()));
        this.oldParameters.put(object, oldParameters);
    }
}

After line

parameterRepository.updateParameters(currentParameter, object);

oldParameters is changing to variable received from currentParameter.

Thanks in advance for pointing why is changing.

best regards

Answer

Java is pass-by-value on all fronts, yes, but remember that all non-primitive values are references. For non-primitives, the values you pass around are always treasure maps and never the treasure. = wipes out the old ‘X marks the spot’ and draws a new X in, and . (as well as [] and a few others, like synchronized(ref)) are java-ese for `follow the X, find a shovel, dig down, and operate on the treasure you find’.

List<String> hello = new ArrayList<String>();
  1. conjures up a new treasure chest out of thin air. Bury it someplace on the vast beach. Like all objects, it has no name.

  2. conjure a treasure map out of new air. It is named hello.

  3. Draw an X on the hello map, marking the position where you buried the chest you made in X.

foo(hello);

Make a copy of your hello map, and hand the copy of the map to foo. If foo reassigns their map (they do param = somethingElse;), your map does not change, as they are operating on their copy. If, however, they follow their map and dig down, well, they find the same treasure you would find on your map:

List<String> list = new ArrayList<String>();
op1(list);
System.out.println(list);

public void op1(List<String> list) {
    list.add("HELLO");
}

public void op2(List<String> list) {
    list = List.of("HELLO");
}

In this code, it prints HELLO, because op1 follow its map and modified the treasure. If you replace it with a call to op2, nothing is printed; op2 makes new treasure and updates its own map which you will not observe, as java is indeed pass-by-value.

so, how can I use variable from map as value not as reference?

Given that variables are references, you are doing it. Presumably you want: “How do I ensure that the method I hand this map to gets its own clone?” and the answer is pretty much in that restated question: By cloning. There is nothing baked into java, because objects need to represent data concepts that are cloneable in the first place. For example, you can’t feasibly clone any InputStream representing an underlying (OS-level) file handle. Also, cloning a huge array or any other object with a large internal data store would then be extremely expensive. Furthermore, any object that has a field of such a non-cloneable type would itself then also be non-cloneable, so non-cloneables are all over the place, and that perhaps explains why java has no baked in support for it.

Most data types where you’d want to make clones DO however have APIs that let you do that:

List<String> original = new ArrayList<String>();
original.add(...);
List<String> clone = new ArrayList<String>(original);

Now clone is a truly full clone. It’s a shallow copy, which in this case is irrelevant, as String is an immutable datatype. That means the treasure chest is made from solid titanium – nobody can mess with it or move it, so you can hand out copies of a map that leads to it with wild abandon – it will never effect you. That’s why immutable data types are often quite convenient. No worries about cloning and handing out references (=treasure maps).

Leave a Reply

Your email address will not be published. Required fields are marked *