Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass an object with an unknown type to a Class with an unknown type

I am currently working on a homework assignment for a Java programming course I am taking. I am not asking for an exact answer, but for some guidance.

The problem I'm working on is this:

I have a filter class that implements a Filter interface. This interface has just one method - matches(T element)

I have configured my filter method to check an Integer that is being passed in for prime-ness.

There is also a decorator class that decorates a collection class to only display objects that pass the filter.

I'm having problems getting the contains(Object o) method to work correctly.

Basically the contains(Obj o) method in the FilteredCollection class should first check to see if the object passes the filter, and then if it does, call the undecorated contains() method on that object.

Assuming I want to be able to use this FilteredCollection class with many different types of filters, How can I determine what type of object is being passed in, and then be able to pass that object to the current Filter that's being implemented.

Here is my PrimeNumberFilter Class:

public class PrimeNumberFilter implements Filter<Integer> {

public boolean matches(Integer e) {
    int n = e.intValue();
    if (n != 2 && n % 2 == 0) {
        return false;
    }

    for (int i = 3; i * i <= n; i += 2) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

}

Then here is my shortened FilteredCollection Class:

class FilteredCollection<T> implements Collection<T> {

Collection<T> fc;
Filter<T> currentFilter;

private FilteredCollection(Collection<T> coll, Filter<T> filter) {

    this.fc = coll;
    this.currentFilter = filter;

}

public static <T> FilteredCollection<T> decorate(Collection<T> coll,
    Filter<T> filter) {

    return new FilteredCollection<T>(coll, filter);
}

public boolean contains(Object o) {

    //What do I do here?
    return fc.contains(o);
}

The object being passed in to the contains method has to pass the filter, in this case a PrimeNumberFilter.

The error I'm getting is it keeps wanting to cast the object to type T, and I know that this will never work because of erasure.

I've done a ton of research, and I've boiled it down to needing to use reflection.

The only hint my instructor will give me is that object only has a few methods I can use, and I should use one of those.

Thanks for your help!

EDIT: One of the requirements of the project is to NOT cast an object to T in any method. So while these answers are great, I am not able to use any of them.

like image 295
Evidencex Avatar asked Feb 16 '23 13:02

Evidencex


2 Answers

The method to use is Object.equals(Object). You can iterate the collection fc and check if it contains an element wich equals(o). If so, continue on with said element (which is of type T).

for(T e : fc) {
    if(o.equals(e)) {
        // carry on with e
    }
}

You might also want to cover o == null.

like image 161
Ben Schulz Avatar answered May 11 '23 03:05

Ben Schulz


There is nothing wrong with your code.

The problem is due to the java.util.Collection.contains(Object o) interface method not being generically typed. This is outside of your control.

Option 1: Simple approach

In your implementation of that method you can cast:

public boolean contains(Object o) {
    return o != null && currentFilter.matches((T)o) && fc.contains(o);
}

Option 2: Add a getParameterType() method to the Filter interface

This method would return the generic type of the filter as implemented in the various subclasses.

interface Filter<T> {
    boolean matches(T parameter);
    Class<T> getParameterType();
}

Then...

public boolean contains(Object o) {
    return o != null && currentFilter.getParameterType().isAssignableFrom(o.getClass()) && currentFilter.matches((T)o) && fc.contains(o);
}

Option 3: Determine generic type via reflection

Technically the generic type of your filter will not actually be erased at runtime. Type erasure does not apply here because PrimeNumberFilter is an actual class which implements a generically typed interface.

@SuppressWarnings("unchecked")
public boolean contains(Object o) {
    Class<?> genericTypeOfFilter = getGenericTypeOfFilter(currentFilter);
    return o != null && genericTypeOfFilter.isAssignableFrom(o.getClass()) && currentFilter.matches((T)o) && fc.contains(o);
}

static <T> Class<T> getGenericTypeOfFilter(Filter<T> filter) {
    try {
        @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
        Class<T> type = (Class<T>) ((ParameterizedType)filter.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        return type;
    }
   catch (Exception e) {
        throw new IllegalStateException("Unexpectedly failed to read generic type of filter: " + filter, e);
    }
}

If it was my code I'd go with Option 2 in this case, it is more robust than relying on reflection.

like image 26
npgall Avatar answered May 11 '23 05:05

npgall