Подтвердить что ты не робот

Инкапсулировать специфические для класса и методы общие типы в одном типе

Скажем, мне нужен класс, ограниченный общим типом Comparable:

class A<T extends Comparable<T>> {

    // this is just an example of usage of T type
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }
}

Класс имеет метод с собственными генериками в сигнатуре:

<V> Future<V> submit(Callable<V> task) {
    return someExecutorService.submit(task);
}

Теперь, есть ли возможность ограничить ввод метода submit только тем, который принимает Callables, который также реализует T? Я сначала попробовал это:

<V, W extends T & Callable<V>> Future<V> submit(W task) {
    if(compareSomething(task) != 0)
        throw new RuntimeException("");
    return someExecutorService.submit(task);
}

но выяснил, что это невозможно (по причинам, описанным здесь). Есть ли какая-то элегантная возможность обойти его?


РЕДАКТИРОВАТЬ: Одна уродливая возможность, о которой я могу думать, состоит в том, чтобы разбить инкапсуляцию на два разных типа и передать пару объектов в submit:

class A<T extends Comparable<T>> {

    // this is just an example of usage of T type
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }
    <V> Future<V> submit(Callable<V> task, T comparable) {
        if(compareSomething(comparable) != 0)
            throw new RuntimeException("");
        return someExecutorService.submit(task);
    }
}

Основной недостаток заключается в том, что сигнатура метода становится более сложной, также мне потребуется некоторое взаимно однозначное отображение T to Callable для некоторого последнего кода. Может быть, можно предложить образец, который решает его соответствующим образом?..


ИЗМЕНИТЬ, возьмите два: позвольте мне вкратце объяснить, чего я пытаюсь достичь. Я работаю над пользовательской реализацией пула потоков, которая может выполнять какое-то специальное планирование задач. Для этого служба принимает только один специальный вид задач Callable. Те Callable должны реализовать пользовательский интерфейс, похожий на Comparable. Сравнивая пары задач с использованием методов в этом интерфейсе, служба будет:

  • Заблокировать входящую задачу, если она заблокирована любой выполняющейся задачей.
  • Вызов ожидающих задач при завершении выполненной задачи Future.
  • Определите порядок выполнения ожидающих задач, сравнив их.

Логика блокировки/сравнения должна предоставляться самими задачами. Таким образом, класс пула потоков должен определять только тот тип Comparable, который принимает объект пула, и его вообще не волнует, что это за тип Callable и каков его тип возврата.


EDIT, возьмите три: на основе Эрик Робертсон ответ, теперь можно предотвратить подачу вонючего задачи:

public static void test(String[] args) {
    A<Valid> scheduler = new A<>();
    scheduler.betterSubmit(new Valid()); // applies to method signature
    scheduler.betterSubmit(new Forbidden()); // rejected on compile time
    scheduler.betterSubmit(new ConformWithValid()); // still appliable because all required interfaces implementations recognised
}

// just a bunch of test classes

private static class Valid implements Comparable<Valid>, Callable<Void> {

    @Override
    public int compareTo(Valid o) {
        return 0;
    }

    @Override
    public Void call() throws Exception {
        return null;
    }
}

private static class Forbidden implements Comparable<Forbidden>, Callable<Void> {

    @Override
    public int compareTo(Forbidden o) {
        return -1;
    }

    @Override
    public Void call() throws Exception {
        return null;
    }
}

private static class ConformWithValid implements Comparable<Valid>, Callable<Boolean> {

    @Override
    public int compareTo(Valid o) {
        return 1;
    }

    @Override
    public Boolean call() throws Exception {
        return Boolean.FALSE;
    }
}

Приятно и легко! Надеюсь, когда-нибудь это поможет кому-то в той же ситуации, что и моя.: -)

4b9b3361

Ответ 1

Используйте Comparable<T>, чтобы ограничить параметр, а не только T.

Если единственным критерием является то, что объект Callable и Comparable, то вы можете просто поместить эти два интерфейса в аргумент. Это даже резервирует возможность добавления именованного класса в параметр, если это требуется для заполнения другого требования. Вам нужно будет подавить одно предупреждение, но это безопасное подавление, потому что вы знаете, что T extends Comparable<T>.

public class A<T extends Comparable<T>> {

    // this is just an example of usage of T type
    List<T> comparables;

    ExecutorService someExecutorService = null;

    int compareSomething(T smth) {
        return this.comparables.get(0).compareTo(smth);
    }

    <V> Future<V> submit(Callable<V> task) {
        return this.someExecutorService.submit(task);
    }

    @SuppressWarnings("unchecked")
    <V, W extends Callable<V> & Comparable<T>> Future<V> betterSubmit(W task) {
        if(this.compareSomething((T) task) != 0)
            throw new RuntimeException("");
        return this.someExecutorService.submit(task);
    }
}

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

Ответ 2

Я думаю, что Erick Robertson answer ближе всего подходит к прямому решению. Я хотел бы предложить менее прямой (который избегает любых непроверенных бросков). Вы писали:

эта служба принимает только один специальный вид задач Callable. Те Callable должны реализовать пользовательский интерфейс, похожий на Comparable.

Если мы действительно объявляем то, что вы описали как тип, мы предлагаем этот интерфейс пересечения:

interface CustomCallable<V> extends Callable<V>, Comparable<CustomCallable<?>> { }

Обратите внимание, как он реализует Comparable<CustomCallable<?>>, а не Comparable<CustomCallable<V>>. Это связано с тем, что я предполагаю, что CustomCallable разных типов вывода все равно будет сопоставим друг с другом пулом. В противном случае не было бы смысла иметь V в качестве параметра типа метода.

Предполагая, что ваш пул может принять любой тип этого интерфейса пересечения, мы можем просто удалить T полностью, а ограничение языка будет спорным:

class A {

    List<CustomCallable<?>> customCallables;

    int compareSomething(CustomCallable<?> smth) {
        return customCallables.get(0).compareTo(smth);
    }

    <V> Future<V> submit(CustomCallable<V> task) {
        if (compareSomething(task) != 0) {
            throw new RuntimeException("");
        }
        return someExecutorService.submit(task);
    }
}

Если вы настаиваете на параметризации A на определенный тип пользовательских вызовов, это становится более сложным. Единственный способ заставить его работать - добавить параметр "self type" к интерфейсу пересечения:

interface CustomCallable<V, SELF> extends Callable<V>, Comparable<CustomCallable<?, ?>> { }

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

С добавленным типом self теперь можно A объявить T, что submit затем настаивает на использовании для собственного типа пользовательских вызываемых вызовов:

class A<T extends CustomCallable<?, ?>> {

    List<CustomCallable<?, T>> customCallables;

    int compareSomething(CustomCallable<?, T> smth) {
        return customCallables.get(0).compareTo(smth);
    }

    <V> Future<V> submit(CustomCallable<V, T> task) {
        if (compareSomething(task) != 0) {
            throw new RuntimeException("");
        }
        return someExecutorService.submit(task);
    }
}

Вот пример реализации CustomCallable, который разрешает тип self:

class MyCustomCallable<V> implements CustomCallable<V, MyCustomCallable<?>> {
    ...
}

Аналогично предыдущему, обратите внимание на то, что тип self "расслаблен" до MyCustomCallable<?>, а не MyCustomCallable<V>, поскольку различные типы вывода взаимозаменяемы по дизайну.

И вот пример использования:

A<MyCustomCallable<?>> pool = new A<>();

MyCustomCallable<String> strCallable = ...;
MyCustomCallable<Integer> intCallable = ...;

Future<String> strFuture = pool.submit(strCallable);
Future<Integer> intFuture = pool.submit(intCallable);

Ответ 3

Я не вижу, как вы можете объединить как свой общий параметр класса T, так и V в аргументе метода Callable<V>, по причинам, указанным в ответах на связанный пост. Однако, возможно, это был бы вариант - я не могу оценить, насколько противоречивым было бы это - изменить действие отправки задачи на метод, вызываемый самой задачей? Что-то вроде линий...

class A<T extends Comparable<T> & Callable<?>> {

    public static abstract class Submittable<T extends Submittable<T,V>,V>
      implements Comparable<T>, Callable<V> {
        // ugly, but unavoidable
        @SuppressWarnings("unchecked")
        public Future<V> submitTo(A<? super T> scheduler) {
          return (Future<V>) scheduler.submit((T) this);
        }
    }

    // this is just an example of usage of T type
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }

    // *can* be left public, but that way you enforce a single way
    // of submitting tasks, namely via submitTo
    private Future<?> submit(T task) {
        if(compareSomething(task) != 0)
            throw new RuntimeException("");
        // the following cast is a save conversion - you could also write it cast-free
        // as Callable<?> callable = task; ...submit(callable);
        return someExecutorService.submit((Callable<?>) task);
    }
}

Теперь ваши задачи должны наследовать A.Submittable, а затем могут быть отправлены в экземпляр планировщика через submitTo, возвращая Future<V> для правильного типа V.

Конечно, это на самом деле полагается на T, фактически являясь самолечением подклассов Submittable. И что происходит в submitTo - ну, уродливо. Но это дает вам возможность вернуть Future<V> правильного типа.

Ответ 4

Помогает ли это решить проблему?

class A<V, T extends MyT<T, V>> {

    // this is just an example of usage of T type
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }

    public <V> Future<V> submit(MyT<T, V> task) {
        return ...;
    }
}

class MyT<T, V> implements Comparable<T>, Callable<V> {

    @Override
    public int compareTo(T o) {
        return 0;
    }

    @Override
    public V call()
        throws Exception {

        return ...;
    }
}

Ответ 5

Что-то вроде этого может работать:

class A<T extends Comparable<T>> {
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }

    public Future<T> submit(B<T> task) {
        return someExecutorService.submit(task);
    }

    public class B<T> implements Callable<T> {
        ...
    }  
}

Ответ 6

ОК, действительно последняя попытка. Можете ли вы изменить свою функцию generics на универсальный или внутренний класс? Вот так:

class A<T extends Comparable<T> >  {


    // this is just an example of usage of T type
    List<T> comparables;

    int compareSomething(T smth) {
        return comparables.get(0).compareTo(smth);
    }

    class B<V> 
     {
      T t;
      T getT() { return t; }
      Callable <V> v;
      Callable <V> getV() { return v; }
      public Future<V> submit(B<V> task) {
           if(compareSomething(task.getT()) != 0)
            throw new RuntimeException("");
        return SomeExecutorService.submit(task.getV());
    }
}

Ответ 7

Почему не так просто, как эта работа?

public class CustomThreadPool {
    public <CallableReturn> Future<CallableReturn> submit(SelfSchedulingCallable<CallableReturn> task) {
        // Use comparePriority or any other interface methods needed to determine scheduling
    }
}

public interface SelfSchedulingCallable<V> extends Callable<V> {
    public boolean isBlockedBy(SelfSchedulingCallable<?> otherTask); // Doesn't care about the return type of otherTask
    public boolean invokeAfter(SelfSchedulingCallable<?> otherTask);
    public int comparePriority(SelfSchedulingCallable<?> otherTask);
}