0

First, I wanna show my code:

public static void main(String[] args)
    {
        Map<String,Integer> map = new LinkedHashMap<String,Integer>();
        map.put("bye",0);
        map.put("Hello",1);
        
        System.out.println(Arrays.toString(map(map, new BiConsumer<String,Integer>(){
            @Override
            public void accept(String s, Integer i){
                s=s+"s";
            }
        })));
    }
    
    public static String[] map(Map<String,Integer> m, BiConsumer<String,Integer> bif){
        String[] key = m.keySet().toArray(new String[0]);
        String[] entries = new String[m.size()];
        for (int i = 0; i < m.size(); i++) {
            bif.accept(key[i],m.get(key[i]));
            entries[i] = key[i] + ", " + m.get(key[i]);
        }
        return entries;
    }

As you can see, I would like to develop a method that accepts a Map<String, Integer> and BiConsumer<String, Integer>, that does what the passed function wants to do with every key and value from the map and that finally returns a String array.

One could say: a method that does roughly that what forEach does: https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html#forEach-java.util.function.BiConsumer-

I was able to add the BiConsumer interface because it doesn't depend on other Java 8 classes.

I need to develop this method without changing anything in the following code as possible and only with Java 7:

System.out.println(Arrays.toString(map(map, new BiConsumer<String,Integer>(){
            @Override
            public void accept(String s, Integer i){
                s=s+"s";
            }
        })));

Expected result:

[byes, 0, Hellos, 1]

But I am getting:

[bye, 0, Hello, 1]

So I don't know, whether I should change the string within a method that is just outside the method.

Do you have any ideas?

Aaron
  • 55
  • 5

2 Answers2

1

Unfortunately, as you may have experienced, Java Strings are immutable. This means, whenever you write something like s = s + s;, you are creating a new object. Additionally, Java is always pass-by-value. So any Object references (like the String here in the accept method) are also passed by value. This means doing things like -

void method(String s){
    s = new String();
}

will also not change the reference outside the method.

There are some ways to get around this but it's a bit of a hassle. You essentially store the String reference in a new class, then operate on that. Here's a demonstration:

public class StringRefContainer{
    public String str;
    StringRefContainer(String str){this.str = str;}
}

// example accept method
void accept(StringRefContainer s, Integer i){
    // operate directly on s.str
    // eg s = s + 's'
    s.str = s.str + "s";
}

Obviously, you can improve this according to your use case (eg, override toString, hashcode, equals, etc for StringRefContainer class, or change/add access specifiers to suit your design, etc).

1

This line:

s=s+"s";

creates a new String and stores it in s. Before the assignment, s stores a reference to the same object as keys[i] does. After the assignment, since s refers to a new object, totally unrelated to what keys[i] refers to. In fact, you cannot change the string in keys[i] by changing s, even though s refers to the same object as keys[i] at the beginning of the method. This is because String is immutable. See also: Immutability of Strings in Java

What you can do, is have the BiConsumer return the new string that is created, and on the caller's side, assign the returned string to keys[i]. At this point, we no longer have a BiConsumer<String, Integer> anymore, but a BiFunction<String, Integer, String>:

interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

map would look like this:

public static String[] map(Map<String,Integer> m, BiFunction<String,Integer, String> bif){
    String[] key = m.keySet().toArray(new String[0]);
    String[] entries = new String[m.size()];
    for (int i = 0; i < m.size(); i++) {
        Integer value = m.get(key[i]);
        String newKey = bif.apply(key[i], value); // note here!
        entries[i] = newKey + ", " + value;

        // uncomment if you want the map's keys to also change
        /*
        m.remove(key[i]);
        m.put(newKey, value);
        */
    }
    return entries;
}

And you would call it like this:

map(map, new BiFunction<>(){
        @Override
        public void apply(String t, Integer u){
            return t + "s";
        }
    })
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • In my case I don't need to change the value. But if someone want to do this, he can't, because BiFunction can only return one object per method. So I wanted to pass one method to my map() method, that can change the passed string and the passed integer and returns both. However I accept your answer because you explained the stuff very well!! – Aaron Jan 05 '21 at 14:03