typescript - 在 Java 中的函数式编程及有用的编译器错误
我正在寻找一种方法来对一段数据定义通用计算,并让编译器在我做错时发出警告。
来自 TypeScript 世界,你可以这样做:
/**
* 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;
* }>
*/
在此结束时,result 的类型将被正确推断,并且 map 中的每个类型也会被推断。
这里的好处是编译器实际上在帮助发现错误。
你会如何在 Java 中做类似的事情?
我尝试使用一个通用的 Map 对象,其泛型类型跨越多个低级类型,类似于 JS 字典。但在这种情况下,编译器不知道键 X 实际上是类型 Y,因此它在捕捉错误方面完全没用。
我的结论是,在 Java 中不可能拥有像 TypeScript 那样的静态分析。Java 编译器本身并不是为了在运行时传播和更新未命名类型并捕捉这类错误而设计的。
答案#1
TypeScript(以及 JavaScript)在概念上与 Java 非常不同。
在 TypeScript/JavaScript 中,一切都围绕 对象 构建,而在 Java 世界中,一切都围绕 类 运转。
在 Java 中,你 不能 直接在现场创建对象(就像在 TypeScript/JavaScript 中可以做到的那样)。
你首先需要定义一个类(或者定义一个记录,它基本上是类的一个专门版本)。当然,你可以使用一个匿名内部类来获得某个特定类型(如java.lang.Object)的子类实例,并且这个子类会在内部自动为你创建,但对于你的对象属性你需要泛型类型,这个选项不适合你的使用场景(在创建匿名内部类时无法定义新的泛型参数)。
所以你需要使用 class 或 Java 16 的 record 来定义你的自定义泛型类型(注意,record 是不可变的,即所有属性都是 final)。
定义一个通用类
在你的示例中,与封闭对象 { a; b; c; } 结构匹配的通用类可能如下所示:
public class MyType<A, B, C> {
private A a;
private B b;
private C c;
// constructor, getters, etc.
}
定义通用记录
具有结构 { d; e; } 的内部对象可以描述为以下记录(注意,记录的规范全参数构造函数、getter、equals/hashCode 和 toString 将由编译器自动生成,因此记录定义实际上可以是一行):
public record Pair<L, R>(L left, R right) {}
注意:一些库,如 Vavr 和 Apache Commons 提供了类型 Triple 和 Pair,但它们在 JDK 中不可用。有些人可能会说标准 JDK 的 Map.Entry 是 Pair 的一个模糊类比,但用 map entry 替代一个领域类型在语义上是有争议的。
类型推断
关于类型推断,在 Java 中我们有诸如目标类型、多态表达式(上下文敏感的表达式,其类型应从赋值上下文、调用上下文或类型转换上下文中推断)等概念。这个主题非常广泛,可能会遇到许多与类型推断相关的问题,但简单来说,如果你需要编译器推断某些类型,那么应该有某种信息来源(例如赋值上下文、参数类型等)。
这是一个使用 Java 10 局部变量类型推断通过 var 关键字 和 Java 7 菱形 <> 操作符 的类型推断示例(假设 MyType 声明了无参和全参构造函数):
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
