You’re likely to need a lot of boilerplate that “go” statement won’t generate: pass and wait for completed results, report errors, timeouts, cancellation, bounded parallelism, pushback, service monitoring.
My comment is perhaps too glib. I'm not trying to say that Go is better, I frankly like Java more. I only mean that if I'm in a Go mindset and I read the linked document on virtual threads, and I see that code example, I'm going to close the tab.
If you want to use that with more concise syntax, there is Kotlin for that. In which case abstracting that is one line of code tucked away in a utility file:
fun <T> go(body: ExecutorService.() -> T): T =
Executors
.newVirtualThreadPerTaskExecutor()
.use(body)
Now you can write:
val result = go {
val result1 = submit { slowOp() }
val result2 = submit { blockingOp() }
result1.get() + result2.get()
}
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter.
class Shortcuts {
static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
try (....) { return task.apply(service) }
}
}
and then you can write:
var result = go(service -> {
var result1 = service.submit(() -> slowOp());
var result2 = service.submit(() -> blockingOp());
result1.get() + result2.get();
});
using static imports.
Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.
You don't need a try-with-resources, you can manually call close if you want, or has to work in a non-lexical scope.
But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.
edit: Sorry, it's actually:
instead of `go`