今天我有一位同事建议我重构我的代码以使用标签语句来控制通过我创建的 2 个嵌套 for 循环的流程。我以前从未使用过它们,因为我个人认为它们会降低程序的可读性。但是,如果论点足够可靠,我愿意改变使用它们的想法。人们对标签声明有何看法?
12 回答
如果您可以跳过两个循环(或包含 switch 语句的循环),则可以更容易地表达许多算法。不要为此感到难过。另一方面,它可能表明解决方案过于复杂。所以退后一步,看看问题。
有些人更喜欢所有循环的“单入口,单出口”方法。也就是说,完全避免中断(和继续)和提前返回循环。这可能会导致一些重复的代码。
我强烈避免做的是引入辅助变量。在状态中隐藏控制流会增加混乱。
将标记的循环分成两种方法可能很困难。异常可能太重了。尝试单进单出的方法。
标签就像 goto 的:谨慎使用它们,只有当它们使您的代码更快,更重要的是,更易于理解时,
例如,如果您处于六级深度的大循环中,并且遇到了使循环的其余部分无法完成的条件,那么在条件语句中设置 6 个额外的陷阱门以提前退出循环是没有意义的。
标签(和 goto 的)并不邪恶,只是有时人们会以不好的方式使用它们。大多数时候,我们实际上是在尝试编写我们的代码,以便您和下一个出现的程序员可以理解。让它超快是次要的问题(提防过早的优化)。
当标签(和 goto 的)被滥用时,它们会降低代码的可读性,这会给您和下一个开发人员带来痛苦。编译器不在乎。
很少有需要标签的情况,而且它们可能会令人困惑,因为它们很少使用。但是,如果您需要使用一个,请使用一个。
顺便说一句:这会编译并运行。
class MyFirstJavaProg {
public static void main(String args[]) {
http://www.javacoffeebreak.com/java101/java101.html
System.out.println("Hello World!");
}
}
我很想知道你的标签替代品是什么。我认为这几乎可以归结为“尽早返回”与“使用变量来保存返回值,并且只在最后返回”的论点。
当您有嵌套循环时,标签是非常标准的。它们真正降低可读性的唯一方法是其他开发人员以前从未见过它们并且不理解它们的含义。
我使用 Java 标记的循环来实现 Sieve 方法来查找素数(针对项目 Euler 数学问题之一完成),与嵌套循环相比,它的速度提高了 10 倍。例如,如果(某些条件)回到外循环。
private static void testByFactoring() {
primes: for (int ctr = 0; ctr < m_toFactor.length; ctr++) {
int toTest = m_toFactor[ctr];
for (int ctr2 = 0; ctr2 < m_divisors.length; ctr2++) {
// max (int) Math.sqrt(m_numberToTest) + 1 iterations
if (toTest != m_divisors[ctr2]
&& toTest % m_divisors[ctr2] == 0) {
continue primes;
}
} // end of the divisor loop
} // end of primes loop
} // method
我问过一个 C++ 程序员,标记循环有多糟糕,他说他会谨慎使用它们,但它们偶尔会派上用场。例如,如果您有 3 个嵌套循环,并且对于某些条件,您想返回最外层的循环。
所以它们有它们的用途,这取决于你试图解决的问题。
我从未见过在 Java 代码中“广泛使用”的标签。如果您真的想打破嵌套循环,请查看是否可以重构您的方法,以便提前返回语句执行您想要的操作。
从技术上讲,我想提前回归和标签之间没有太大区别。但实际上,几乎每个 Java 开发人员都看到了早期的回报并且知道它做了什么。我猜许多开发人员至少会对标签感到惊讶,并且可能会感到困惑。
我在学校里学习过单入口/单出口的正统观念,但从那以后我开始欣赏早期的返回语句和打破循环作为简化代码并使代码更清晰的一种方式。
我认为使用新的 for-each 循环,标签可以非常清晰。
例如:
sentence: for(Sentence sentence: paragraph) {
for(String word: sentence) {
// do something
if(isDone()) {
continue sentence;
}
}
}
我认为通过让您的标签与新 for-each 中的变量相同,这看起来真的很清楚。事实上,也许Java应该是邪恶的,并为每个变量添加隐式标签嘿嘿
我会在某些地方支持它们,我发现它们在这个例子中特别有用:
nextItem: for(CartItem item : user.getCart()) {
nextCondition : for(PurchaseCondition cond : item.getConditions()) {
if(!cond.check())
continue nextItem;
else
continue nextCondition;
}
purchasedItems.add(item);
}
我从不在代码中使用标签。我更喜欢创建一个守卫并将其初始化为null或其他不寻常的值。这个守卫通常是一个结果对象。我没有看到我的任何同事使用标签,也没有在我们的存储库中找到任何标签。这实际上取决于您的编码风格。在我看来,使用标签会降低可读性,因为它不是一种常见的构造,而且通常不在 Java 中使用。
是的,你应该避免使用标签,除非有特定的理由使用它们(它简化算法实现的例子是相关的)。在这种情况下,我建议添加足够的注释或其他文档来解释其背后的原因,这样以后就不会有人出现并从“改进代码”或“摆脱代码异味”的概念中破坏它或其他一些潜在的 BS 借口。
我会将这类问题等同于决定何时应该或不应该使用三元 if。主要理由是它会妨碍可读性,除非程序员非常小心地以合理的方式命名事物,否则使用诸如标签之类的约定可能会使事情变得更糟。假设使用“nextCondition”和“nextItem”的示例使用“loop1”和“loop2”作为他的标签名称。
个人标签是对我来说没有多大意义的功能之一,除了汇编或 BASIC 和其他类似的有限语言。Java 有很多更传统/常规的循环和控制结构。
我发现标签有时在测试中很有用,可以分离通常的设置、练习和验证阶段以及分组相关语句。例如,使用 BDD 术语:
@Test
public void should_Clear_Cached_Element() throws Exception {
given: {
elementStream = defaultStream();
elementStream.readElement();
Assume.assumeNotNull(elementStream.lastRead());
}
when:
elementStream.clearLast();
then:
assertThat(elementStream.lastRead()).isEmpty();
}
您的格式选择可能会有所不同,但核心思想是,在这种情况下,标签在构成您的测试的逻辑部分之间提供了明显的区别,比注释更好。我认为Spock库只是基于这个特性来声明它的测试阶段。
就个人而言,每当我需要使用嵌套循环而最里面的循环必须跳出所有父循环时,我只需在满足我的条件时将所有内容写在一个带有 return 语句的方法中,它更具可读性和逻辑性。
示例使用方法:
private static boolean exists(int[][] array, int searchFor) {
for (int[] nums : array) {
for (int num : nums) {
if (num == searchFor) {
return true;
}
}
}
return false;
}
使用标签的示例(不太可读的 imo):
boolean exists = false;
existenceLoop:
for (int[] nums : array) {
for (int num : nums) {
if (num == searchFor) {
exists = true;
break existenceLoop;
}
}
}
return exists;