【问题标题】:An edge case in class String?String 类的边缘情况?
【发布时间】:2013-11-11 06:37:16
【问题描述】:

考虑以下场景:

String str = "str";
System.out.println("str subs: " + str.substring(3,3));

预期结果:
StringIndexOutOfBoundsException(因为 beginIndex 在字符串结束“之后”开始)

实际结果:
打印空字符串

来自String.java

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

很容易看出,实现没有处理以下边缘情况:
beginIndex == endIndex == countcount 是字符串的长度)。

根据the manual方法子串:

返回一个新字符串,它是该字符串的子字符串。子串 从指定的 beginIndex 开始并延伸到字符 index endIndex - 1。因此子字符串的长度是 endIndex-beginIndex.

它还指出该方法抛出:

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

makes sense 是否考虑以下情况:beginIndex == endIndex == count 有效?
我错过了什么吗?

【问题讨论】:

  • 这当然是有争议的,但我个人喜欢这种行为。 PHP 的substr 函数在这种情况下返回false,我发现这是一个烦人的边缘情况,我从某个字符串缓冲区中提取数据,一旦它被完全清空而不是"",就会变成false,因此打破了后来的$buf === "" 支票。
  • @Boann 我没有探索substr,在 Java 中这种行为对我来说似乎 arguable (实际上 - 我正在寻找一个论点可以解释这种行为但没有找到——这就是我发布这个问题的原因。
  • 子字符串extends to the character at index endIndex - 1。你为什么不期待substring(0,0) 例外?
  • @Arian 因为string[0] 没有越界。
  • 对于endIndex = 0,要包含的最后一个字符位于-1,与startIndex = length 一样超出范围。然而,您接受0,0 成功。 3,3 也一样,只是从字符串的另一端开始。

标签: java string substring


【解决方案1】:
"abc".substring(3,3) == ""

如你所说,我们来看看manual

返回一个新字符串,它是该字符串的子字符串。

好的

子字符串从指定的 beginIndex 开始并延伸到索引 endIndex - 1 处的字符。

无论字符串的长度如何,这句话的解释都很困难。但我认为我们可以同意空字符串不违反这一点。

因此子串的长度是endIndex-beginIndex。

好的

抛出:IndexOutOfBoundsException - 如果 beginIndex 为负数

不是

或者endIndex大于这个String对象的长度

不是

或者 beginIndex 大于 endIndex。

不是。

行为似乎对我有所承诺。

你也可以这样看:字符串"abc"包含四个空子字符串,两个字符之间,一个在开头,一个在结尾。它们可以分别通过substring1,12,20,03,3 访问。也和下面的代码比较一下

class EmptyTest {

    public static void main (String[] args) {

         Matcher m = Pattern.compile("").matcher("abc");
         while (m.find()) {
            System.out.println(m.start() + "," + m.end());
         }
    }
}

打印出来的

0,0
1,1
2,2
3,3

【讨论】:

  • +1 以获得很好的详细答案 - 您显然仔细研究了文档!然而,索引 3 是 outOfBound 的事实是显而易见的(尝试将字符串转换为 char 数组并访问索引 length 处的项目)。根据规范,对于 end-index 这是很好的原因,它被设计为最后一项+1 的正确标记,但无论如何都没有记录或详细说明 begin-index 可能超出范围。模式匹配器也“越界”的事实是这个错误的直接结果......
  • 访问3 的字符在某种程度上等同于substring(3,4),这也失败了。关键是,3,3 是一个实际的子字符串,所以它应该可以通过substring 访问,不是吗?
  • 字母 c 位于索引 2,所以我不明白您为什么将 [3,3] 视为实际的子字符串
  • 3,3 标识字符串末尾的空子字符串,after c,位于索引 2 处。除非您不同意“abc " 有 四个 空子字符串。
  • 我也认为这是最合理的解释,但是,如果字符串“末尾”的空字符串是[3,3] - 应用相同的逻辑我们应该能够做到@ 987654340@ 为字符串开头的空字符串。因为考虑第一个和最后一个空子字符串的“正确”方法是没有意义的:[0,0][2,2]
【解决方案2】:

beginIndex == endIndex == count 表示虚拟“起始光标”将放置在字符串中的最后一个字符之后,与“结束光标”位于同一点,因此您将得到一个长度为零的字符串。它似乎与为 (0,0) 返回空字符串一样有效。

【讨论】:

  • beginIndex 在字符串结束后开始。这对我来说似乎无效。 “子字符串”应该是 stringreal 子字符串(空字符串是任何字符串的有效子字符串)。但是在这里我们尝试从字符串“外部”“获取”一个子字符串,这没有任何意义。
  • 此外,如果您的声明有效,在相同的逻辑下,我们应该得到从调用返回的空字符串:substring(length*2, length*2)。如果你是对的,那是一种不一致的行为!
  • @alfasin 除了它明确禁止将索引放在字符串之外。我将这种方法想象为在字符数组之间的插槽中的某个位置放置两个可滑动的边界,并且“在第一个之前”和“在最后一个之后”都是有意义的(尽管并不总是有用的)位置。特别是,这意味着循环可以更灵活地遍历字符串。
  • "after the last" 仅对endIndex 有意义,根据定义,它是一个右标记+1。让左标记去OutOfBounds 是没有意义的
【解决方案3】:

此行为符合:

String str = "str";
System.out.println("str subs: " + str.substring(2,2));

还返回空字符串而不是子字符串。空集是所有集的子集。

参考类 java.lang.String 参数: beginIndex 起始索引,含。

【讨论】:

  • 根据您的逻辑,此行为str.substring(5,5)(引发异常)一致
  • @harsh “最多” - 是的,包括在内 - 不!
  • 但在这种情况下,您无法验证是否在字符串的范围内。如果行为是在 start Index 和 End Index 相等时抛出异常,您会接受吗?我不会,我想我并不孤单。
  • @NitinDandriyal 你错过了重点:我不希望在开始索引和结束索引相等时抛出异常,我希望在开始索引超出范围时抛出异常(当起始索引指向string[length] 时,它不再指向字符串!)。
  • Javadoc 说 beginidex 是包容性的:param beginIndex 起始索引,包容性。
猜你喜欢
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多