12

可能的重复:
经常滥用?

我已经看到-1在各种 API 中使用过,最常见的是在搜索具有从零开始的索引的“集合”时,通常表示“未找到”索引。这“有效”是因为-1从一开始就不是一个合法的索引。似乎任何负数都应该起作用,但我认为-1几乎总是被用作某种(不成文的?)约定。

我想至少现在将范围限制在 Java 上。我的问题是:

  • Sun 关于使用-1这样的“特殊”返回值的官方说法是什么?
  • 关于这个问题有哪些引用,例如 James Gosling、Josh Bloch,甚至是 Java 之外的其他权威人物?
  • 过去关于这个问题的一些值得注意的讨论是什么?
4

10 回答 10

11

这是类型不包括范围检查的语言中的常见习语。“超出范围”值用于指示若干条件之一。在这里,返回值表示两件事:1) 是找到的字符,以及 2) 在哪里找到的。使用 -1not found和非负索引简洁地将这两者编码为一个值,并且不需要返回索引found的事实。not-found

在具有严格范围检查的语言中,例如 Ada 或 Pascal,该方法可能被实现为(伪代码)

   bool indexOf(c:char, position:out Positive);

Positive是 int 的子类型,但仅限于非负值。

这将找到/未找到标志与位置分开。该位置作为输出参数提供 - 本质上是另一个返回值。它也可以是一个输入输出参数,从给定位置开始搜索。此处不允许使用 -1 表示未找到,因为它违反了对 Positive 类型的范围检查。

java中的替代方案是:

  • 抛出异常:这不是一个好的选择,因为找不到字符不是异常情况。
  • 将结果拆分为几种方法,例如boolean indexOf(char c); int lastFoundIndex();. 这意味着对象必须保持状态,这在并发程序中不起作用,除非状态存储在线程本地存储中,或者使用同步——所有这些都是相当大的开销。
  • 分别返回位置和找到的标志:如boolean indexOf(char c, Position pos). 在这里,创建位置对象可能被视为不必要的开销。
  • 创建多值返回类型

class FindIndex {
   boolean found;
   int position;
}

FindIndex indexOf(char c);

尽管它清楚地分离了返回值,但它会遭受对象创建开销。其中一些可以通过传递FindIndex作为参数来缓解,例如

FindIndex indexOf(char c, FindIndex start);

顺便说一句,多个返回值将成为 java (oak) 的一部分,但在 1.0 之前被取消以缩短发布时间。詹姆斯高斯林他希望他们被包括在内。它仍然是一个希望的功能

我的看法是,使用魔法值是在单个返回值中编码多值结果(标志和值)的实用方法,不需要过多的对象创建开销。

但是,如果使用魔法值,如果它们在相关的 api 调用中保持一致,则使用起来会更好。例如,

   // get everything after the first c
   int index = str.indexOf('c');
   String afterC = str.substring(index);

Java 在这方面做得不够,因为在调用中使用 -1substring会导致IndeOutOfBoundsException. 相反,如果认为负值从字符串的末尾开始,则在使用 -1 调用时,子字符串返回“”可能会更加一致。错误条件的魔法值的批评者说返回值可以被忽略(或假设为正)。以一种有用的方式处理这些魔法值的一致 api 将减少检查 -1 的需要并允许更清晰的代码。

于 2010-06-13T11:38:50.667 回答
4

-1 是一个神奇的数字吗?

在这种情况下,并非如此。...没有什么特别-1的,除了因为它是负数而保证它是一个无效的索引值。

反模式?

不,要成为反模式,这个习语需要有一些有害的东西。我认为使用-1这种方式没有任何害处。

代码味道?

同上。(使用命名常量而不是纯-1文字可以说是更好的风格。但我认为这不是你要问的,无论如何,它不会算作“代码气味”,IMO。)

当局的报价和指导方针

不是我知道的。但是,我会观察到这个“设备”用于各种标准类。例如,String.indexOf(...)返回-1说找不到字符或子字符串。


就我而言,这只是一种在某些情况下有用的“算法设备”。我敢肯定,如果您回顾文献,您会看到以这种方式使用-1(或0用于基于 one 的数组的语言)的示例可以追溯到 1960 年代及之前。

在这种情况下,选择-1而不是其他负数只是个人喜好问题,并且(IMO)不值得分析。


-1方法返回(或其他值)来指示错误而不是抛出异常可能是一个坏主意。但是,这里的问题不在于返回的值,而是该方法要求调用者显式测试错误这一事实。

另一方面是,如果由-1(或其他)表示的“条件”不是“错误”/“异常条件”,那么返回特殊值既合理又适当。

于 2010-06-13T12:50:59.543 回答
3

-1当找不到索引时,Java 和 JavaScript 都会使用。由于索引始终0-n是一个非常明显的选择。

//JavaScript
var url = 'example.com/foo?bar&admin=true';
if(url.indexOf('&admin') != -1){
  alert('we likely have an insecure app!');
}

我发现这种方法(我在扩展 Array 类型元素以具有.indexOf()方法时使用过)非常正常。

另一方面,您可以尝试 PHP 方法,例如strpos()但恕我直言,由于有多种返回类型,它会让人感到困惑(它在未找到时返回 FALSE)

于 2010-06-13T11:15:16.073 回答
2

-1 作为返回值有点难看但很有必要。恕我直言,发出“未找到”情况的替代方案要糟糕得多:

  • 您可以抛出异常,但这并不理想,因为异常最好用于指示需要某种形式的恢复或传播故障的意外情况。没有找到子字符串的出现实际上是非常预期的。异常抛出也有显着的性能损失。

  • 您可以将复合结果对象与 (found,index) 一起使用,但这需要调用者进行对象分配和更复杂的代码来检查结果。

  • 您可以为 contains 和 indexOf 分离出两个单独的函数调用 - 但是这对于调用者来说又是相当麻烦的并且还会导致性能下降,因为这两个调用都是 O(n) 并且需要完全遍历字符串。

就个人而言,我从不喜欢引用 -1 常量:我对 not-found 的测试总是类似于:

int i = someString.indexOf("substring");
if (i>=0) {
  // do stuff with found index
} else {
  // handle not found case
}
于 2010-06-13T13:22:05.223 回答
1

为代码中的所有常量值定义最终类变量是一种很好的做法。但一般都接受使用 0、1、-1、""(空字符串)而无需显式声明。

于 2010-06-13T11:16:46.280 回答
1

这是从 C 的继承,其中只能返回一个原始值。在java中你也可以返回一个对象。

因此,对于新代码,返回一个基类型的对象,其子类型指示要与 instaceof 一起使用的问题,或者抛出“未找到”异常。

对于现有的特殊值,请在您的代码名称中相应地使 -1 成为常量 - NOT_FOUND - 这样读者无需检查 javadocs 即可知道其含义。

于 2010-06-13T11:17:28.557 回答
1

与 with 相同的做法null适用于-1. 它被讨论了很多次。

例如Java api 设计 - NULL 或 Exception

于 2010-06-13T11:18:52.190 回答
1

之所以使用它,是因为它是您在基于 0 的数组中遇到的第一个无效值。如您所知,并非所有类型都可以保存 null 或什么都没有,因此需要“某物”来表示什么。

我想说它不是官方的,它刚刚成为惯例(不成文),因为它对这种情况非常明智。就个人而言,我也不会称之为问题。API 设计也由作者负责,但可以在网上找到指南

于 2010-06-13T11:24:17.243 回答
1

据我所知,这些值被称为哨兵值,尽管大多数常见的定义与这种情况略有不同。

Java 等语言选择不支持通过引用传递(我认为这是一个好主意),因此虽然单个参数的值是可变的,但传递给函数的变量不受影响。因此,您只能拥有一种类型的返回值。所以你要做的是选择一个有效类型的其他无效值,并将其返回以传输附加语义,因为返回值实际上不是操作的返回值,而是一个特殊的信号。

现在我想,最干净的方法是拥有一个contains和一个indexOf方法,如果您要求的元素不在集合中,则第二个方法会引发异常。为什么?因为人们会期望以下是真的:

someCollection.objectAtIndex(someCollection.indexOf(someObject)) == someObject

您可能会得到一个异常,因为-1它超出了界限,而这种似是而非的关系不正确的实际原因是,它someObject不是 的元素someCollection,这就是内部调用应该引发异常的原因。

现在,尽管它可能是干净和健壮的,但它有两个关键缺陷:

  • 通常这两种操作通常都会花费你 O(n) (除非你在集合中有一个逆映射),所以你最好只做一个。
  • 这真的很冗长。

最后,由您决定。这是一个哲学问题。我将其称为“语义黑客”,以牺牲健壮性为代价实现简短和速度。你的来电 ;)

问候
back2dos

于 2010-06-13T11:41:10.700 回答
1

就像为什么 51% 意味着公司股东之间的一切,因为它是最接近且有意义的,而不是 -2 或 -3 ...

于 2010-06-13T12:09:26.000 回答