2016-06-24 1 views
1

(Извините за титул, я не мог придумать ничего лучшего)Java генерик - Тип неважного

Я работаю над большим проектом, заполненных ссылками на сырье типов, и я пытаюсь избавиться из них. Проблема в том, что в большинстве случаев мы действительно не знаем фактического типа. Пример, вероятно, лучший способ объяснить.

Рассмотрим:

public interface Event<S, D> { 
    S getSource(); 
    D getData(); 
} 

public interface Listener<E extends Event<?, ?>> { 
    void onEvent(E event); 
} 

public interface Listenable<E extends Event<?, ?>> { 
    void addListener(Listener<E> listener); 
    void removeListener(Listener<E> listener); 
} 

public class DataAccess implements Listenable { 

    ... 

    public void addListener(final Listener listener) { 
    eventHandler.addListener(listener); 
    } 

    public void removeListener(final Listener listener) { 
    eventHandler.removeListener(listener); 
    } 
} 

DataAccess на самом деле не волнует, какой тип слушателя мы проходим, как он может получать события с различными типами. Я подозреваю, что дженерики используются здесь не так, и, возможно, Listener и Listenable не должны быть общими?

Я думал об изменении Слушатель к

public interface Listener { 
    void onEvent(Class<? extends Event<?, ?>> event); 
}  

Но тогда OnEvent не будет иметь доступ к методам мероприятия.

Я что-то упустил?

+0

Не можете ли вы просто сменить подпись 'addListener' и' removeListener' на 'public void addListener (final Listener listener)'? – marstran

+1

'DataAccess' * должен заботиться *, поскольку он обещает доставить определенный тип' X' в 'Listener ' и ничего больше. Все остальное должно считаться сломанным (и будет тормозить во время выполнения). Или, ну, вы удаляете общий тип параметра из «Listener» и «Listenable». Но почему вы меняете тип параметра от 'Event' до' Class' выше меня. – Holger

+0

Если вам действительно не нужны параметры Event на этом уровне, то почему бы не создать суперкласс без этих генериков fe: «Событие» <- «DataEvent », а затем вы не загрязняете модель прослушивателя с параметрами, которые не используются. –

ответ

1

Если бы я был вами, я бы остался самым общим, насколько это возможно, в интерфейсах, и я бы позволил реализациям определить явные типы, иначе, если в ваших интерфейсах вы будете использовать только подстановочные знаки, которые вы больше не сможете использовать в силе генерических средств в ваших реализациях.

Так что я бы что-то вроде этого

public interface Event<S, D> { 
    S getSource(); 
    D getData(); 
} 

public interface Listener<E extends Event<S, D>, S, D> { 
    void onEvent(E event); 
} 

public interface Listenable<E extends Event<S, D>, S, D> { 
    void addListener(Listener<E, S, D> listener); 
    void removeListener(Listener<E, S, D> listener); 
} 

Благодаря этому гораздо более гибкий подход, если у вас есть реализации, которые принимают определенные события, которые вы затем можете детализировать тип разрешенных событий в противном случае вы можете использовать символы подстановки поддерживать любые события.

+0

Но тогда реализации Listener должны были указывать типы. И это еще одна проблема, потому что слушатель может работать с различными событиями. Вот почему я говорю, что использование дженериков здесь неверно. – algiogia

+0

, если события не имеют ничего общего, тогда вы будете использовать wild card в реализациях, но в случае, если события имеют один и тот же тип (совместно использующий один и тот же inteferface), вы можете использовать более подходящий общий тип. Дело здесь в том, что если вы используете wild card везде, просто не используйте generics, они будут бесполезны. Как правило, другой ответ является хорошим доказательством –

0

Почему нет:

public class DataAccess implements Listenable<Event<?,?>> 
    { 


      private Listenable<Event<?, ?>> eventHandler; 

      public void addListener(final Listener<Event<?,?>> listener) 
      { 
      eventHandler.addListener(listener); 
      } 

      public void removeListener(final Listener<Event<?,?>> listener) 
      { 
      eventHandler.removeListener(listener); 
      } 

    } 

E extends Event<?,?> в конце Event<?,?>, и вы должны специально попросить типа в onEvent, брось к этому типу, чтобы вызвать специфический метод подтипа.

Вот пример кода, который передает идею:

import java.util.ArrayList; 
import java.util.List; 


public class TestGeneric 
{ 

    private Listenable<Event<?, ?>> eventHandler = new MyListenable(); 


    public void run() 
    { 
     MyEvent<Object, Object> myEvent = new MyEvent<Object, Object>(); 

     MyExtendedEvent<Object, Object> myExtendedEvent = new MyExtendedEvent<Object, Object>(); 

     eventHandler.addListener(new MyListener()); 


     ((MyListenable)eventHandler).fire(myEvent); 
     ((MyListenable)eventHandler).fire(myExtendedEvent); 
    } 

    class MyListenable implements Listenable<Event<?,?>> 
    { 

     private List<Listener<Event<?, ?>>> listeners = new ArrayList<Listener<Event<?, ?>>>(); 

     public void addListener(Listener<Event<?, ?>> listener) 
     { 
      listeners.add(listener); 

     } 

     public void removeListener(Listener<Event<?, ?>> listener) 
     { 
      listeners.remove(listener); 

     } 

     public void fire(Event<?,?> event) 
     { 
      for (Listener<Event<?, ?>> listener : listeners) 
      { 
       listener.onEvent(event); 
      } 
     } 
    } 


    class MyEvent<S, D> implements Event<S, D> 
    { 

     public S getSource() { 
      // TODO Auto-generated method stub 
      return null; 
     } 

     public D getData() { 
      // TODO Auto-generated method stub 
      return null; 
     } 

     public void printStatement() 
     { 
      System.out.println("Hi there from MyEvent!"); 

     } 

    } 

    class MyExtendedEvent<S, D> implements ExtendedEvent<S, D> 
    { 

     public S getSource() { 
      // TODO Auto-generated method stub 
      return null; 
     } 

     public D getData() { 
      // TODO Auto-generated method stub 
      return null; 
     } 

     public void printStatement() { 
      // TODO Auto-generated method stub 

     } 

     public void printStatementFromExtendedEvent() 
     { 
      System.out.println("Hi there from extended event!"); 

     } 

    } 

    public interface ExtendedEvent<S, D> extends Event<S, D> 
    { 
     void printStatementFromExtendedEvent(); 
    } 

    class MyListener implements Listener<Event<?,?>> 
    { 

     public void onEvent(Event<?, ?> event) 
     { 
      if (event instanceof ExtendedEvent) 
      { 
       ExtendedEvent<?, ?> extEvent = (ExtendedEvent<?, ?>)event; 
       extEvent.printStatementFromExtendedEvent(); 
      } 
      else 
      { 
       event.printStatement(); 
      } 

     } 



    } 




} 
1

Если Listener может работать с любым типом события, то правильно определить его как:

public interface Listener { 
    void onEvent(Event<?, ?> event); 
} 

Вы можете позвонить getSource() и getData() в версиях onEvent(), но они вернут значения типа Object, и вам, возможно, придется сделать несколько кастингов, чтобы сделать что-нибудь полезное в ваших слушателях.

Тот факт, что слушатели могут обрабатывать любые события, делает весь S & D универсальным видом бессмысленным. @ Ответ NicolasFilotto показывает, как должен выглядеть звуковой дизайн, но, возможно, у вас нет выбора.

+0

Я пришел к тому же решению (если так мы можем его назвать). У вас есть точка моего вопроса: похоже, что они ввели дженерики только «потому что». Он не используется, и есть множество отливок. – algiogia

+0

@algiogia хорошо, извините прочитайте это. Что вы можете сделать, чтобы смягчить это, так это собрать всю свою общую модель с полнофункциональными и специфичными для типа слушателями, а затем поместить ваши текущие реализации в слабо типизированные подклассы (с использованием подстановочных символов «Object» и «?»). Таким образом, у вас есть хорошая модель для будущих разработок, и вам не нужно менять слишком много вещей. –

1

Учитывая нынешнее заявление, я могу написать следующее без компилятора возражающего:

void example(DataAccess eventSource) { 
    abstract class MyEvent implements Event<BigInteger,String>{} 
    eventSource.addListener(new Listener<MyEvent>() { 
     public void onEvent(MyEvent event) { 
      BigInteger bi=event.getSource(); 
      String str=event.getData(); 
     } 
    }); 
} 

Это не только трудно себе представить, что источник события надлежащим образом обеспечивает ожидаемый тип события, он даже не хватает информацию, чтобы решить никогда не вызывать этого слушателя, так как единственным способом узнать ожидаемый тип является Reflection, который имеет несколько ограничений, когда речь идет о генерических типах.

Вывод состоит в том, что вы не можете обеспечить такую ​​гибкость. Как правило, источник событий должен знать, какие события он может выполнить. В этом случае, он должен объявить, что нравится:

public class DataAccessEvent implements Event<DataAccess,SpecificDataType> { 
    public DataAccess getSource() { 
    // ... 
    } 
    public SpecificDataType getData() { 
    // ... 
    } 
} 
public class DataAccess implements Listenable<DataAccessEvent> { 

    public void addListener(Listener<DataAccessEvent> listener) { 
    eventHandler.addListener(listener); 
    } 

    public void removeListener(Listener<DataAccessEvent> listener) { 
    eventHandler.removeListener(listener); 
    } 
} 

Обратите внимание, что правильная подпись метода теперь потребовал в связи с объявлением класса. Нет смысла регистрировать слушателей для типов событий, которые источник никогда не будет выполнять.


То же самое возможно без экспорта фактического типа события, например.

public class DataAccess implements Listenable<Event<DataAccess,SpecificDataType>> { 

    public void addListener(Listener<Event<DataAccess,SpecificDataType>> listener) { 
    eventHandler.addListener(listener); 
    } 

    public void removeListener(Listener<Event<DataAccess,SpecificDataType>> listener) { 
    eventHandler.removeListener(listener); 
    } 
} 

Для поддержки слушателей, которые могут обрабатывать более широкие типы, вам придется объявить интерфейс, как

public interface Listenable<E extends Event<?, ?>> { 
    void addListener(Listener<? super E> listener); 
    void removeListener(Listener<? super E> listener); 
} 

Таким образом, эти методы также принимают слушателей, которые могут обрабатывать супер типы типа, что это источник может доставить. Сюда входят слушатели, способные потреблять Event<?,?>. Разумеется, классы-исполнители должны вставлять ? super … в свои подписи.

+0

Проблема в том, что DataAccess запускает другой подкласс Event. Я полагаю, что я могу заставить их расширять один и тот же интерфейс, расширяющий Event ... – algiogia

+0

Прямое решение - объявить общий базовый класс всех возможных событий. Следует избегать добавления слушателя, который может обрабатывать только один конкретный подкласс, но получает другой подкласс. Поэтому требовать, чтобы все слушатели могли обрабатывать общий базовый класс, является самым простым выбором. В противном случае вам понадобится другой параметр для поддержки фильтрации, например. ' void addListener (класс , который, слушатель прослушиватель);', но это усложняет реализацию. – Holger

Смежные вопросы