As with the TypeScript examples, we can make the top-level apply method generic instead of the whole class to avoid some casts. In particular, notice that the <T> inferred from calling Circle() and Rectangle() is the unmeaningful <Object>.
The downside is that Java doesn't let you implement generic methods with lambdas, which is silly, so you have to use an anonymous subclass. But at least the call-side uses stay the same.
(And then, combining the `_Circle` and `_Rectangle` interfaces gets you to a more canonical visitor pattern, at the expense of lambdas at the call-site.)
public interface Shape {
interface _Circle<T> {
T apply(int x, int y, int r);
}
interface _Rectangle<T> {
T apply(int x, int y, int w, int h);
}
<T> T apply(_Circle<T> circle, _Rectangle<T> rectangle);
static Shape Circle(int x, int y, int r) {
return new Shape() {
public <T> T apply(_Circle<T> circle, _Rectangle<T> rectangle) {
return circle.apply(x, y, r);
}
};
}
static Shape Rectangle(int x, int y, int w, int h) {
return new Shape() {
public <T> T apply(_Circle<T> circle, _Rectangle<T> rectangle) {
return rectangle.apply(x, y, w, h);
}
};
}
static double area(Shape shape) {
return shape.apply(
(x, y, r) -> Math.PI * r * r,
(x, y, w, h) -> 1. * w * h
);
}
static void main(String[] args) {
var exampleCircle = Circle(2, 1, 4);
var exampleRectangle = Rectangle(1, 3, 10, 7);
System.out.println(area(exampleCircle));
System.out.println(area(exampleRectangle));
}
}