5

I got a StringIndexOutOfBoundsException for setCharAt(int index, char ch):

StringBuilder word = new StringBuilder(4);

for(int i = 0; i < 4; i++)
    word.setCharAt(i,'-');

StringBuilder(int capacity): Constructs a string builder with no characters in it and an initial capacity specified by the capacity argument.

setCharAt(int index, char ch): The character at the specified index is set to ch.

The only thing I can think of is that no memory was allocated, but then what's the point of StringBuilder(int capacity)?

Steve P.
  • 13,674
  • 6
  • 38
  • 67
  • 1
    Here's [some discussion](http://stackoverflow.com/a/3184277/778118) of the purpose of the _capacity_ argument/constructor. – jahroy Jun 08 '13 at 01:08

3 Answers3

6

Just use StringBuilder.append()

The constructor you're using does nothing to put data in the StringBuilder.

It only specifies a practical size for the StringBuilder (which I believe is for performance).

Until you actually put stuff in the StringBuilder, your code will throw Exceptions.

Note that the documentation you've quoted says this explicitly:

"Constructs a string builder with NO CHARACTERS in it..."

jahroy
  • 20,588
  • 9
  • 52
  • 104
  • That's actually what I ended up doing. I was just wondering what the constructor actually does, if not allocate any memory? I understand that no characters are in, but why does there need to be any? – Steve P. Jun 08 '13 at 00:56
  • 2
    I suggest reading the source code to find out. I believe that the capacity is primarily used internally for performance (as the StringBuilder grows), but I'm not sure. – jahroy Jun 08 '13 at 00:57
4

This seems like a bit of an odd behavior in StringBuilder.

In AbstractStringBuilder (which it inherits), the constructor is defined as:

AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}

However, when using setCharAt(), we check count instead:

public void setCharAt(int index, char ch) {
    if ((index < 0) || (index >= count))
        throw new StringIndexOutOfBoundsException(index);
    value[index] = ch;
}

So, since we didn't set count by using the initial constructor, we have an error thrown in our faces.

count is redefined in append(), replace(), insert(), and setLength(). You could solve your problem by also calling setLength(4); however, as @jahroy pointed out, it is simpler to just use append().

Relevant source codes: StringBuilder, AbstractStringBuilder

Community
  • 1
  • 1
Eric
  • 63,873
  • 22
  • 120
  • 135
  • I've never looked at the source, but I assumed it looked something like that, which is why I was so confused when I got the exception. – Steve P. Jun 08 '13 at 01:01
  • @SteveP. Yeah... Technically, it *should* let you. But instead of checking `value.length`, it checks `count` which wasn't updated. It's hard to actually say if this is by design or not. – Eric Jun 08 '13 at 01:02
  • @Eric I would like to down vote you comment. See this: http://stackoverflow.com/a/16994970/122207 – Tim Bender Jun 08 '13 at 01:03
  • @TimBender I won't stop you from any kind of voting. The array is still encapsulated, regardless of the functions we call on the `StringBuilder`. If we were manipulating the array directly, I'd say you had a point. But we're not. – Eric Jun 08 '13 at 01:07
  • There is no downvoting comments ;) The answer is good. It walks through the code somewhat and reaffirms why an exception is being thrown. I just think your perspective on whether or not `capacity` is more valid than `count` is wrong. – Tim Bender Jun 08 '13 at 01:10
  • 2
    @TimBender (I know. :P) I would say, though, that if I *manually* set a capacity, it should allow me to manipulate that data how I see fit. In the case of the default constructor, it allocates 16 blocks in array, but I should not be able to access them. In terms of the explicit setting of the capacity, though, I think that's different. (Being forced to call `setLength()` isn't so bad though, I guess.) – Eric Jun 08 '13 at 01:12
  • Yes! There is a question of intent. If one sets a high capacity, one intends to have enough room to avoid a potentially expensive grow operation. If one sets a length, then it is intended to interact the data up to the specified length. – Tim Bender Jun 08 '13 at 01:28
  • 2
    Maybe they should just change the name of the argument from _capacity_ to _growFactor_ (or something more appropriate)... It's funny how important names can be. – jahroy Jun 08 '13 at 01:35
  • 2
    @jahroy I agree completely. – Eric Jun 08 '13 at 01:36
  • @jahroy Curiously enough, a variant of `Vector`'s constructor actually includes a separate `growFactor` arg. I think the overloaded constructor was abandoned for being unnecessarily fine grained. – Tim Bender Jun 08 '13 at 01:58
4

The only thing I can think of is that no memory was allocated, but then what's the point of StringBuilder(int capacity)?

The point is to provide an up front opportunity to allocate a sufficiently large chunk of memory to handle all the data that will be inserted. When the capacity is exceeded, internally a new chunk of memory will have to be allocated and all the values from the old memory copied over. As you can imagine, this could be a performance burden if it occurs a multitude of times.

The behavior is not really odd or unexpected, in fact it is quite consistent with the practice of encapsulation.

What would ArrayList do? An ArrayList is backed by an array, and using a sufficient initial capacity is more performant, however doing so still does not allow arbitrary access to indices of the array.

I think the odd behavior is thinking of a StringBuilder as a representation of a char[]. The representation is properly encapsulated at it is, don't poke at it in curious ways.

Tim Bender
  • 19,152
  • 2
  • 44
  • 56