0

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

kamil wilk
  • 117
  • 7
  • `@Component` `@RequiredArgsConstructor` doesn't look like standard come from *standard* Java. If those are relevant to the question consider adding tags relevant to those annotations to attract experts which may be using that technology. If they are not important then consider removing them from your example and leave simple Java classes/methods. – Pshemo Mar 20 '21 at 18:00
  • 2
    https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value – chptr-one Mar 20 '21 at 18:00
  • "I thought Java already works on pass-by-value not by reference" -- ...errr... no. Only for primitives, but not for objects. It'd be really silly (and slow) if you had to allocate enough memory to make a copy of every object you passed as a parameter, and that would mean you couldn't pass objects to functions that were expected to modify those objects and let the caller interact with the modified version later (at least, not without returning the updated version, and since Java doesn't support multiple return values from a function, that would be messy). – Charles Duffy Mar 20 '21 at 18:01
  • @CharlesDuffy thanks - so, how can I use variable from map as value not as reference? – kamil wilk Mar 20 '21 at 18:07
  • As someone who lives in the Clojure-verse, I don't recommend making in-place changes to objects _at all_ -- a method that creates a new value should be creating a new object to store that value in. Granted, if you live the more regular Java ecosystem you don't have tools that make it easy (and high-performance) to do that as part of your usual update primitives, so you end up copying objects explicitly. – Charles Duffy Mar 20 '21 at 18:09
  • 1
    @CharlesDuffy "*"I thought Java already works on pass-by-value not by reference" -- ...errr... no. Only for primitives, but not for objects.*" well, actually answer is more like *yes*, since we pass to method *value* held by reference-type-variable used while invoking method. That variable holds *identifier* of object (which is just a number JVM uses to identify objects - and is what we call *reference* in Java). So while invoking a method *copy of that number* (*value* of variable) is being passed to method parameter, which allows it to manipulate the object but not original variable. – Pshemo Mar 20 '21 at 18:20
  • 3
    Before going deeper into pass-by-value and pass-by-reference in Java this question should be read first: [What is the difference between a variable, object, and reference?](https://stackoverflow.com/q/32010172) – Pshemo Mar 20 '21 at 18:24

1 Answers1

1

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).

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72