I'm looking for a way to define generic computation on a piece of data and get the compiler to yell if I get it wrong.
Coming from the typescript world, you can do something like this:
/**
* Type inferred as:
* Array<{
* a: number;
* b: string;
* c: { d: number; e: string };
* }>
*/
const data = [
{ a: 1, b: "b", c: { d: 4, e: "e" } },
{ a: 2, b: "b", c: { d: 4, e: "e" } },
{ a: 3, b: "b", c: { d: 4, e: "e" } },
];
const result = data
// change the type of c to string
.map(o => ({...o, c: JSON.stringify(o.c)}))
// adding a new field
.map(o => ({...o, d: "new"}))
;
/**
* `result` type is automatically inferred as:
* Array<{
* d: string;
* c: string;
* a: number;
* b: string;
* }>
*/
At the end of this, the type of result
will be properly infered and each type in between map
is inferred too.
Benefits here is that the compiler is actually helping uncover bugs.
How would you do something similar in java?
I tried to use a generic Map
object with a generic type spanning multiple lower level types that sort of mimic a JS dictionary. But in this case, the compiler doesn't know that key X is actually of type Y, so it's totally useless in catching bugs.
My conclusion is that it's not possible to have the same kind of static analysis in java that we have in Typescript. The Java compiler is just not made to propagate and update unnamed types as it goes on and catch those kinds of bugs.
CodePudding user response:
TypeScript (as well JavaScript) conceptually very different from Java.
In TypeScript/JavaScript everything is build around Objects, whilst in Java world everything revolves around Classes.
In Java, you can't create an object right on the spot (like it can be done in TypeScript/JavaScript).
You need to define a class
first (or a record
which is basically a specialized version of a class
). Sure, you can use an anonymous inner class of obtain an instance of a subtype of a particular type (like java.lang.Object
) and this subclass would be created for you under the hood, but you need Generic type for the properties of your object this option doesn't fit into your use-case (there's no way to define new Generic parameters while creating an anonymous inner class).
So you need to use either a class
or a Java 16 record
to define your custom generic type (note that records are immutable, i.e. all properties are final).
Defining a Generic Class
A generic class that matches the structure of the enclosing object { a; b; c; }
in your example might look like this:
public class MyType<A, B, C> {
private A a;
private B b;
private C c;
// constructor, getters, etc.
}
Defining a Generic Record
The inner object which has a structure { d; e; }
can be described as the following record (note that canonical all-args constructor, getters, equals/hashCode
and toString
of a record would be auto-generated by the compiler, so the record definition can be literally a one-liner):
public record Pair<L, R>(L left, R right) {}
Note: some libraries like Vavr and Apache Commons offer types Triple
and Pair
, but they are not available in the JDK. Some might say that standard JDK's Map.Entry
is a vague analogous of Pair
, it would be semantically vied to substitute a domain type with map entry.
Type Inference
Regarding the type inference, in Java we have such notions like Target type, poly expressions (expressions that are context-sensitive and which type should be inferred either from Assignment context, Invocation context or Casting context), etc. This subject is very broad, and many issues related to type inference might be encountered, but to put it simple if you need the compiler to infer some types there should be some source of that information (like assignment context, argument types, etc.).
Here's an example of type inference with Java 10 Local variable type inference via var
word and Java 7 diamond <>
operator (suppose MyType
declares both no-args and all-args constructors):
MyType<Integer, String, Pair<Integer, String>> myType = new MyType<>();
var myType2 = new MyType<>(1, "b", new Pair<>(4, "e")); // <> literally means - infer the type of generic parameters