7

Here is some code to illustrate the problem.

String str1 = '';
System.debug(str1.containsOnly('W'));

I would expect false as the result but surprisingly the result was true. I don't understand why this is the case considering an empty string does not contain a 'W' character. Is this expected behavior or a bug?

Edit: After the responses I have received so far, I think I need to rephrase my question and make it a more fundamental one. By so doing, the original question will answered.

One underlying assumption I am making is that the documentation of the method preceded and was the basis of the implementation of the method and not vice versa. If you think this assumption is wrong, please let me know.

Thus putting aside how containsOnly() is currently implemented, how should the documentation of the method be understood? It is more of a question of meaning or semantics than a technical one. This is the given description.

containsOnly(inputString) Returns true if the current String contains characters only from the specified sequence of characters and not any other characters; otherwise, returns false.

Here are the possible interpretations so far:

  1. The current string does not contain any characters outside of the characters in the input string. (@PhilW)

  2. The current string contains any of the characters in the input string (containsAny(inputString)) && interpretation 1. (My understanding).

Please let me know how you understand the documentation or the phrase containsOnly and if you have any other plausible interpretations, I will add them to the list. I think some consensus is needed on this before getting into the actual implementation of the method.

2
  • I know that when we do 'abcd'.split('') it seperate each character so its something related to that. Commented Sep 11, 2020 at 5:23
  • Tushar - That may be true but would you say that the result was what you expected? It seems not what you would expect intuitively at least for me. This is what I feel needs to be answered so as to determine if indeed it is expected behavior or a bug.
    – user73893
    Commented Sep 11, 2020 at 5:38

2 Answers 2

8

Logically, yes, because the empty string does not contain any disallowed characters.

Disallowed characters are any characters not explicitly listed in the "only" specification. So in this case it is any letter other than "W".

If I were implementing this method myself I would do something like:

public Boolean containsOnly(String allowed) {
    // "chars" is the internal array of characters in the string
    for (Integer i = 0; i < chars.size(); i++) {
        if (allowed.indexOfChar(chars[i]) < 0) {
            return false;
        }
    }

    return true;
}

The point is, it is a compact iteration looking for disallowed characters with early exit as required and if you don't find any disallowed characters you simply return true.

I don't think this behaviour can be considered a bug; it is a natural outcome of checking in a compact and efficient way. You can also view an empty string as zero instances of the 'W' character. String.containsOnly will return true for:

  • zero 'W' characters (the empty string)
  • one 'W' character (the string 'W')
  • two 'W' characters (the string 'WW')
  • etc.

If you want to verify that you have a non-empty string and it has just the specified characters in it, adapt your code to do:

if (str1.isNotEmpty() && str1.containsOnly('W')) {
   ...
}
9
  • 2
    Could you explain what is meant by disallowed chars in this case? When I do str1.contains('W') or ''.contains('W'), I get the expected result (i.e. returns false). Also, when I do str1.containsOnly('abc'), I get true (same result as mentioned by @user73893).
    – arut
    Commented Sep 11, 2020 at 6:30
  • 1
    The method might be implemented as you put it but shouldn't the check be for the allowed characters? There is already a containsNone() method for checking disallowed characters.
    – user73893
    Commented Sep 11, 2020 at 6:46
  • +1 Thanks @PhilW for elaborating on your answer with sample snippet. Considering the possible lack of check for empty string in the apex source code for this method, this must be considered as bug (not something that most people wound run into though).
    – arut
    Commented Sep 11, 2020 at 11:46
  • @PhilW Appreciate the additional clarification.
    – arut
    Commented Sep 11, 2020 at 13:22
  • @PhilW Thanks for expounding on your original post but I have to point out a few points on which I have differing views.1. What you wrote was how you would implement the method but based on a premise (i.e. a string does not contain disallowed characters as the only condition that needs to be checked) that itself needs to be established.
    – user73893
    Commented Sep 13, 2020 at 14:35
3

I am inclined to think that this isn't a bug and it is standard behavior, but the documentation is ambiguous at best and makes no indications as to how an empty string should operate. Additional clarification is warranted given that contains and containsOnly return different outcomes in these scenarios:

''.contains('W');     //false
''.containsOnly('W'); //true

Addendum: Some additional examples showing how this function is more set-like than contains which is looking for a substring in a given string:

'abab'.containsOnly('ab');   //true
'ab'.containsOnly('abab');   //true
'abab'.containsOnly('abcd'); //true

'abab'.contains('ab');       //true
'ab'.contains('abab');       //false
'abab'.contains('abc');      //false

The containsOnly operation should be defined in mathematical terms and I have a hunch that it's implemented in that way. There isn't strictly an operation like containsOnly (that I know of - I am not a mathematician), but you can get there two ways with standard set operations. If you take the intersection (∩) of the two sets and the intersection is the same as your first operand, it's true, meaning the two sets overlap entirely:

{A,B} ∩ {A,B} = {A,B}

{} ∩ {A,B} = {}

But the rules of set algebra treat an empty set in a unique way in that all of the following are equivalent:

{} ∩ {A,B} ≡ {} ∩ {} ≡ {}

This would explain why an empty string, which is an empty 'set' of characters, would always return true when the operation is done on it. The intersection of the empty set with any other set (including another empty set) is always the empty set.

That was one way of implementing a containsOnly function using set operations. The other is to use the difference (\). If the difference of two sets is the same as your original, you could say that the first set contains only the second set. Again, the empty set behaves in a similar way as using an intersection.

{A,B} \ {A} = {B}

{A,B} \ {A,B} = {} ≡ {} \ {A,B} = {}{} \ {} = {}

My assumption is that containsOnly is implemented using one of these methods for its algorithm and the documentation for the String class needs to be updated to reflect the current methodology of the method (so that future developers are aware of the nuances). Or the method should be changed to be made consist with contains.

1
  • 1
    Something I would add is that Salesforce documentation is always rubbish at covering edge cases and exception scenarios. One thing I would say is that the Apex team doesn't tend to introduce breaking changes in behaviour even when the documentation is lacking.
    – Phil W
    Commented Sep 11, 2020 at 22:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .