【far 语言】组合与继承

本文是「far 语言」系列文档之一,总目录见《【far 语言】语言文档目录》。

类继承与多态

Far 支持单继承 + 完整多态。多能力通过 impl 接口列表表达。

语法

class 子类 : 父类 impl 接口1, 接口2 {
    // 子类成员
}
形态 示例
仅继承 class Cat : Animal { }
仅接口 class Box impl IShow { }
继承 + 多接口 class Dog : Animal impl INamed, IRunner { }
泛型 + 继承 + 接口 class Box<T> : Base<T> impl IShow { }
  • 只允许单继承class A : B, C { } 会报错——多能力请用接口。
  • extends 关键字未启用,写了会得到指向 : 的友好报错。
  • 顺序固定为「父类 → impl → 接口列表」。

字段与方法继承

class Animal {
    long hp = 100;                 // 字段初始化器
    string kind = "animal";

    string speak(){ return "animal"; }
    long getHp(){ return this.hp; }
}

class Dog : Animal {
    long bark(){ return 7; }
}

Dog d = new Dog();
println(d.hp);        // 100  —— 父类字段初始化器自动执行
println(d.getHp());   // 100  —— 继承父类方法
println(d.bark());    // 7    —— 子类自有方法
  • 父类的字段初始化器会在子类实例上自动执行(沿继承链,祖先在前)。
  • 子类字段不得与父类同名(字段同存于一张表,遮蔽会静默覆盖父类值,编译期即拦住)。
  • 沿父链查找方法:getA() 定义在祖父类上也能调用。

多态

父类类型的变量可以持有子类实例,方法调用按运行期实际类型分派:

class Animal {
    string speak(){ return "animal"; }
}
class Dog : Animal {
    string speak(){ return "woof"; }
}
class Cat : Animal {
    string speak(){ return "meow"; }
}

Animal a = new Dog();
println(a.speak());   // woof —— 分派到 Dog 实现

a = new Cat();
println(a.speak());   // meow —— 同一变量换绑,分派随之变化

Animal xs[3] = [new Dog(), new Cat(), new Animal()];
println(xs[0].speak());   // woof
  • 向上转型(子类 → 父类)自动成立;向下转型必须显式 as,运行期校验失败会抛异常。
  • 父类类型的形参、字段、数组/容器元素同样支持多态。

覆盖与 super

class Animal {
    long legs(){ return 4; }
    string speak(){ return "animal"; }
}

class Dog : Animal {
    @Override
    string speak(){ return super.speak() + "+woof"; }
    long total(){ return super.legs() + 1; }
}

println(new Dog().speak());   // animal+woof
println(new Dog().total());   // 5
  • 子类定义同签名方法与父类 ⇒ 覆盖(override),多态分派走子类实现。
  • 同名但签名不同 ⇒ 视为重载,不是覆盖。
  • super.method() 静态调用父类实现(不参与多态分派),编译期直接绑定父类符号。
  • @Override 是可选断言:写了就要求父链或所实现接口中确实存在同签名方法,否则编译报错—— 用来拦「以为覆盖了、其实签名写错」的静默 bug。它也可用于声明「实现了接口方法」。

与接口的关系

interface Named {
    string name();
}

class Animal impl Named {
    string name(){ return "animal"; }
}

class Dog : Animal {          // 未写 impl
    string name(){ return "dog"; }
}

Named n = new Dog();          // OK:父类 impl 的接口,子类天然满足
println(n.name());            // dog —— 分派到子类实现
  • 父类 impl 的接口,子类天然满足(Java IS-A 语义),无需重复声明。
  • 接口继承链上的满足性同样成立class Dog impl Agedinterface Aged : NamedDog 天然满足 Named,可直接赋给 Named 变量;接口 → 父接口的赋值也成立。 细节见 interface-generic.md 的「接口继承与满足性」。
  • 接口变量可接受具体类类型的表达式(变量 / 方法返回 / new X()),调用走动态分派。

向下转型

向上转型(子类 → 父类)自动成立;向下转型必须显式 as,运行期校验:

class Animal { }
class Dog : Animal { long bark(){ return 7; } }
class Cat : Animal { }

Animal a = new Dog();

Dog d = a as Dog;        // 校验通过 → 得到 Dog 视图
println(d.bark());       // 7

Cat c = a as Cat;        // 校验失败 → 抛异常(Cannot cast Dog to Cat)
  • as 校验的是运行期实际类;失败抛异常(catch (Exception e) 可捕获),不会返回 null
  • 接口目标同样支持:a as Nameda 的实现类 impl 了 Named 时通过。
  • 不写 as 直接把父类类型赋给子类变量是编译错误(禁止隐式向下转型)。
  • object 源同样受此约束(对齐 Java:Object o; Named n = o; 是编译错误):必须显式 as, 且变量声明 / 实参 / return / 数组元素四条路径口径一致Named n = o as Named; · take(o as Named); · return c as HttpConn;。 该约束只针对以「用户声明的类 / 接口」为目标的情形;T → object(向上)恒可, 其余目标(object 自身、string / long / map 等内建类型)维持既有宽松。

编译期拦截的错误

写法 报错
class C : A, B { } 只支持单继承,多能力请用 impl
class Dog extends Animal { } extends 未启用,继承统一用冒号
循环继承 A : B + B : A 继承链形成环
子类字段与父类同名 禁止字段遮蔽
@Override 标在无同签名可覆盖的方法上 断言失败
继承不存在的父类 父类未定义

与重载 / 变长参数

class Fmt : Base {
    string fmt(long... nums){ /* nums 是 long[] */ }
    string fmt(string s, long... rest){ /* ... */ }
}

Fmt f = new Fmt();
f.fmt(1, 2, 3);        // 第一组
f.fmt("a", 1, 2);      // 第二组
  • 重载与变长参数见 function.md
  • 子类重载不会改变父类已有方法的符号,二者互不影响。
posted @ 2026-09-13 05:05  方东信  阅读(4)  评论(0)    收藏  举报