The question about the "Why" may be considered as philosophical or academic, and provoke answers along the line of "That's just the way it is".
However, from a more general, abstract point of view, it is a valid question, when considering the alternatives: One could imagine two forms of this method:
String substringByIndices(int startIndex, int endIndex);
and
String substringByLength(int startIndex, int length);
In both cases, there is another dimension in the design space, namely whether the indices are inclusive or exclusive.
First of all, note that all versions are basically equivalent. At the call site, it's usually trivial to change the call according to the actual semantics of the method:
int startIndex = ...;
int endIndex = ...;
String s = string.substringByLength(startIndex, endIndex-startIndex);
or
int startIndex = ...;
int length = ...;
String s = string.substringByIndices(startIndex, startIndex+length);
The choice of whether the indices are inclusive or exclusive will add some potential for having to fiddle around with a +1
or -1
here and there, but this is not important here.
The second example already shows why the choice to use an inclusive start index and an exclusive end index might be a good idea: It's easy to slice out a substring of a certain length, without having to think about any +1
or -1
:
int startIndex = 12;
int length = 34;
String s = string.substringByIndices(startIndex, startIndex+length);
// One would expect this to yield "true". If the end index
// was inclusive, this would not be the case...
System.out.println(s.length() == length);
This somehow may also be considered to be in line with things like for
-loops, where you usually have
for (int i=startIndex; i<endIndex; i++) { ... }
The start is inclusive, and the end is exclusive. This choice thus matches nicely into the usual, idiomatic language patterns.
However, regardless of which choice is made, and regardless how it is justified: It is important to be
consistent
throughout the whole API.
For example, the List
interface contains a method subList(int, int):
List<E> subList(int fromIndex, int toIndex)
Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive.
with is in line with this convention. If you had to mix APIs where the end index is sometimes inclusive and sometimes exclusive, this would be error-prone.