Java Generics ограничивает интерфейс

Я не уверен, возможно ли это или нет, но я хочу выполнить следующее:

public static <A,B extends SomeClass & A> B makeB(A thing) {...}

По существу, используя процесс, основанный на отражении/генерации, я хочу предоставить вещь типа B, где B имеет класс SomeClass и реализует интерфейс A, а A предоставляется пользователем через Generics.

Я не спрашиваю о механизме генерации B - у меня есть это под контролем. Я ищу способ ограничить аргумент основного типа <A> интерфейсами, а не классами, поэтому я могу использовать синтаксис B extends SomeClass & A для безопасности безопасного типа.

Возможно ли это? Кто-нибудь знает об альтернативном подходе к этой проблеме?


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

B предназначен для подстановки подстановочного знака, так что клиент может получить один объект, который является как SomeClass, так и A, без необходимости делать кастинг на основе доверия. Клиент не будет иметь доступ к имени фактического класса, который реализует SomeClass и A, поскольку он генерируется во время компиляции, следовательно, это проблема безопасности типов.

Ответ 1

Невозможно наложить такое ограничение времени компиляции. Типичные типовые параметры - это stand-ins для ссылочных типов; они не делают различий между типами классов и типами интерфейсов. Тот факт, что дополнительные ограничения в объявлении параметра типа должны быть типами интерфейсов, является просто случайным - ваша стратегия использовать это как средство для приведения типа в качестве интерфейса была умной, но она была побеждена ограничением, которое вводит параметры не может использоваться в нескольких границах.

Ваши единственные варианты - установить для проверки времени выполнения, используя Class.isInterface(), как Louis Wasserman указали, или оставить его вызывающему абоненту, чтобы он отвечал за то, что он передает. В любом случае, убедитесь, что вы четко документируете методы ожидания и поведение.


B предназначен для подстановки подстановочного знака, так что клиент может получить один объект, который является как SomeClass, так и A, без необходимости делать кастинг на основе доверия. У клиента не будет доступа к имени фактического класса, который реализует SomeClass и A

Это кажется мне противоречием. Нет смысла объявлять B, если вызывающий абонент не может знать, что он оценивает. Помните: вызывающий объект общего метода предоставляет свои аргументы типа. Таким образом, вызывающий абонент, принимающий решение B без каких-либо оснований, может только угадывать - и это никогда не может быть безопасным по типу.

Кажется, что вы действительно хотите, чтобы ваш метод возвращался, это какой-то тип, который является как SomeClass, так и A, но это сложно, потому что они не имеют общего супертипа:

public static <A> SomeClass&A makeSomeClass(A thing) {...}

(это бессмысленный синтаксис только для демонстрационных целей)

В качестве обходного пути рассмотрите альтернативные способы представления как a SomeClass, так и некоторого типа интерфейса. Например, интерфейсы-кандидаты могут иметь общий метод для возврата SomeClass:

public interface IsSomeClass {
    SomeClass asSomeClass();
}

public interface Foo extends IsSomeClass { }

Реализация asSomeClass на самом деле просто вернет this. Тогда вы можете сделать:

public static <A extends IsSomeClass> A makeSomeClass(Class<A> type) {...}

И вызывающий объект этого метода сможет использовать возвращаемый объект как тип:

final Foo foo = makeSomeClass(Foo.class);
final SomeClass someClass = foo.asSomeClass();

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

final class SomeClassWrapper<A> {

    private final SomeClass someClass;
    private final A a;

    //constructor and getters, etc.
}

И ваш метод вернет экземпляр оболочки вместо этого, назначив экземпляр реализации как SomeClass, так и A:

public static <A> SomeClassWrapper<A> makeSomeClass(Class<A> type) {...}

Ответ 2

Если SomeClass всегда является классом, A в <B extends SomeClass & A> может быть только интерфейсом, потому что на Java нет множественного наследования. Единственный способ & A может быть выполнен, если A является интерфейсом.

Ответ 3

Я думаю, проблема в том, что вы хотите вернуть B из этого метода.

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

Как компилятор должен вывести возвращаемый тип из аргументов????

Нет возможности для кода клиента указать, что B.

Кажется, вы должны вернуть либо SomeClass, либо A.

Любой может быть B под капотом, но должен выглядеть как SomeClass или A для кода клиента.