span
span

Reputation: 5624

Class and generic type casting to use in parameterised type

I have two loops that I'm trying to refactor into a single method. The loops notify listeners of different types and they are declared as generic. I thought I could do it with some reflection but I can't seem to get my head around it.

Here is my message class and listener interface:

public class Message {

    private RequestType type; // Enum
    private String data; 
    ...
}

public interface IRequestListener<T> { 
    public void resultUpdated(T result);
}

Here are the loops:

...
// Notification is an Enum
for (IRequestListener<?> listener : listeners) {
    IRequestListener<Notification> notificationListener = 
        (IRequestListener<Notification>) listener;

    notificationListener.resultUpdated(Notification.valueOf(message.getData()));
}

...

for (IRequestListener<?> listener : listeners) {
        IRequestListener<Integer> notificationListener = 
            (IRequestListener<Integer>) listener;

        notificationListener.resultUpdated(Integer.valueOf(message.getData()));
}

Here is my casting attempt at solving which is obviously wrong:

...

notifyMessageListeners(Notification.class, essage)

...

notifyMessageListeners(Integer.class, message);

...

private void notifyMessageListeners(Class clazz, Message message) {
    LinkedList<IRequestListener<?>> listeners = 
         requestListenerMap.get(message.getType());

    for (IRequestListener<?> listener : listeners) {

        // How to solve this cast?
        // Error: unknown class: 'clazz'
        IRequestListener<clazz> notificationListener = 
            (IRequestListener<clazz>) listener;

        // How to solve the valueOf? Need to cast to a class which has valueOf()?
        // Error: cannot resolve method valueOf
        notificationListener.resultUpdated(clazz.valueOf(message.getData()));
     }
}

Here is my generic attempt at solving which is also wrong:

private void notifyMessageListeners(Message message) {

    LinkedList<IRequestListener<?>> listeners = 
        requestListenerMap.get(message.getType());

    for (IRequestListener<?> listener : listeners) {
        // Causes error
        // resultUpdated (<?>)  in IRequestListener cannot be applied to (java.lang.String)
        listener.resultUpdated(message.getData());
    }
}

I've spent quiet some time on SO and in the docs trying to figure this out. There seem to be many posts about generics but I can't find something that I can relate to my specific use case.

Cheers.

EDIT:

Solved with help of accepted answer.

notifyMessageListeners(message, new IValueGetter<T>() {
    public T getValue(String data) {
        return (T) Notification.valueOf(data);
    }
});

private void notifyMessageListeners(Message message, IValueGetter<T> getter) {
    LinkedList<IRequestListener<?>> listeners = 
        requestListenerMap.get(message.getType());
    for (IRequestListener<?> listener : listeners) {
        IRequestListener<T> notificationListener = (IRequestListener<T>) listener;
        notificationListener.resultUpdated(getter.getValue(message.getData()));
    }
}

Upvotes: 1

Views: 267

Answers (1)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726529

Here is how to fix your first attempt:

First, define an interface for calling valueOf (you can avoid this by using reflection, but I think that a "reflectionless" approach is cleaner)

interface ValueGetter<T> {
    T getValue(String data);
}

This generic interface lets you pass a piece of code to notifyMessageListeners so that the method would call you back to extract the desired value from the string.

private void notifyMessageListeners(Message message, ValueGetter<T> getter) {
    LinkedList<IRequestListener<?>> listeners = 
         requestListenerMap.get(message.getType());

    for (IRequestListener<?> listener : listeners) {
        IRequestListener<T> notificationListener = 
            (IRequestListener<T>) listener;
        notificationListener.resultUpdated(getter.getValue(message.getData()));
     }
}

When you call notifyMessageListeners, you need to pass an instance of ValueGetter<T>, like this:

notifyMessageListeners(message, new ValueGetter<T>() {
    public T getValue(String data) {
        return (T) Notification.valueOf(data);
    }
});

notifyMessageListeners(message, new ValueGetter<T>() {
    public T getValue(String data) {
        return (T) Integer.valueOf(data);
    }
});

Upvotes: 2

Related Questions