To understand why we need string interpolation and how to use it, consider following example: We have to implement a function, which can build a short person descriptor.
StringBuilder implementation:
class Greeter {
public String describe(Person person) {
StringBuilder builder = new StringBuilder();
builder.append("Hello! My name is ");
builder.append(person.getName());
builder.append(". I'm ");
builder.append(person.getAge());
return builder.toString();
}
}
A lot of code to implement such simple thing. May be String.format
can help us.
class Greeter {
public String describe(Person person) {
String template = "Hello! My name is %s. I'm %d";
return String.format(template, person.getName(), person.getAge());
}
}
Better, but we can do even better. String interpolation can help us to write such code in a nicer and shorter way.
class Greeter {
public String describe(Person person) {
return s("Hello! My name is ${person.getName()}. I'm ${person.getAge()}");
}
}
First, you need to add interp4j-core
dependency to your project. For example, if you use maven, add the following
configuration to your pom.xml
file:
<dependency>
<groupId>dev.khbd.interp4j</groupId>
<artifactId>interp4j-core</artifactId>
<scope>compile</scope>
<version>LATEST</version>
</dependency>
Now, you can use function from Interpolations
class in your code. These function are entry point to interpolations.
Second, you need to configure your build tool to run interpolation process before source files compilation.
s
interpolators
interpolator is simple. Write down a text and inject any java expression into in ${...}
. If an expression is
a java identifier curly braces can be omitted. For example, s("Hello, ${person.name}")
or s("Hello, $name")
.
fmt
interpolatorfmt
interpolator is a replacement for String.format
. The usage is quite the same as s
interpolator, but
before each expression valid specifier must be provided. For example, fmt("Hello, %20.4s${person.name}")
.
All features of String.format
are supported, but there are several syntactic rules:
fmt("%1$s${person.name}")
or fmt("%1$s${person.name}, %<d${person.age}")
are not valid.$$
and %n
no code is allowedWe are going to support separate version for each LTS release as long as that release is supported. In the following table, you can find the latest interp4j version for each supported java version.
Java version |
Latest release |
---|---|
1.8 |
|
11 |
|
17 |
|
21 |
To interpolate strings in maven-based projects you have to configure compiler to enable interp4j compiler plugin during
compilation. Add the following configuration to your pom.xml
file and that's it.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<!-- enable interp4j compiler plugin -->
<arg>-Xplugin:interp4j</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>dev.khbd.interp4j</groupId>
<artifactId>interp4j-processor</artifactId>
<version>LATEST</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Compiler plugin uses internal jdk api to interpolate string literals and this api is strongly encapsulated by default in jdk 17. To relax it at compile time configuration should be changed accordingly.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<compilerArgs>
<!-- enable interp4j compiler plugin -->
<arg>-Xplugin:interp4j</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>dev.khbd.interp4j</groupId>
<artifactId>interp4j-processor</artifactId>
<version>LATEST</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Additional exports are needed only for compiling process, resulted code will not be dependent on internal jdk api.
To interpolate strings in gradle-based projects you have to configure compiler to enable interp4j compiler plugin during
compilation. Add the following configuration to your build.gradle
file and that's it.
dependencies {
compileOnly group: 'dev.khbd.interp4j', name: 'interp4j-core', version: interp4j_version
annotationProcessor group: 'dev.khbd.interp4j', name: 'interp4j-processor', version: interp4j_version
}
task.withType(JavaCompile) {
options.fork = true
options.compilerArgs.add('-Xplugin:interp4j')
}
If you use jdk 17 or higher additional options are required to allow compiler plugin to use internal jdk api.
dependencies {
compileOnly group: 'dev.khbd.interp4j', name: 'interp4j-core', version: interp4j_version
annotationProcessor group: 'dev.khbd.interp4j', name: 'interp4j-processor', version: interp4j_version
}
task.withType(JavaCompile) {
options.fork = true
options.compilerArgs.add('-Xplugin:interp4j')
options.forkOptions.jvmArgs.addAll([
'--add-exports', 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports', 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-exports', 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED'
])
}
Additional exports are needed only for compiling process, resulted code will not be dependent on internal jdk api.
To support interp4j
by intellij, install
interp4j-intellij-plugin.
To look at modified source code after interpolation, set flag prettyPrint.after.interpolation
to true
. For example,
for maven-based projects it can look like that:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Xplugin:interp4j prettyPrint.after.interpolation=true</arg>
</compilerArgs>
...
</configuration>
</plugin>
By default, this feature is disabled.
All benchmarks were run on:
See latest benchmark result here .
As you can see, compile time interpolation is about 10 times faster then String.format
and at the same time as fast as manual string concatenation. Benchmarks source code can
be found in interp4j-benchmark
module.
To run benchmarks do several steps:
mvn package -Pbenchmark
interp4j-benchmark/target
directory. interp4j-benchmark-${version}-jar-with-dependencies.jar
should be
generatedjava -cp ./interp4j-benchmark-${version}-jar-with-dependencies.jar dev.khbd.interp4j.benchmark.BenchmarkRunner -rf json
jmh-result.json
report should be generated