3

I have already look at this questions, but my problem is a little different.

I have a "baseString", n HashMap and an output string. I want to fill the baseString with the hashmap, to construct a json call parameters.

I have already done it with Java 7, in this way:

HashMap<String,Integer> element= getAllElement();

String baseElem="{\"index\":{}}\r\n" + 
        "{\"name\":\"$name$\",\"age\":$age$}";

String result=baseElem;

for (Map.Entry<String, Integer> entry : element.entrySet()) {

    result=result.replace("$name$", entry.getKey());
    result=result.replace("$age$", entry.getValue().toString());
    result=result+baseElem;
}
result= result.replace(baseElem, "");

Now I want to the same with Java 8, I have tried in this way:

element.forEach((k,v)->{
            result=result.replaceAll("$name$", k);
            result=result.replaceAll("$age$", v.toString());
            result=result+baseElem;
        });

But for each result I have an error

"Local variable result defined in an enclosing scope must be final or effectively final"

So the question is: I can do that in some kind of way with Java 8 and streams? Or there is no way, and so I can use the simple Java 7 for?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Liz Lamperouge
  • 681
  • 14
  • 38
  • Define the variables as a class variables instead of defining inside a method. – NKR Sep 05 '19 at 15:43
  • That's not a proper way to do JSON in Java, you should use Jackson to convert Java objects to JSON and JSON to Java objects. If you insist on using templates, there are also better solutions with freemarker, mustache, thymeleaf, ... – Nyamiou The Galeanthrope Sep 05 '19 at 17:27
  • Note that `replaceAll` expects a *regular expression*, and `$` is a special character denoting an end-of-input boundary. It can only ever match the end of the input string, so `$name$` and `$age$` are never matched. – MC Emperor Sep 05 '19 at 19:34
  • @NyamiouTheGaleanthrope thank you for your advise, I am trying all the possibile solution and implementation. – Liz Lamperouge Sep 06 '19 at 08:47

5 Answers5

3

Your approach is going entirely into the wrong direction. This is not only contradicting the functional programming style, the Stream API adopts. Even the loop is horribly inefficient, performing repeated string concatenation and replace operations on the growing result string.

You did a Shlemiel the painter’s algorithm

You actually only want to perform a replace operation on the baseElem template for each map entry and join all results. This can be expressed directly:

Map<String,Integer> element = getAllElement();

String baseElem = "{\"index\":{}}\r\n{\"name\":\"$name$\",\"age\":$age$}";

String result = element.entrySet().stream()
    .map(e -> baseElem.replace("$name$", e.getKey())
                      .replace("$age$",  e.getValue().toString()))
    .collect(Collectors.joining());
Holger
  • 285,553
  • 42
  • 434
  • 765
  • thank you for all your suggest, I am very new to Java 8, so I am trying to do my best learning and understaing my mistakes. – Liz Lamperouge Sep 06 '19 at 08:29
2

As for "Local variable result defined in an enclosing scope must be final or effectively final" see this answer for further explanation.

As for:

So the question is: I can do that in some kind of way with Java 8 and streams? Or there is no way, and so I can use the simple Java 7 for?

The logic you're performing with the iterative approach is known as "fold" or "reduce" in the functional world i.e. streams.

So, what you want to do is:

String result = element.entrySet()
                .stream()
                .reduce(baseElem,
                        (e, a) -> e.replace("$name$", a.getKey()).replace("$age$",
                                a.getValue().toString()),
                        (x, y) -> {
                            throw new RuntimeException();
                        });

the third input argument to reduce is known as the combiner function which should be an associative, non-interfering, stateless function for combining two values, which must be compatible with the accumulator function.

if you don't plan on using a parallel stream then the current logic should suffice otherwise you'll need to replace (x, y) -> {...} with the actual logic.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • 1
    There's no need to abused `reduce` in this way, once you realize what's wrong even with the loop. The [clean solution](https://stackoverflow.com/a/57812945/2711488) is simple and efficient. – Holger Sep 05 '19 at 21:06
  • Unlike the joining one, this solution does not concatenate things, so the output string is exactly the input string with its replacements. So it all depends on the need... And this one perfectly fit my needs today :p Thx – Olivier B. Oct 12 '21 at 14:58
1

It think, you could use this one:

private static Map<String, Integer> getAllElement() {
    Map<String, Integer> map = new HashMap<>();
    map.put("\\$name\\$", 666);
    map.put("\\$age\\$", 777);
    return map;
}

Map<String, Integer> map = getAllElement();
String[] json = { "{\"index\":{}}\r\n{\"name\":\"$name$\",\"age\":$age$}" };

map.forEach((key, val) -> json[0] = json[0].replaceAll(key, String.valueOf(val)));
System.out.println(json[0]);

Output:

{"index":{}}
{"name":"666","age":777}
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
1

From the docs,

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted. Similar rules on variable use apply in the body of an inner class (§8.1.3). The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. Compared to the final restriction, it reduces the clerical burden on programmers.

What is to be a constant here is the reference, but not the values.

You are getting this exception because, you are changing the reference of result. You are re-assigning result to some point to some other String inside your lambda. Thus, conflicting with the JLS, dynamically-changing local variables

Also, Adding to this, You can use Jackson ObjectMapper for producing JSON from Java object(s) instead of hardcoding and replacing stuff.

Mohamed Anees A
  • 4,119
  • 1
  • 22
  • 35
0

Define the result variable outside your method something like below

Class A{
String result=null;
Method a(){
//method implementation 
}
}
NKR
  • 167
  • 9