Как параметризовать универсальный синглтон в Java - PullRequest
3 голосов
/ 02 сентября 2011

У меня следующая проблема. У меня есть интерфейс:

public interface Worker<T> {
    public void start(Class<? extends T> taskClass);
}

и одноэлементная реализация этого интерфейса:

public final class Listener<T extends Runnable> implements Worker<T> {
    private Listener() {}

    @Override
    public void start(Class<? extends T> taskClass) { ... }

    public static Listener getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static final Listener INSTANCE = new Listener();
    }
}

У меня вопрос, могу ли я получить экземпляр Singleton таким образом:

Worker<Thread> listener = Listener.getInstance();

все еще безопасен для типов? если нет, то как я могу использовать generic в синглтонном экземпляре для безопасного типа?

Ответы [ 2 ]

5 голосов
/ 02 сентября 2011

Все генерики удаляются во время выполнения.

Это означает, что у вас будет только один static class SingletonHolder и только один INSTANCE, независимо от того, что является универсальным.

Чтобы обойти это, вы можете иметь «несколько синглетонов», если это имеет смысл. Вам нужно было бы добавить какую-то регистрацию, поэтому, если вы попытались получить такую, которой еще не было, создайте и сохраните ее перед возвратом. Обратите внимание, что это не скомпилировано, и с ним могут быть некоторые проблемы, но основной подход все тот же.

public final class Listener<T extends Runnable> implements Worker<T> {
    private Listener() {}

    @Override
    public void start(Class<? extends T> taskClass) { ... }

    public static Listener getInstance(Class<T> clazz) {
        return SingletonHolder.INSTANCE.get(clazz);
    }

    private static class SingletonHolder {
        private static final Map<Class<? extends Runnable>,Listener<? extends Runnable> INSTANCE = new ...;
    }
}
1 голос
/ 03 сентября 2011

Просто

public final class Listener implements Worker<Runnable> {
    private Listener() {}

    @Override
    public void start(Class<? extends Runnable> taskClass) { ... }

}

ясно, что экземпляр слушателя может принять любой подкласс Thread.

Worker<Runnable> worker = Listener.getInstance();
worker.start( MyThread.class );

Если вы хотите ограничить тип, чтобы уточнить, что рабочий предназначен для подклассов Thread, вы не можете

Worker<Thread> worker = Listener.getInstance(); // error[1]

но вы можете

Worker<? super Thread> worker = Listener.getInstance();
worker.start( MyThread.class ); // works for all Thread subclasses.
worker.start( SomeRunnable.class ); // error; only for Thread subclasses.

Если мы ненавидим групповые символы (мы это делаем), мы можем утверждать, что явно Worker<Runnable> также является Worker<Thread>; error[1] это просто глупое ограничение.

Одним из преимуществ стирания является то, что мы можем установить этот вид ковариации.

@SuppressWarnings("unchecked")
Worker<Thread> worker = (Worker<Thread>)(Worker) Listener.getInstance();

Мы знаем, что актеры безопасны для всех намерений и целей.

Если это необходимо часто, мы можем переместить ковариацию на getInstance() для удобства использования:

Worker<Thread> worker = Listener.getInstance();

public final class Listener implements Worker<Runnable> 

    static Listener instance = new Listener();

    // it is safe to cast Worker<A> to any Worker<B>
    // as long as B is subtype of A

    @SuppressWarnings("unchecked")

    static public <T extends Runnable> Worker<T> getInstance()

        return (Worker<T>)(Worker)instance;
...