你应该在 java 中对泛型类型使用 var

Should you use var on generic types in java

我的问题是代码风格问题。因此,显然没有明确的“正确”答案。 var foo = new Foo<String>();
要么
Foo<String> foo = new Foo<>();
假设通用 class Foo 存在,这两个版本都是等效的有效 java 代码。我找不到任何关于 var 和通用类型的指南。

以下是您如何处理风格建议:

首先,永远不要盲目听从建议。实际上,没有编程建议是一条零例外的规则,可以在任何情况下盲目应用。它们是经验法则 - 通常但并非总是如此。

因此,如果您不了解规则背后的实际原因,这是没有希望的 - 您将在编程生涯中走上 cargo culting 之路,这是不可能的为了活着。在规则完全错误的情况下,您将严格应用规则!

因此,第 1 步:弄清楚 为什么 建议不要使用您在代码中执行的某些模式、样式或其他操作。你要具体原因,最好有例子。

然后,根据您的经验(可能有限)可以提出并评估的最佳示例来衡量这些具体原因。这些论点对您来说是否有意义,或者它们看起来 完全 与您获得的好处无关或不成比例?

如果是这样,那么忽略那个建议,它很愚蠢,或者你现在没有希望正确应用它

那是因为只有3个选项:

  • 反对者是 koolaid 酒鬼,完全 错误。忽略它们是正确的做法。这不太可能,但它发生在编程领域;不幸的是,狂热有些普遍。

  • 他们正在考虑甚至还没有进入您的脑海的不同用法,或者他们的代码风格与您的代码风格截然不同,他们 var 确实很糟糕,但它根本不适用于您。你听说过“你不应该使用 var”,但这并不是他们真正的意思。他们的意思是:“你不应该在 [不适用于你的情况] 中使用 var”。他们的建议应该被忽略:它不适用于你。

  • 他们发现了一些适用于您的隐藏真相,并且是不做您正在做的事情的合理理由。然而,这种推理对你来说太陌生了,你严重误判了情况。你甚至不能开始跟随它。这 可能的,有经验的程序员是一件很难处理的事情,而且编程非常复杂,因此这是一个现实的选择。但是,这也意味着您根本没有机会运用常识来扩展经验法则以实际正确地应用它。 你注定要犯他们警告你的错误,无论你是否应用他们的经验法则。

你知道什么是错误吗?它们是 菜鸟程序员转变成经验丰富的程序员所必需的东西。所以不要着急。忽略他们的建议,因为您无法应用它。如果真的是这种情况,你最终会低声发誓,然后突然意识到:他们是对的。但是现在你开悟了,并且正在成为更有经验的程序员的路上。我还没有想出一个更好的方法来将新编码员变成有经验的编码员;在此过程中需要一些错误,这可能就是其中之一。庆祝一下。

举个例子,让我们看一下var

首先,我们忽略“只是不要使用它”式的“建议”。没有理由,我们不能应用常识,这使它毫无用处。我们关注那些将可证伪性带到 table 并关注他们的推理的人。

根据我的经验,给出的两个主要命名原因是 var foo = something;:

  • 它的可读性较差 - 您根本无法分辨 foo 可能是什么。
  • 它隐藏了细节,例如,如果您将 List<String> foo = new ArrayList<>(); 替换为 var foo = new ArrayList<String>();,那么您 'lost' 认为您不关心 foo 的想法实现,你只关心它是一个 List.

幸运的是,在绝大多数代码片段案例中,这两点都不相关。至少,当我尝试将这些规则应用到我的代码风格时。当然,您的里程可能会有所不同。即:

隐藏类型

仔细阅读任何 java 项目,毫无疑问,您会发现很多很多 'chained methods' 的实例。像这样简单的东西:

someObj.getClass().getName()

someStr.toLowerCase().replace("_", "");

这些都是与上述问题一样!它们隐藏了 first 方法调用 return 的类型。毕竟,一个人怎么可能知道 someObj.getClass()java.lang.Class<?whoknows?> 类型,并且 .getName() 被调用(幸运的是,也避免了我们不知道泛型是什么的问题那里。getName() 根本不关心它们,所以它们在这里不相关)。同样,someStr.toLowerCase()return是什么意思?我们在调用 replace 什么样的东西?完全是个谜!

当然这不是一个谜,即使是非常初级的程序员,哎呀,在其他语言方面有一些经验但在 java 方面几乎为零的程序员无疑会对以上两个片段完全不感兴趣. getClass() return 是 Class 的实例?天哪,真是个惊喜。 .toLowerCase() return 是一个字符串?你不说!让我做一些笔记!

很明显,任何提倡这些规则的人都是伪君子,或者至少是在很大程度上过于简单化了。除非他们在 var 引入之前实际上禁止在他们的代码库中进行任何类型的任何方法链接。我认识一些 var 不喜欢的人。 None 他们不喜欢方法链。我还没有听到他们对这些概念明显的双重想法的充分理由。

但它也突出了var的一个关键方面。方法链接并不总是正确的。出于同样的原因,var 也不是。如果你这样写:

// This is an entirely hypothetical method!
foo.pingPeer(message, sender).getName()

那么这也许不是个好主意。 pingPeer return 是什么意思?我不知道。除非随便熟悉一下 API 的含义会使答案显而易见,否则以上内容可能不是一个好主意。 pingPeergetName 都不知道这里到底发生了什么。方法链接隐藏了重要信息。由于这个原因,这段代码是糟糕的代码风格——它比明显的替代品更不清晰:

MessageReply reply = foo.pingPeer(message, sender);
reply.getName();

是的,它更长,更长的代码通常更糟糕,但并非总是如此。这是其中一个案例。

(我也不知道回复如何有名字。正如我所说,假设 API 我只是编造的。回复有名字的事实令人惊讶是要点的一部分:而且有理由确保仔细阅读此代码的任何人都清楚您所获得的名称)。

因此,规则:仅当现在隐藏的类型可根据上下文 轻松确定时才使用var。如果类型名称可以从变量名称或方法中简单地推导出来,则可以很好地提示这种情况,例如:

// eligible for replacing with var
List<Student> x = course.getEnrolledStudents();
List<Student> students = course.getEnrolled();

// Not so clear cut, use careful judgement
List<Student> x = course.getEnrolled();

类型特异性损失

相同的方法链接参数也适用于此处,但程度较低。这个困境的解决方案也很相似:如果感觉省略这些信息会影响随意浏览该方法的可读性,那么不要使用 var.

如果您从不重新分配变量,一个知道它不太可能重要的简单方法是。如果你写:

public void List<String> foo() {
  var out = new ArrayList<String>();
  // code that interacts with "out",
  // but never overwrites it, i.e. no out = ... anywhere..
  return out;
}

那么你可能想将out标记为final,但总的来说,感觉缺少表示'actual list impl does not really matter'与理解这段代码的内容特别相关确实如此,而且这段代码的 reader 不太可能完全被错误地认为是徒劳的追逐:“哈!原作者一定非常关心列表,具体来说,是一个数组列表!我”我要阅读整个方法来寻找原因,而不是专注于它在做什么!”。

我会毫不犹豫地在这里使用 var

我不知道的第三个原因

如果我编程的时间足够长并且参与了足够多的不同类型的项目,也许有一天我会意识到 var 的使用给我带来了很多痛苦,现在我知道了第三个原因。事实上,我永远不会忘记它。我会低声发誓,但那一刻会过去,因为我也在庆祝我刚刚成为一个更好的程序员。我将重构代码并将其起飞时间写为必须解决的未知技术债务。