Как создать рекурсивные связанные дженерики с внутренним классом в Java?

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

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

package tests.java;

public class Try_GenericInnerRecursion {

    // base class, consisting of outer and inner parts
    public static class Outer1<E extends Outer1<E>.Inner1> {
        public class Inner1 {
        }

        public void addElement(E e) {
            System.out.println("Added " + e.toString());
        }
    }

    // extending outer, but not inner
    public static class Outer2<E extends Outer1<E>.Inner1> extends Outer1<E>{

    }

    // extending both outer and inner
    public static class Outer3<E extends Outer3<E>.Inner3> extends Outer1<E>{
        public class Inner3 extends Inner1 {
        }
    }

    // extending both outer and inner and stopping extension
    public static class Outer4 extends Outer1<Outer4.Inner4> {
        public class Inner4 extends Outer1<Inner4>.Inner1 {
        }
    }



    // instantiating
    public static void main(String[] args) {

        Outer1<Outer1.Inner1> a1; // WARNING: Outer1.Inner1 is a raw type

        a1 = new Outer1<Outer1.Inner1>(); // WARNING: Outer1.Inner1 is a raw type

        Outer1<?> a2; // OK 

        a2 = new Outer1<?>(); // ERROR: Cannot instantiate the type Outer1<?>

        Outer1<Outer1<?>.Inner1> a3; // ERROR: Bound mismatch: The type Outer1<?>.Inner1 is not a valid substitute for the bounded parameter <E extends Outer1<E>.Inner1> of the type Outer1<E>

    Outer1<? extends Outer1<?>.Inner1> a4; // OK

    a4 = new Outer1<Outer1.Inner1>(); // ERROR: Type mismatch

            Outer2<Outer1.Inner1> b1; // WARNING: Outer1.Inner1 is a raw type

            b1 = new Outer2<Outer1.Inner1>(); // WARNING: Outer1.Inner1 is a raw type

        // and so on
    }

}

Как правильно использовать этот шаблон?

Ответ 1

Я думаю, вы можете просто сделать

    DerivedCorpus1<?>

во время захвата подстановки, он становится

    DerivedCorpus1<x> where x extends Corpus<x>.Element

который правильно ограничен.

В вашем exmaple

    DerivedCorpus1<? extends Corpus<?>.Element>

во время захвата подстановки, он становится

    DerivedCorpus1<x> where x extends Corpus<x>.Element
                        and x extends Corpus<?>.Element

очевидно, предложение extends Corpus<?>.Element является избыточным.

Ответ 2

Outer1<Outer1.Inner1> a1; // WARNING: Outer1.Inner1 is a raw type

На самом деле, я получаю аргумент типа Outer1.Inner1 не находится в пределах границ переменной типа E`.

Outer1.Inner1 является сырым типом, потому что Outer1 является сырым типом. Чтобы использовать не-сырой тип, вам нужно написать Outer1<something>.Inner1. Однако, что something также должно было бы расширить Outer1<something>.Inner1 по очереди. Чтобы иметь такую ​​рекурсию, вам нужен именованный рекурсивный тип. К сожалению, поскольку Inner1 - нестатический внутренний класс, он имеет неявную ссылку на экземпляр Outer1, и поэтому любой класс, который его расширяет, также должен иметь охватывающий экземпляр Outer1. Outer4 и Inner4 в основном делают это.

Outer4 a1 = new Outer4(); // compiles fine

a2 = new Outer1<?>(); // ERROR: Cannot instantiate the type Outer1<?>

Вы никогда не сможете сделать new something<?>().

Outer1<Outer1<?>.Inner1> a3; // ERROR: Bound mismatch: The type Outer1<?>.Inner1 is not a valid substitute for the bounded parameter <E extends Outer1<E>.Inner1> of the type Outer1<E>

Это верно. Outer1<?>.Inner1 не является подтипом Outer1<E>.Inner1 - он наоборот - Outer1<E>.Inner1 является подтипом Outer1<?>.Inner1. Это точно так же, как ArrayList<?> не является подтипом ArrayList<String>; это наоборот.

Outer1<? extends Outer1<?>.Inner1> a4; // OK

Это нормально, потому что у вас есть подстановочный знак на верхнем уровне, и ваш шаблонный символ пересекает параметр типа E. На самом деле все, что удовлетворяет исходной оценке E, должно удовлетворять этой оценке, поэтому эта оценка бесполезна, и это то же самое, что и Outer1<?> a2; выше.

a4 = new Outer1<Outer1.Inner1>(); // ERROR: Type mismatch

Не работает, по другим причинам, по той же причине, что a1 не работает (Outer1.Inner1 не удовлетворяет E). Кроме того, он не удовлетворяет вашей оценке (Outer1<?>.Inner1), я считаю.

Outer2<Outer1.Inner1> b1; // WARNING: Outer1.Inner1 is a raw type

Это фактически дает ту же ошибку, что и a1, по той же причине

Ответ 3

Общая спецификация

Прежде чем перейти к проблемам создания экземпляра, я пересмотрю ваше общее определение:

Outer1<E extends Outer1<E>.Inner1>

Outer1 должен в основном содержать элементы типа Inner1 или любые дочерние классы. Поэтому тип объекта Outer1 не имеет значения и может быть заменен следующим кодом:

Outer1<E extends Outer1<?>.Innter1>

Также расширяющиеся классы были слегка изменены с использованием вышеупомянутого.

Инстанцирование

Для создания объектов вы указали следующее:

Outer1<Outer1.Inner1> a1;          // WARNING: Outer1.Inner1 is a raw type
a1 = new Outer1<Outer1.Inner1>();  // WARNING: Outer1.Inner1 is a raw type

Как уже упоминалось в newacct, Outer1 объявляется с общим типом, хотя вы не указывали при создании экземпляра объекта. Поэтому код должен быть заменен на следующий:

Outer1<Outer1<?>.Innter1> a1;           // or simply Outer1<?> a1;
a1 = new Outer1<Outer1<?>.Innter1>();   // or simply a1 = new Outer1<>();

Аналогично для назначения классов, которые расширили базовый класс до типа этого базового класса.

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

Объявление типа

Outer1<Outer1<?>.Innter1> test;

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

test = new Outer1<Outer1<?>.Inner1>();
test = new Outer2<Outer2<?>.Inner1>();

но не Outer3 или даже Outer4!

Даже если вы измените код для добавления f.e.

public static class Outer5<E extends Outer1<?>.Inner1> extends Outer1<E>{
    public class Inner5 extends Inner1 {
    }
}

вы просто можете добавить

test = new Outer5<Outer5<?>.Inner1>();

но не

test = new Outer5<Outer5<?>.Inner5>(); // FAILS!

для того, что определило Inner1 как аргумент типа.

Однако на самом деле существует довольно простое решение этой проблемы. Если вы определяете объект следующим образом:

Outer1<? extends Outer1<?>.Inner1> test2;

вы действительно можете сделать следующее:

test2 = new Outer1<Outer1<?>.Inner1>();
test2 = new Outer2<Outer2<?>.Inner1>();
test2 = new Outer3<Outer3<?>.Inner3>();
test2 = new Outer4();
test2 = new Outer5<Outer1<?>.Inner1>();
test2 = new Outer5<Outer5<?>.Inner1>();
test2 = new Outer5<Outer5<?>.Inner5>();

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

код

Я обновил ваш код и добавил несколько новых назначений, чтобы убедиться, что сделанные изменения фактически не приводят к ошибкам или предупреждениям компиляции, кроме redundant type arguments in new expression из-за явного указания аргументов типа вместо использования оператора алмаза <>:

public class GenericInnerRecursion
{
    // base class, consisting of outer and inner parts
    public static class Outer1<E extends Outer1<?>.Inner1> {
        public class Inner1 {
        }

        public void addElement(E e) {
            System.out.println("Added " + e.toString());
        }
    }

    // extending outer, but not inner
    public static class Outer2<E extends Outer1<?>.Inner1> extends Outer1<E>{

    }

    // extending both outer and inner
    public static class Outer3<E extends Outer3<?>.Inner3> extends Outer1<E>{
        public class Inner3 extends Inner1 {
        }
    }

    // extending both outer and inner and stopping extension
    public static class Outer4 extends Outer1<Outer4.Inner4> {
        public class Inner4 extends Outer1<Inner4>.Inner1 {
        }
    }

    public static class Outer5<E extends Outer1<?>.Inner1> extends Outer1<E>{
        public class Inner5 extends Inner1 {
        }
    }


    // instantiating
    public static void main(String[] args) {

        Outer1<Outer1<?>.Inner1> a1;
        a1 = new Outer1<Outer1<?>.Inner1>();

        Outer1<?> a2;
        a2 = new Outer1<>();

        Outer1<Outer1<?>.Inner1> a3; 

        Outer1<? extends Outer1<?>.Inner1> a4;
        a4 = new Outer1<Outer1<?>.Inner1>();

        Outer2<Outer1<?>.Inner1> b1;
        b1 = new Outer2<Outer1<?>.Inner1>();

        // and so on

        // assigning extension-classes to the parent-class
        Outer1<Outer1<?>.Inner1> c1;
        c1 = new Outer2<Outer2<?>.Inner1>();
        // assigning inner-extension-classes to parent-class
        Outer1<Outer3<?>.Inner3> c2;
        c2 = new Outer3<Outer3<?>.Inner3>();
        // assigning extension class without specified generics to parent class
        Outer1<Outer4.Inner4> c3;
        c3 = new Outer4();

        Outer1<Outer1<?>.Inner1> test;
        test = new Outer1<>();
        test = new Outer2<>();
        test = new Outer5<Outer5<?>.Inner1>();

        Outer1<? extends Outer1<?>.Inner1> test2;
        test2 = new Outer1<Outer1<?>.Inner1>();
        test2 = new Outer2<Outer2<?>.Inner1>();
        test2 = new Outer3<Outer3<?>.Inner3>();
        // new Outer3<Outer3<?>.Inner1>(); not possible as generic type extends Outer3<?>.Inner3 and not Outer1<?>.Inner1!
        test2 = new Outer4();
        test2 = new Outer5<Outer1<?>.Inner1>();
        test2 = new Outer5<Outer5<?>.Inner1>();
        test2 = new Outer5<Outer5<?>.Inner5>();
    }
}