11

This applies to to both Java SE & Android, as the contracts are identical.

CharSequence does not define a contains(CharSequence) method. I can't seem to find a reason why, and including it would be quite useful, preventing the need to call CharSequence#toString() to check for a sequence of characters.

For example, in Android, users are forced to call Editable#toString() to see if it contains a sequence of characters, albeit Editable implements CharSequence, which can be avoided if CharSequence defined contains(CharSequence).

What's the idea behind this design choice? Is it a potential oversight, or is there a design reason for this?

10

The point of CharSequence is to provide a read-only view to a character sequence, and that's it. This interface does not provide any string manipulation or searching methods. Those are out of scope.

The Interface Segregation Principle suggests that clients of a type should not depend on methods they don't use. Therefore, an interface should declare only the minimal useful set. If a different use case needs different methods, there should be a different interface.

A client that only needs a character source likely does not need search methods.

It is of course possible to overdo this Principle and end up with a thousand little interfaces. That's not good either. So the CharSequence interface doesn't just contain the minimal charAt() and length() methods, but also the deeply related convenience method subSequence(). (A CharSequence can likely provide a view onto a subsequence without a string copy, which is why this should be an instance method). Specifying toString() is OK because that method would be inherited anyway from Object. The methods chars() and codePoints() adapt a CharSequence to a Stream interface. Because these are default methods, they do not impose additional requirements for classes implementing CharSequence.

The CharSequence type is useful when a method needs a generic character source without specifying a particular implementation (e.g. String vs. CharBuffer vs. StringBuilder). The String#join() and String#contains() methods are good examples of using CharSequences.

It is not necessary for CharSequence to provide a contains() method because it can be implemented externally. While Java does not have the convenience of C#'s extension methods, a static method is essentially the same thing. So instead of boolean Editable#contains(CharSequence needle) you would have a static boolean contains(CharSequence haystack, CharSequence needle). String search algorithms are a well-studied computer science topic. Different algorithms with different tradeoffs are readily available.

Further reading:

  • 2
    You mention "This interface does not provide any string manipulation or searching methods. Those are out of scope.", but contains is not a mutation method, and there does exist searching methods (charAt), so how does this apply?. Also, "Because these are default methods, they do not impose additional requirements for classes implementing CharSequence." - Couldn't contains be implemented as a default via the impl return to String().contains(...), removing the requirement for classes to implement? – Vince Emigh Oct 22 '17 at 18:09
  • 1
    @VinceEmigh Yes, contains() could be a default method. If it existed it should not be implemented in terms of String#contains but the other way round: String should use the CharSequence implementation. The charAt() is different. It does not implement a search algorithm, it is a crucial part of the CharSequence: without it, the contents couldn't be copied into a different type like String. Streams are a crucial part of Java8, and adding these default methods is in line with additions to other interfaces like Collection. – amon Oct 22 '17 at 18:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.