Functional Interfaces - HimxhMC/HimDSL GitHub Wiki

函数式接口 / Functional Interfaces

从一段报错说起 / Starting from an Error

有些情况下,你要把一个 lambda 传给 Java 代码,但 Java 不知道 .apply(...) 是什么。例如下面这段:

Some cases require passing a lambda to Java code, but Java does not know what .apply(...) means. For example:

var r = new Runnable(() -> runcmd("CONSOLE", "say hi"));
r.run();

这里的 Runnable 是 Java 自带的一个接口,它只定义了一个方法 run()。HimDSL 会把 lambda 适配成 Runnable 实例:调用 r.run() 时,实际执行 lambda 的 body。

Runnable is a Java interface with a single method run(). HimDSL adapts the lambda into a Runnable instance: calling r.run() executes the lambda body.

这样的接口叫函数式接口:只有一个抽象方法。Java 标准库里有很多,例如 Runnable、Consumer、Function、Supplier、Predicate。把 lambda 传进去,就能让 Java 代码在合适的时机调用你的逻辑。

Such an interface is called a functional interface: exactly one abstract method. Java's standard library has many — Runnable, Consumer, Function, Supplier, Predicate. Passing a lambda in lets Java call your logic at the right moment.


用 new 转换 / Converting with new

把一个 lambda 转成函数式接口,用 new:

Convert a lambda into a functional interface with new:

var task = new Runnable(() -> {
    runcmd("CONSOLE", "say 一秒钟后执行");
});

task.run();

new Runnable(lambda) 会生成一个实现 Runnable 的对象,其 run 方法就是 lambda 的 body。之后凡是需要 Runnable 的地方,都可以用 task。

new Runnable(lambda) produces an object implementing Runnable, whose run method is the lambda body. Wherever a Runnable is needed, task fits.

注意这里用 var 声明变量。函数式接口通常带泛型参数,泛型语法不属于 HimDSL,所以必须用 var 让编译器自己推断。写 Runnable task = ... 是可以的(Runnable 没有泛型),但写 Consumer c = ... 就要小心——请统一用 var。

Note the use of var. Functional interfaces usually carry generic parameters, and generic syntax is not part of HimDSL, so var is mandatory. Runnable task = ... works (Runnable has no generics), but Consumer c = ... is trickier — stick to var throughout.


一个实用例子 / A Practical Example

某些 Bukkit API 接收 Runnable 参数。例如调度一次性延迟任务:

Some Bukkit APIs take a Runnable. For example, scheduling a one-shot delayed task:

int main() {
    var later = new Runnable(() -> {
        runcmd("CONSOLE", "say 3 秒后执行");
    });
    // 伪代码:假设有调度函数接收 Runnable
    // scheduleLater(later, 60);
}

HimDSL 本身提供了更简便的调度方式(@BukkitRunnable),所以日常脚本里不常直接手写 Runnable。但当你需要调用某个只收 Runnable 的 Java API 时,就需要用到这层适配。

HimDSL itself offers a simpler scheduling mechanism (@BukkitRunnable), so everyday scripts rarely hand-write Runnable. But when you call a Java API that only accepts a Runnable, this adaptation comes in.


Consumer 与 Function / Consumer and Function

Consumer 接收一个参数、不返回值。Function 接收一个参数、返回一个值。它们的泛型参数在 HimDSL 里不写出来,交给 var 推断:

Consumer takes one argument and returns nothing. Function takes one argument and returns a value. Their generic parameters are not written in HimDSL; var handles inference:

var callback = new Consumer((player p) -> {
    runcmd("CONSOLE", "say 玩家: " + p.name);
});

var square = new Function((int x) -> x * x);

用 var 声明,是因为 HimDSL 语法里没有 <T>、<T, R> 这样的泛型写法。想把它们显式写出来是不支持的:

Use var because HimDSL has no <T> / <T, R> syntax. Writing them explicitly is not supported:

Consumer<Player> cb = ...        // ✗ 语法错误
Function<Integer, Integer> f = ... // ✗ 语法错误

所以规则很简单:凡是接口名后面带尖括号的,就省略尖括号,改用 var。

So the rule is simple: whenever the interface name would carry angle brackets, drop them and use var.


Predicate:条件回调 / Predicate: Conditional Callbacks

Predicate 接收一个参数、返回布尔值,常用于"过滤":

Predicate takes one argument and returns a boolean, common for "filtering":

var isHighHp = new Predicate((player p) -> {
    return p.health > 15;
});

HimDSL 本身没有集合过滤 API,但如果某个插件提供接收 Predicate 的方法,就能传进去。

HimDSL has no built-in collection filtering, but if a plugin exposes a method taking a Predicate, you can pass one.


匹配规则 / Matching Rules

把 lambda 适配到接口时,HimDSL 按照以下规则:

When adapting a lambda to an interface, HimDSL follows these rules:

  1. 接口必须是函数式接口(只有一个抽象方法)。

  2. lambda 的形参个数必须等于那个抽象方法的参数个数。

  3. 参数类型不强制匹配。运行时由适配层负责转换。

  4. The interface must be a functional interface (one abstract method).

  5. The lambda's parameter count must equal the method's parameter count.

  6. Parameter types are not strictly checked; the adaptation layer converts at runtime.

如果接口有多个抽象方法,HimDSL 会报错,因为无法确定 lambda 对应哪个方法。

If the interface has more than one abstract method, HimDSL errors — it cannot tell which one the lambda implements.

var bad = new SomeMultiMethodInterface((int x) -> x);   // ✗ 报错

标准库里几乎所有函数式接口都是单抽象方法,所以一般不会遇到这个问题。自定义接口时,需要注意只留一个抽象方法。

Nearly every standard functional interface has a single abstract method, so this rarely happens. Custom interfaces must leave exactly one abstract method.


何时需要函数式接口 / When You Need This

函数式接口主要出现在与 Java 生态交互时:调用第三方插件提供的方法、注册监听器、使用 Java 标准库 API。

Functional interfaces mainly appear when interacting with Java ecosystems: calling third-party plugin methods, registering listeners, using standard library APIs.

日常脚本里你用 @EventHandler 和 @BukkitRunnable 就能满足绝大多数需求——它们已经内置了框架支持,不用手写 Runnable。

Everyday scripts satisfy nearly all needs with @EventHandler and @BukkitRunnable — the framework already handles those, no hand-written Runnable needed.

只有当你遇到"这个方法要求我传一个 Runnable,但 HimDSL 没提供对应的便捷语法"时,才需要本章的内容。

You only need this chapter when you hit "this method needs a Runnable and HimDSL has no shorthand for it".


什么时候不用 / When Not to Use

如果 HimDSL 已经提供更直接的写法,优先用直接写法:

When HimDSL already offers a direct form, prefer it:

  • 定时任务:用 @BukkitRunnable,不用 new Runnable。

  • 事件监听:用 @EventHandler,不用适配 Listener。

  • GUI 点击:直接 slot.onLeftClick = lambda,不用手动 new Consumer。

  • Timers: use @BukkitRunnable, not new Runnable.

  • Event listeners: use @EventHandler, not a hand-adapted Listener.

  • GUI clicks: assign slot.onLeftClick = lambda, no need to new Consumer.

函数式接口是兜底方案,用于那些 HimDSL 没有内置支持的场合。

Functional interfaces are a fallback, for cases HimDSL does not cover natively.


小结 / Summary

  • 函数式接口 = 只有一个抽象方法的接口。

  • 用 new InterfaceName(lambda) 适配。

  • 泛型参数不写,一律用 var 声明。

  • 匹配规则:单抽象方法 + 参数个数一致。

  • 类型不强制匹配,运行时转换。

  • 只在调用外部 Java API 时用到。

  • HimDSL 内置支持的场景(定时、事件、GUI)优先用内置写法。

  • Functional interface = an interface with one abstract method.

  • Adapt with new InterfaceName(lambda).

  • Generic parameters are omitted; declare with var.

  • Matching: single abstract method + same parameter count.

  • Types are loose; converted at runtime.

  • Only needed when calling external Java APIs.

  • For timers, events, GUI, use HimDSL's built-in forms first.


函数部分回顾 / Functions Review

函数部分到此结束。四章覆盖了:具名函数与返回值、重载与解析规则、lambda 与闭包、函数式接口适配。后面的内容会大量使用这四章的知识,但不会再引入新的函数语法。

The Functions section ends here. Four chapters covered: named functions and return values, overloading and resolution, lambdas and closures, and functional-interface adaptation. Later chapters use all four but introduce no new function syntax.


下一步 / Next Steps

下一部分进入数据结构。第十章 [数组 / Arrays] 从最基础的数组讲起。

The next part covers data structures. Chapter [数组 / Arrays] starts with the most basic: arrays.


⚠️ **GitHub.com Fallback** ⚠️