为啥“子字符串(startIndex,endIndex)”没有抛出“超出范围”

Posted

技术标签:

【中文标题】为啥“子字符串(startIndex,endIndex)”没有抛出“超出范围”【英文标题】:Why is "out of range" not thrown for 'substring(startIndex, endIndex)'为什么“子字符串(startIndex,endIndex)”没有抛出“超出范围” 【发布时间】:2011-03-15 02:04:01 【问题描述】:

在 Java 中,我使用的是 substring() 方法,但我不确定它为什么没有抛出“超出索引”错误。

字符串 abcde 的索引从 0 到 4 开始,但 substring() 方法将 startIndex 和 endIndex 作为参数,因为我可以调用 foo.substring(0) 并获得“abcde”。

那么为什么 substring(5) 有效?该索引应该超出范围。有什么解释?

/*
1234
abcde
*/
String foo = "abcde";
System.out.println(foo.substring(0));
System.out.println(foo.substring(1));
System.out.println(foo.substring(2));
System.out.println(foo.substring(3));
System.out.println(foo.substring(4));
System.out.println(foo.substring(5));

此代码输出:

abcde
bcde
cde
de
e
     //foo.substring(5) output nothing here, isn't this out of range?

当我将 5 替换为 6 时:

foo.substring(6)

然后我得到错误:

Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
    String index out of range: -1

【问题讨论】:

【参考方案1】:

substring(5) 指向一个现有的索引...它恰好指向一个空字符串。另一方面,substring(6) 只是疯狂的谈话。 :)

【讨论】:

【参考方案2】:

当您执行foo.substring(5) 时,它会获取从“e”之后的位置开始并在字符串末尾结束的子字符串。顺便说一句,开始和结束位置恰好是相同的。因此,空字符串。您可以认为索引不是字符串中的实际字符,而是字符之间的位置。

        ---------------------
String: | a | b | c | d | e |
        ---------------------
Index:  0   1   2   3   4   5

【讨论】:

【参考方案3】:

根据Java API doc,当起始索引大于字符串的长度时,子字符串会抛出错误。

IndexOutOfBoundsException - 如果 beginIndex 为负数或大于 此 String 对象的长度。

事实上,他们举了一个很像你的例子:

"emptiness".substring(9) returns "" (an empty string)

我想这意味着最好将 Java 字符串视为以下内容,其中索引包含在 | 中:

|0| A |1| B |2| C |3| D |4| E |5|

也就是说一个字符串既有开始索引又有结束索引。

【讨论】:

啊!感谢您的提示,我正在查看相同的文档页面,但不知道我必须一直向下滚动才能获得更多详细信息... 希望 javadoc 可以对此进行注释,否则像我这样粗心的人会期望 IndexOutOfBoundsException 如果 beginIndex=String.length() 发生。【参考方案4】:

这是因为 substring 函数返回一个“包含”的子字符串。所以索引 5 指向字符串末尾之前的位置,但在字符串的最后一个显示字符之后。

这显示在文档中: http://download.oracle.com/docs/cd/E17476_01/javase/1.4.2/docs/api/java/lang/String.html#substring(int)

【讨论】:

【参考方案5】:

来自字符串 API javadoc:

public String substring(int beginIndex)
    Returns a new string that is a substring of this 
    string. The substring begins with the "" character 
    at the specified index and extends to the end of this string.

public String substring(int beginIndex, int endIndex)
    Returns a new string that is a substring of this 
    string. The substring begins at the specified beginIndex 
    and extends to the character at index endIndex - 1. Thus 
    the length of the substring is endIndex-beginIndex.

例子:

"unhappy".substring(2) returns "happy" 
"Harbison".substring(3) returns "bison"
"emptiness".substring(9) returns "" (an empty string)

"hamburger".substring(4, 8) returns "urge"
"smiles".substring(1, 5) returns "mile"

参数:

beginIndex - the beginning index, inclusive.
Returns:
the specified substring.
Throws:
IndexOutOfBoundsException - if beginIndex is negative or 
larger than the length of this String object.

====

所以这是设计使然。如果将索引作为字符串的大小,则返回空字符串。

【讨论】:

【参考方案6】:

我知道这个帖子已经很老了,但这是一个非常基本的问题,我认为它值得澄清。

问题是正确的。我认为这是 Java String.substring(int beginIndex, int endIndex) 方法中的软件故障。

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#substring%28int,%20int%29.

来自 Java 文档https://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html

Java/C/C++ 和我所知道的所有其他语言都不会将数组索引视为数组元素之间的“分隔符”。

参数: beginIndex - 起始索引,包括在内。 endIndex - 结束索引,不包含。

endIndex 名称错误,因为该语言不允许内存访问 endIndex + 1 处的地址,该地址需要包含最后一个数组元素,或者 endIndex 定义错误并且必须是: endIndex - 结束索引,包括。

最有可能的情况是第二个参数命名错误。它应该是: length - 从 beginIndex 开始的所需字符串的长度。

我们知道 Gosling 基于 C/C++ 语言的 Java 语法是为了熟悉。从C++字符串类http://www.cplusplus.com/reference/string/string/substr/我们看到方法定义是:

string substr (size_t pos = 0, size_t len = npos) const;

请注意,方法定义中的第二个参数是 'len' 表示长度。

长度 要包含在子字符串中的字符数(如果字符串较短,则使用尽可能多的字符)。

testString 有 10 个字符,索引位置为 0 到 9。指定 10 的 endIndex 应该总是抛出 IndexOutOfBoundsException(),因为 testString 没有 10 的 endIndex。

如果我们用 C++ 方法的具体值测试 JUnit 中的方法,我们期望:

字符串 testString = "testString"; assertThat(testString.substring(4, 6), equalTo("String"));

但我们当然会得到 Expected: "String" but was "St"

'String' 中从索引 0 到字符 'g' 的 testString 长度为 10 个字符。 如果我们使用 10 作为 'endIndex' 参数,

字符串 testString = "testString"; assertThat(testString.substring(4, 10), equalTo("String"));

来自 JUnit 的“通过”。

如果我们将参数 2 重命名为“lengthOfSubstringFromIndex0”,则您不必进行 endIndex - 1 计数,并且它永远不会抛出 IndexOutOfBoundsException(),而在指定 endIndex 10 时,它超出了底层数组。 http://docs.oracle.com/javase/7/docs/api/java/lang/IndexOutOfBoundsException.html

这只是您必须记住这种方法的特殊性的那些时候之一。第二个参数命名不正确。 Java 方法签名应该是:

public String substring(int beginIndex,
           int lengthOfSubstringFromIndex0)

或重新定义方法以匹配 C++ string::substr 方法。重新定义当然意味着重写整个互联网,所以不太可能。

【讨论】:

以上是关于为啥“子字符串(startIndex,endIndex)”没有抛出“超出范围”的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?

为啥 CAP 定理中的 RDBMS 分区不能容忍,为啥它可用?