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

Java generics - сделать Generic для расширения двух интерфейсов

Как вы делаете эту работу:

public class Frankenstein<T extends IHuman, IMonster>{
}

Без создания

public interface Weirdo extends Ihuman, IMonster{
}

Edit

Почему это не работает?

public <T> void mapThis(
        Class<? extends MyClass<T>> key, Class<? extends T & IDisposable> value) {

}

Я получаю сообщение сообщения компилятора Class<? extends T & IDisposable> как ошибку.

4b9b3361

Ответ 1

Reimeus уже указывал, что то, что вы просите в своем редактировании, невозможно. Я просто хотел бы немного рассказать о том, почему.

Можно подумать, что вы можете использовать следующее:

public <T, U extends T & IDisposable> void mapThis(
        Class<? extends MyClass<T>> key,
        Class<? extends U> value
) { ... }

В самом деле, что мне пришло в голову, когда я впервые увидел этот пост. Но это фактически приводит к ошибке компилятора:

переменная типа может не сопровождаться другими ограничениями

Чтобы помочь мне объяснить, почему я хотел бы процитировать сообщение в блоге Oracle от Виктора Рудометова об этой ошибке:

Этот факт не всегда ясен, но это правда. Следующий код не следует компилировать:

interface I {}

class TestBounds <U, T extends U & I> {

}

Потому что JLS Chapter 4 Типы, значения и переменные Раздел 4.4. Переменные типа: " bound состоит из либо переменной типа, либо класса или типа интерфейса T, за которым последуют дополнительные типы интерфейсов я 1,..., я n. может использовать T extends U, T extends SomeClass и I, но не T расширяет U и I. Это правило применяется ко всем случаям, включая переменные типа и границы в методы и конструкторы.

Причины этого ограничения изучаются в тесно связанной записи: Почему я не могу использовать аргумент типа в параметре типа с несколькими ограничениями?

Подводя итог, было наложено ограничение, чтобы "исключить некоторые неловкие ситуации, возникающие" (JLS §4.9).

Какие неудобные ситуации? Ответ Криса Повирка описывает один:

[Причиной ограничения является] возможность указания нелегальных типов. В частности, дважды расширив общий интерфейс с разными параметрами. Я не могу придумать не надуманный пример, но:

/** Contains a Comparator<String> that also implements the given type T. */
class StringComparatorHolder<T, C extends T & Comparator<String>> {
  private final C comparator;
  // ...
}

void foo(StringComparatorHolder<Comparator<Integer>, ?> holder) { ... }

Теперь holder.comparator является Comparator<Integer> и a Comparator<String>.

Крис также указывает на Sun bug 4899305, который был ошибкой, оспаривающей это ограничение языка. Он был закрыт, так как Will not Fix со следующим комментарием:

Если переменной типа могут следовать переменные типа или (возможно параметризованных) интерфейсов, вероятно, будет более взаимно рекурсивных переменных типа, которые очень трудно обрабатывать. вещи уже сложны, когда оценка является просто параметризованным типом, например <S,R extends Comparable<S>>. Следовательно, границы не идут теперь измениться. Как javac, так и Eclipse соглашаются, что S&T и S&Comparable<S> являются незаконными.

Итак, вот причины этого ограничения. Обращаясь к типичным методам (что касается вашего вопроса), я хотел бы еще раз отметить, что вывод типа теоретически приводил бы к тому, что такие оценки будут бессмысленными.

Если мы пересмотрим параметры типа, указанные в гипотетической сигнатуре выше:

<T, U extends T & IDisposable>

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

<T, U extends Object & IDisposable>

Или просто это (тонкая разница, но что другая тема):

<T, U extends IDisposable>

Это связано с тем, что T не имеет границ, поэтому независимо от того, какой тип аргументов передается, T может, по крайней мере, разрешить Object, а затем может U.

Вернемся назад и скажем, что T ограничено:

<T extends Foo, U extends T & IDisposable>

Это можно уменьшить тем же способом (Foo может быть классом или интерфейсом):

<T extends Foo, U extends Foo & IDisposable>

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

Приложение Pre-Java 8:

До Java 8 существует прецедент для того, что вы пытаетесь сделать. Из-за ограничения того, как компилятор описывает параметры типа универсального метода, мои рассуждения выходят из окна. Возьмите следующий общий метод:

class MyClass {
    static <T> void foo(T t1, T t2) { }
}

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

MyClass.foo("asdf", 42); // legal

Здесь T определяется как Object - это соответствует предыдущим рассуждениям об упрощении параметров типа mapThis. Вы должны вручную указать параметры типа для достижения требуемой проверки типа:

MyClass.<String>foo("asdf", 42); // compiler error

Тем не менее,, и здесь, когда ваш прецедент начинает входить, это другое дело с несколькими параметрами типа с шахматными границами:

class MyClass {
    static <T, U extends T> void foo(T t, U u) { }
}

Теперь эти ошибки вызова:

MyClass.foo("asdf", 42); // compiler error

Столы повернуты - мы должны вручную расслабить параметры типа, чтобы их компилировать:

MyClass.<Object, Object>foo("asdf", 42); // legal

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

Однако эта проблема, по-видимому, исправлена ​​в Java 8, а MyClass.foo("asdf", 42) теперь компилируется без каких-либо ошибок (благодаря Regent для указания этого).

Ответ 2

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

    /**
     * This is a type-checking method, which gets around Java inability
     * to handle multiple bounds such as "V extends T & ContextAware<T>".
     *
     * @param value initial value, which should extends T
     * @param contextAware initial value, which should extend ContextAware<T>
     * @return a new proxy object
     */
    public T blah(T value, ContextAware<T> contextAware) {
        if (value != contextAware) {
            throw new IllegalArgumentException("This method expects the same object for both parameters.");
        }

        return blah(value);
    }

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