使用“默认”参数值重载 Java 方法

Posted

技术标签:

【中文标题】使用“默认”参数值重载 Java 方法【英文标题】:Java method overloading with "default" argument values 【发布时间】:2022-01-18 07:39:45 【问题描述】:

首先,我知道官方对这个问题的回答是,它根本无法在原生 Java 环境中完成。所以我不是在问这是否是 Java 本身可以做的事情。我只是想问是否可以在 Java 中完成 - 也许通过我不知道的其他方式。

我一直在尝试使用我想要的库,开发人员可以在其中与对象交互,通过不引用它可以识别该对象,然后我只需在库中通过默认名称,或者,开发人员可以为对象分配名称,在这种情况下,每个给定的新名称都会在库中创建一个新的对象实例。

但是在提供与库的功能相关的公共方法方面,我不得不为每种方法创建两个,这显着增加了库的大小,其中很多都必须复制因为 Java 进行方法重载的方式。是的,我确实在所有可能的地方合并重复代码,但这并没有改变我必须为每个提供的函数创建两个方法的事实。

理想情况下,这个

public static void getThis(float value, String objectName) 
   //related code for objectName


public static void getThis(float value) 
  //related code for the defaultObjectName

如果我可以为 objectName 分配一个默认值,就会变成这个

public static void getThis(float value, String objectName = defaultObjectName) 
  //related code handles objectName

所以我的问题是,这可以通过任何方式来完成吗?也许是第三方选项?

另外,有谁知道为什么 Java 不提供这种能力?

编辑:阅读第一条评论后,我删除了最后一个问题的一部分。

【问题讨论】:

请阅读***.com/help/dont-ask。这个问题要么是询问第三方工具,要么是抱怨为什么该语言不能按照你想要的方式工作——不管怎样,这对 Stack Overflow 来说都是无关紧要的。诸如“是否违反某种法律”之类的短语也无助于善意问题。 也就是说:按照最典型的标准,Java 是“面向对象”语言的特别差示例。最值得注意的是,Java 中的许多东西不是对象,而其他语言(例如 Python)中的东西。而且,OOP 与问题无关;没有理由阻止没有“对象”概念的语言具有“默认参数”的概念,也没有理由“面向对象”应该要求函数或方法的默认参数。 @KarlKnechtel 我当然不是在咆哮,也不是在抱怨……这绝不是我问题的意图。我认为 Java 没有提供这种能力是有充分理由的,尽管我不知道那是什么......我的意思是这篇文章没有任何不和谐。 【参考方案1】:

另外,有谁知道为什么 Java 不提供这种能力,而许多其他语言(例如 C++)提供这种能力?在严格的 OOP 标准空间中是否存在某种违法行为?

没有坚定的信念,没有。它比你想象的要复杂,团队正在处理其他事情;它不会在接下来的 2 到 3 个版本中出现,但可能会在以后出现(请注意,每 6 个月发布一个版本)。

所以我的问题是,这可以通过任何方式完成吗?也许是第三方选项?

Builder 模式或 lombok 风格的注解处理器似乎是相关的。我们(来自 Project Lombok)还没有添加它,因为它真的很复杂。如果您的默认值是System.currentTimeMillis(),那么每次调用该方法时可能应该“实时”计算。但是,如果默认值是“someComplexConstant”,是否应该每次都毫无意义地重新计算?我敢肯定你有一个偏好,但这正是问题所在:有些人想要 A,有些人想要 B,而且这两个阵营都没有 90% 以上的市场份额,这意味着你有大量程序员得到了令人讨厌的惊喜(例如: Python 的默认 args 只评估一次。“每次都重新评估”表面上更明智,但人们总是从他们知道的其他语言中带来包袱,因此您可以轻松地得到“两个阵营都没有压倒性多数”,即,至少对我们来说,是个大人物)。

可能的解决方案是简单地不允许任何不是编译时常数的东西,但是你会用 CTC 的定义来打扰用户,这些定义并不像人们想象的那么明确(你知道吗null例如,不是一个吗?也许我们应该允许所有 CTC 以及 null... 但像 LocalDate.of(1900, 1, 1) 这样的东西也不是一个,这似乎是一个很大的烦恼!)。

自行构建它的成本极不可能值得您花时间。如果你真的想要,你可以创建一个从不被任何东西使用的“模板”类,除了一个注释处理器,它会生成你真正想要的实际源文件。然而,沿着这条路走下去有重大的开发过程缺点:在您运行带有注释处理器的完整编译构建之前,您的代码会被破坏,这需要时间,因此会减慢您的开发周期。任何时候你弄乱了“模板”,你很可能会以“在我们进行完整的清理和重建周期之前一切都完全崩溃”而告终,如果你这样做,那么你就做错了。您希望在最多 2 秒内看到您编写的任何内容的效果 - 热代码替换、增量编译和快速启动至关重要(您可以在 eclipse 中免费获得 HCR,在几乎每个 IDE 中获得 IC)。

Lombok 通过直接修补您的 IDE 来解决这个问题,以便在您键入时以增量方式处理注释,但正如我所提到的,这是一个难以实现的主要功能,现在 lombok 坚持简单的规则注释处理必须能够完成这项工作,这意味着输入源文件必须在语法上正确。 void foo(int x = 5) 不是,而且替代品很烂 (void foo(@DefaultValueInt(5) x)? Meh)。

第三种选择是放弃语法上有效的 java 的想法并修补所有内容。但在某些时候,您必须承认您只是在编写将在启动时接管所有系统并破解/修补所有相关工具以实际解析/支持不再是 java 的语言的软件。

我们可以尝试支持LocalDate.of 并硬编码一个“已知”常量值的大列表,或者允许hackery 在您将静态最终字段设置为默认值的情况下,然后您可以根据需要对其进行定义。这一切都会奏效,但一个不错的功能的要点大概是它对所有人都有意义。如果您需要阅读 5 页的文档并在一个月后重新阅读,因为它的实际工作方式对您来说不合逻辑 - 这是一个真正的问题。

此外,编写 lombok 或类似的东西所需的工作量非常大,更不用说维护它了。

消息来源:我作为核心 lombok 开发人员已经有 12 年了 :)

【讨论】:

天哪!这是我从未预料到的答案。尽管我现在了解你们在该语言的这种特殊用例中所面临的问题。我相信你们都讨论过其他选项,通过更严格地定义可以实现的方式,可以减轻或消除其中一些复杂性 - 比如说只允许在这些值是现有的实例化类时分配默认值,例如String, int 等不需要计算或导出被分配的值 - 也许作为起点? 我想我在这里的意思是,如果 Java 中不存在这种能力的原因是该功能的高级实现是高端开发人员争论的焦点,那么为什么不是说,“我们将及时处理这些用例,但现在,至少让我们提供一个基本实现,这样做不需要复杂的工程” - 例如我需要传递的值是一个需要的字符串没有复杂的代码来获得价值。当然,即使是一个基本的实现作为起点,也可以帮助无数开发人员减少他们的代码足迹。 我还要补充一点,提供这样的实现将激励许多开发人员走出 1.8 的“安全区”(我喜欢这样称呼它),许多开发人员认为没有必要使用更新的版本爪哇的。在 switch case 中所做的更改非常好,当这些便利不断添加到 java 中时,开发人员将看到使用更新版本的价值。但这只是我的看法——至少从我自己的经验来说。 是的,'只有字符串和 int 字面量,而 null' 是值得简化的。然后我们进入语法问题(显然void foo(int x = 5) 是最好的,但这不可能。还有组合爆炸,但这很容易通过仅按线性顺序解决(void foo(int a = 1, long b = 2, short b = 3) 给你foo()foo(int a)foo(int a, long b)foo(int a, long b, short c)。不是foo(long b),例如。但有时这就是你想要的......也很棘手。 为什么不先开始并在以后弄清楚复杂的情况:因为 java 希望保持向后兼容,有时从一个方向开始会关门。在开始之前,您至少需要对如何处理更高级的用例进行一些尽职调查。 “预览特性”在这里很有用——它们为 openjdk 团队提供了引入特性并在以后破坏它们的机会。 Lombok 也有这个(lombok.experimental 包)。【参考方案2】:

你可以考虑这样的结构:

interface ContextFactory 
  Context getDefaultContext();
  Context getExplicitContext(String objectName);


interface Context 
  void getThis(float value);

顺便说一句,你给库的用户提供静态方法并没有给他们任何好处。他们将如何对代码进行单元测试?

假设我写:

...
void doIt() 
  if (Library.getThis(10) > 0) 
    service.effectSomething();
   else 
    service.effectSomethingElse();
  

...

我想为那个函数写一个单元测试。

我必须以某种方式进行设置,以便在我的一个测试用例中 Library.getThis(10) 返回大于零,而在另一个测试用例中它返回零或更少。

如果我有一个可以模拟的接口,它已经注入到我的类中,那么这很容易。如果我要调用静态方法,那就很难了。

【讨论】:

库主要是一个与其上下文相关的实用程序类。它会违背要求它被实例化的目的,因为它旨在在项目中全局使用。虽然我意识到开发人员可以在他们的项目中全局声明它,但我的目标是保持简单。但我对有关该主题的其他想法或讨论持开放态度,因为我总是渴望学习如何以正确的方式做事。 看看你使用接口的例子——我以前从未使用过接口,所以我阅读了一些关于它们使用的教程,但我仍然不清楚如何为用户提供library 能够为 objectName 传递他们自己的 String,同时还提供不传递该 String 的能力,从而库使用默认值。换句话说,我看不出这如何让我编写一种方法来处理这两种情况。您能否修改您的答案以包含一个如何工作的示例?

以上是关于使用“默认”参数值重载 Java 方法的主要内容,如果未能解决你的问题,请参考以下文章

Java函数(方法)的默认值问题

java函数参数默认值

java学习--- 方法

比较Java方法的重载与覆盖

Java重载和重写的区别

Java重载(Overload)与重写(Override)