4

I am currently at a bit of trouble finding a way to write this generic Set method.

Due to the fact that Apex does not have a Set operation called containsAny, I decided to write my own version within my umbrella/catch all Utility class for future projects down the line. Unfortunately, I came across some issues developing it.

The code below:

public static Boolean SetContainsAny(Set<Object> currentSet, Set<Object> comparingSet)
{
    for(Object setElement : comparingSet)
        if(currentSet.contains(setElement))
            return true;
    return false;
} 

With this example (Example A):

Set<String> stringSet = new Set<String>{ '1', '6' };
Set<Integer> integerSet = new Set<Integer>{1, 2, 3};
Boolean containsAny = GeneralUtilities.SetContainsAny(stringSet, integerSet);

Please note: I used two different types of Sets to show-case it should take any set. I planned on checking if they both matched the same instance later.

Gave me the following error:

Method does not exist or incorrect signature: GeneralUtilities.SetContainsAny(Set, Set)

So I figured I could modify the example to get around it:

Set<String> stringSet = new Set<String>{ '1', '6' };
Set<Integer> integerSet = new Set<Integer>{1, 2, 3};
Boolean containsAny = GeneralUtilities.SetContainsAny((Set<Object>)stringSet, (Set<Object>)integerSet);

This however, gave me the following error:

Incompatible types since an instance of Set is never an instance of Set

So I decided, I could get around it by changing the signature:

public static Boolean SetContainsAny(Object currentSet, Object comparingSet)
{
    for(Object setElement : (Set<Object>)comparingSet)
        if(((Set<Object>)currentSet).contains(setElement))
            return true;
    return false;
}

This saved to the server, but when I tried running my first example (Example A), I got the following error:

System.TypeException: Invalid conversion from runtime type Set to Set

Now aside from doing the obvious to my example:

Set<Object> stringSet = new Set<String>{ '1', '6' };
Set<Object> integerSet = new Set<Integer>{1, 2, 3};

Is there anyway to make the Utility method perform correctly?

2 Answers 2

3

Yeah Set will not generically cast to Object, though List will. If you want this to work with specific types of sets, you will need to add overloads specific to them. This would be a real pain if you want to be able to cross types as in your example, although I don't feel like that makes a whole lot of sense. A Set<Integer> will never contain a String.

You may just want to write macros for converting sets of primitive types into generics:

public static Set<Object> getGeneric(Set<String> strings)
{
    return new Set<Object> ((List<Object>)new List<String> ( strings ));
}

public static Boolean setContainsAny(Set<String> currentSet, Set<String> comparingSet)
{
    return setContainsAny(getGeneric(currentSet), getGeneric(comparingSet));
}

It's a little ugly but it works.

In more detail:

// Input value
Set<String> strings = { 'foo', 'bar' };

// Construct a typed List from the typed Set
List<String> stringList = new List<String> ( strings );

// Cast the typed List to generic List<Object>
List<Object> objectList = (List<Object>)stringList;

// Construct a Set<Object> from the resulting List<Object>
Set<Object> objects = new Set<Object> ( objectList );
5
  • The example was there to show that it will take any type of Set. I was going to add type checking after a successful operation. Jun 25, 2015 at 20:39
  • Right well, I probably would not bother with typed overloads where the types do not match.
    – Adrian Larson
    Jun 25, 2015 at 20:40
  • Yeah, that would be a kind of odd to add. That could be an expansion, but for now, I would just throw an Exception if the types did not match. Jun 25, 2015 at 20:42
  • There shouldn't be a need. Make your signature that accepts Set<Object> as private and then if someone tries to use a signature you haven't defined, it won't compile.
    – Adrian Larson
    Jun 25, 2015 at 20:44
  • Out of curiosity, do you think if both sets are null, it should return false? Jun 27, 2015 at 19:23
0

Using Adrian Larson's answer, I was able to make the following modifications to the utility methods to compile and work correctly.

private static Boolean SetContainsAny(Set<Object> currentSet, Set<Object> comparingSet)
{
    for(Object setElement : comparingSet)
        if(currentSet.contains(setElement))
            return true;
    return false;
}

public static Boolean SetContainsAny(Set<String> currentSet, Set<String> comparingSet)
{
    return SetContainsAny(new Set<Object>((List<Object>)new List<String>(currentSet)), 
        new Set<Object>((List<Object>)new List<String>(comparingSet)));
}

//A somewhat safer example
public static Boolean SetContainsAny(Set<String> currentSet, Set<String> comparingSet)
{
    return (currentSet == null || comparingSet == null) ? false :
        SetContainsAny(new Set<Object>((List<Object>)new List<String>(currentSet)), 
        new Set<Object>((List<Object>)new List<String>(comparingSet)));
}

It looks pretty hacky, but it works the way I want it to.

Here are some examples:

Set<String> stringSet = new Set<String>{ '1', '6' };
Set<String> stringSetTwo = new Set<String>{ '5', '7' };
Boolean containsAny = GeneralUtilities.SetContainsAny(stringSet, stringSetTwo);
//containsAny equals 'false'

stringSet = new Set<String>{ '1', '7' };
containsAny = GeneralUtilities.SetContainsAny(stringSet, stringSetTwo);
//containsAny equals 'true'
2
  • I do think adding the getGeneric method helps a lot with readability (and will help you avoid hard to track down errors later).
    – Adrian Larson
    Jun 25, 2015 at 21:58
  • Yeah, only reason I don't prefer it is that if I wanted to add this functionality for other Sets, I'd have to add two methods each time instead of one. There is the case of adding prechecks in the concrete implementations, which I should add now that I'm thinking about it. Jun 25, 2015 at 22:07

You must log in to answer this question.

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