AgilePro
AgilePro

Reputation: 5598

How to create a type specific Comparator for a flexible type array?

I am stuck in the middle of 'generic-type-qualifier-land' and can't find the right combination of type qualifiers. I have a class that represents a flexible array that can have a number of different types in it. The class holds an ArrayList<Object> as the container for the elements.

class MyArray {
    ArrayList<Object>  list;

    ...

    public void sortAsString(Comparator<String> comp) {
        Collections.sort(list, comp);
    }
}

There are type specific accessors, like getString(i) and getInt(i) etc.

I would like to be able to sort this array in different ways. Sometimes the array is full of strings, and I would like people to be able to pass in a Comparator<String> object. If I declare the class as above, the compiler rejects it because the list is a collection of Object while the Comparator is a Comparator<String>

But I can't type-cast either. This does not work:

    public void sortAsString(Comparator<String> comp) {
        Coparator<Object> foo = (Comparator<Object>) comp;
        Collections.sort(list, foo);
    }

The Collections.sort function second parameter requires Comparator<? super Object>. Is there any way to declare the method and/or to type cast such that someone can pass a Comparator<String> and have it work on this ArrayList<Object>?

I also tried casting the list to ArrayList<String> but that is not allowed either. In the cases where this is called, the user is going to be fairly sure that the array has only strings in it, but of course we don't know that at compile time. I am trying to tell it that even though it is not declared as an array of string, go ahead and treat it like one for sorting. I don't care if it blows up when the member object turns out to not be a string. I just some syntactic sugar to tell the compiler to go ahead and let it call the sorting method.

Upvotes: 1

Views: 122

Answers (3)

Stuart Marks
Stuart Marks

Reputation: 132460

You're on the right track trying to cast the list to ArrayList<String> (or perhaps just List<String>). The compiler flags this as an error, though, because in general it's definitely unsafe.

If it's safe for the caller to make this call, you can use the "cast through raw" technique to avoid the compilation error. You'll still get an unchecked warning, which you can then suppress. Also, it's probably a good idea to check the contents of the list before making the unsafe cast. This allows you to verify that what you're doing is in fact safe, or if it isn't, you can communicate that to the caller in the appropriate way -- possibly by throwing an exception. For example,

    public void sortAsString(Comparator<String> comp) {
        if (! list.stream().allMatch(o -> o instanceof String)) {
            throw new IllegalStateException("MyArray contains non-strings");
        }
        @SuppressWarnings("unchecked")
        ArrayList<String> temp = (ArrayList<String>)(ArrayList)list;
        temp.sort(comp);
    }

Upvotes: 4

Sweeper
Sweeper

Reputation: 272750

Assuming that you are absolutely sure that the ArrayList<Object> contains only Strings, and you are prepared for it to crash if that is not the case...

You can create a Comparator<Object> from the Comparator<String>, that will crash if a non-String gets passed to it:

Collections.sort(list, (x, y) -> comp.compare((String)x, (String)y));

I would suggest checking if all the objects are strings before that, and throw an exception with a meaningful message/do nothing if that is not the case.

Upvotes: 0

tgdavies
tgdavies

Reputation: 11464

Just use a raw type. This compiles

    public void sortAsString(Comparator<String> comp) {
        List<Object> list = null;
        Comparator foo = comp;
        Collections.sort(list, foo);
    }

Upvotes: 0

Related Questions