How can the methods `makeConcat​` and `makeConcatWithConstants` in `StringConcatFactory` used by directly calling the API?

5k views Asked by At

I believe since Java 9 string concatenation has been implemented using StringConcatFactory.

Since this is provided as an API in Java how are the methods makeConcat​ and makeConcatWithConstants in StringConcatFactory used by directly calling the API? I so far could not find any examples of the different ways to use it. Also, what the parameters String name, MethodType concatType in makeConcat​ and makeConcatWithConstants and parameters String recipe, Object... constants in makeConcatWithConstants mean and what should be passed to them are not self-evident to me from the Java Docs.

2

There are 2 answers

3
Holger On BEST ANSWER

You are not supposed to call this API directly. The class has been designed to provide bootstrap methods for an invokedynamic instruction, so its API is straight-forward for that use case, but not for direct invocations.

But the documentation is exhaustive:

Parameters

  • lookup - Represents a lookup context with the accessibility privileges of the caller. When used with invokedynamic, this is stacked automatically by the VM.
  • name - The name of the method to implement. This name is arbitrary, and has no meaning for this linkage method. When used with invokedynamic, this is provided by the NameAndType of the InvokeDynamic structure and is stacked automatically by the VM.
  • concatType - The expected signature of the CallSite. The parameter types represent the types of concatenation arguments; the return type is always assignable from String. When used with invokedynamic, this is provided by the NameAndType of the InvokeDynamic structure and is stacked automatically by the VM.

Emphasis added by me

Note how all parameters are normally provided by the JVM automatically on the basis of the invokedynamic bytecode instruction. In this context, it’s a single instruction consuming some arguments and producing a String, referring to this bootstrap method as the entity knowing how to do the operation.

When you want to invoke it manually, for whatever reasons, you’d have to do something like

String arg1 = "Hello";
char arg2 = ' ';
String arg3 = "StringConcatFactory";

MethodHandle mh = StringConcatFactory.makeConcat(
    MethodHandles.lookup(), // normally provided by the JVM
    "foobar", // normally provided by javac, but meaningless here
    // method type is normally provided by the JVM and matches the invocation
    MethodType.methodType(String.class, String.class, char.class, String.class))
    .getTarget();

// we can now use the handle to perform a concatenation
// the argument types must match the MethodType specified above
String result = (String)mh.invokeExact(arg1, arg2, arg3);

System.out.println(result);

You could re-use the MethodHandle for multiple string concatenations, but your are bound to the parameter types you’ve specified during the bootstrapping.

For ordinary string concatenation expressions, each expression gets linked during its bootstrapping to a handle matching the fixed number of subexpressions and their compile-time types.

It’s not easy to imagine a scenario where using the API directly could have a benefit over just writing arg1 + arg2 + arg3, etc.


The makeConcatWithConstants bootstrap method allows to specify constant parts in addition to the potentially changing parameters. For example, when we have the code

String time = switch(LocalTime.now().get(ChronoField.HOUR_OF_DAY) / 6) {
    case 0 -> "night"; case 1 -> "morning"; case 2 -> "afternoon";
    case 3 -> "evening"; default -> throw new AssertionError();                
};

System.out.println("Hello "+System.getProperty("user.name")+", good "+time+"!");

we have several constant parts which the compiler can merge to a single string, using the placeholder \1 to denote the places where the dynamic values have to be inserted, so the recipe parameter will be "Hello \1, good \1!". The other parameter, constants, will be unused. Then, the corresponding invokedynamic instruction only needs to provide the two dynamic values on the operand stack.

To make the equivalent manual invocation more interesting we assume the system property user.name to be invariant, hence we can provide it as a constant in the bootstrap invocation, use the placeholder \2 to reference it, and produce a handle only consuming one dynamic argument, the time string:

MethodHandle mh = StringConcatFactory.makeConcatWithConstants(
    MethodHandles.lookup(), // normally provided by the JVM
    "foobar", // normally provided by javac, but meaningless here
    // method type is normally provided by the JVM and matches the invocation
    MethodType.methodType(String.class, String.class),
    "Hello \2, good \1!", // recipe, \1 binds a parameter, \2 a constant
    System.getProperty("user.name") // the first constant to bind
).getTarget();

// we can now use the handle to perform a concatenation
// the argument types must match the MethodType specified above
String result = (String)mh.invokeExact(time);

System.out.println(result);

Ordinary Java code will rarely make use of the additional constants. The only scenario I know of, is the corner case of having \1 or \2 in the original constant strings. To prevent them from being interpreted as placeholders, those substrings will be provided as constants then.

As demonstrated in this online code tester, the code

String time = switch(LocalTime.now().get(ChronoField.HOUR_OF_DAY) / 6) {
    case 0 -> "night"; case 1 -> "morning"; case 2 -> "afternoon";
    case 3 -> "evening"; default -> throw new AssertionError();                
};

System.out.println("Hello "+System.getProperty("user.name")+", good "+time+"!");

String tmp = "prefix \1 "+time+" \2 suffix";

gets compiled to (irrelevant parts omitted):

 0: invokestatic  #1                  // Method java/time/LocalTime.now:()Ljava/time/LocalTime;
 3: getstatic     #7                  // Field java/time/temporal/ChronoField.HOUR_OF_DAY:Ljava/time/temporal/ChronoField;
 6: invokevirtual #13                 // Method java/time/LocalTime.get:(Ljava/time/temporal/TemporalField;)I
 9: bipush        6
11: idiv
12: tableswitch   { // 0 to 3
               0: 44
               1: 49
               2: 54
               3: 59
         default: 64
    }
44: ldc           #17                 // String night
46: goto          72
49: ldc           #19                 // String morning
51: goto          72
54: ldc           #21                 // String afternoon
56: goto          72
59: ldc           #23                 // String evening
61: goto          72
64: new           #25                 // class java/lang/AssertionError
67: dup
68: invokespecial #27                 // Method java/lang/AssertionError."<init>":()V
71: athrow
72: astore_1
73: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
76: ldc           #37                 // String user.name
78: invokestatic  #39                 // Method java/lang/System.getProperty:(Ljava/lang/String;)Ljava/lang/String;
81: aload_1
82: invokedynamic #43,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
87: invokevirtual #47                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
90: aload_1
91: invokedynamic #53,  0             // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
96: astore_2
BootstrapMethods:
  0: #150 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #151 Hello \u0001, good \u0001!
  1: #150 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #153 \u0002\u0001\u0002
      #155 prefix \u0001
      #157  \u0002 suffix
0
AudioBubble On

The documentation that you linked states, "These methods are typically used as bootstrap methods... to support the string concatenation feature", that means, they are used by the compiler to prepare to do String concatenation - in other words, not intended to be used by programmers (as I remember, they basically create a (kind of lambda) method to be called for concatenation).

Easy way to use that: the + operator to concatenate string and let the compiler do the job. If you really want to use that methods directly (Why? it is not that trivial), create a class using concatenation and check the generated javacode with some decompiler.

see also: What is a bootstrap method? (not 100% related, but the last sentence)