跳到内容

第 9 卷 / 第 1 章 / 第 1 课

1.1 类型、子类型与多态:程序能做什么 ​

不同语言表达类型规则的方式千差万别,起点却是同一个问题:怎样在程序运行前后约束值与操作的合法组合?

类型描述值以及允许对它进行的操作。静态类型检查能在运行前排除一部分组合错误,但能保证多少取决于语言规则与使用方式。业务金额算错了,类型仍然可能完全正确;编译器并不知道你今天打算收几折。

静态与动态描述检查时机 ​

静态类型系统在运行前为表达式建立类型关系;动态语言把更多检查留到运行时。两者都可能有运行时类型信息、反射和显式检查。

“强类型/弱类型”没有跨语言统一的严格尺度,常被混用来描述隐式转换、内存安全或运行时检查。比较语言时应直接说具体规则:是否允许数字与字符串隐式相加、是否能绕过边界、错误发生在哪个阶段。

Nominal 与 Structural 决定兼容依据 ​

名义类型(nominal typing)依据显式声明的类型身份与继承关系。把下面代码保存为 TypesDemo.java,使用 JDK 17 或更高版本运行 java TypesDemo.java:

java
class TypesDemo {
    interface Reader { byte[] read(); }
    static final class FileReader implements Reader {
        public byte[] read() { return new byte[] {1, 2, 3}; }
    }
    static final class Lookalike {
        public byte[] read() { return new byte[] {1, 2, 3}; }
    }
    static int readLength(Reader reader) { return reader.read().length; }
    public static void main(String[] args) {
        System.out.println(readLength(new FileReader()));
        // System.out.println(readLength(new Lookalike()));
    }
}

输出为 3。取消注释后,Lookalike 虽然有同样的 read 方法,编译仍会失败,因为它没有实现 Reader。

结构类型(structural typing)则主要检查对象是否具有所需成员,无需同名接口声明。两种方式都仍需处理可变性、可见性和函数参数兼容规则;仅有相同的方法名并不足以保证整个接口兼容。

结构相同不代表语义相同。Meters 和 Seconds 都可能只含一个浮点数,仍不应互换;可以使用 distinct/newtype 封装让类型系统保存单位信息。

Subtyping 不是代码复用 ​

类型规则中的 S <: T 允许在相应的 T 位置使用 S。要让替换在行为上也成立,还需满足调用方的契约:前置条件不能更强,后置条件不能更弱,不变量仍要保持。普通编译器通常只检查类型与方法签名,不会自动证明这些业务性质。

继承语法可以复用实现,却不自动满足 substitutability。正方形继承可变矩形后若 setWidth 与 setHeight 的契约冲突,就是经典例子。组合、不可变值和窄接口常比深继承更可靠。

多态有多种来源 ​

text
Ad-hoc polymorphism:重载、type class/trait implementation
Parametric polymorphism:对任意 T 使用同一结构
Subtype polymorphism:通过共同上层类型动态分派

参数多态让同一实现服务于多种类型。对一个只返回输入的 identity<T>(x: T): T,可以在不指定 T 的情况下推理;但在支持反射、类型测试、异常或 null 的语言里,这个签名本身不能证明实现一定只返回输入。若泛型函数需要比较、序列化或相加,就应明确声明所需能力。

类型安全有边界 ​

外部 JSON、数据库、FFI、反射和不安全代码可能带来静态类型未覆盖的输入。类型声明不会自动校验这些数据;边界处需要解析与验证,再构造内部合法类型:

text
untrusted bytes → parser → validated domain value → typed core

raw type、any 和未检查的强制转换会削弱静态保证;Java 普通引用转换通常仍有运行时检查,但不会顺便验证业务约束。将这些操作集中封装,并用边界检查和失败用例检验假设。

下一课处理泛型中最容易混淆的关系:Cat <: Animal 并不自动推出 Box<Cat> <: Box<Animal>。

参考资料 ​

Built with VitePress | Software Systems Atlas