Java Generics — подстановочные знаки

Я новичок в Java и попал в ситуацию, когда очевидно, что я что-то неправильно понимаю в том, как он обрабатывает Generics, но чтение руководств и поиск в stackoverflow не дали (по крайней мере, до сих пор) ясность, кроме того, что я подозреваю Я неправильно использую подстановочные знаки. Напомню, что у меня есть опыт работы с C++, поэтому то, как он работает с шаблонами, вероятно, влияет на то, как я к этому подошел.

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

abstract class PacketHeader{
   // some stuff
}

class TypeOfPacketHeader extends PacketHeader{
   // extended stuff
}

abstract class Packet<T extends PacketHeader>{
    T mHeader;
    // some methods treating T as a type of PacketHeader
    // some abstract methods
}

class TypeOfPacket extends Packet<TypeOfPacketHeader>{
    static TypeOfPacket obtain {
        return new TypeOfPacket();
    }
    // overriden abstract functions that call specific TypeOfPacketHeader methods on mHeader
}

interface PacketParser<T extends Packet<? extends PacketHeader>>{
T obtainPacket();
        void parse(T packet);
}

class ImplementedPacketParser implements PacketParser<TypeOfPacket>{
     TypeOfPacket obtainPacket(){
         return TypeOfPacket.obtain();
     }
     void parse(TypeOfPacket packet){
         // code that relies on TypeOfPacket specific functions
     }
}

Кажется, все правильно (или, по крайней мере, eclipse не жалуется), проблемы возникают, когда я пытаюсь их использовать. Моя первая попытка была:

class User{
    PacketParser mParser;

    User(PacketParser parser){
        mParser = parser;
    }

    void DoSomething(){
         Packet packet = mParser.obtainPacket();
         // do some stuff with the packet
         mParser.parse(packet);
    }
}

и приводило к предупреждениям типов Raw. Итак, я попытался...

class User{
    PacketParser<? extends Packet<? extends PacketHeader>> mParser;

    User(PacketParser<? extends Packet<? extends PacketHeader>> parser){
        mParser = parser;
    }

    void DoSomething(){
         Packet<? extends PacketHeader> packet = parser.obtainPacket();
         // do some stuff with the packet
         mParser.parse(packet);
    }
}

Но это приводит к ошибке, которая

Метод parse(capture#9-of ? extends Packet) в типе PacketParser> неприменим для аргументов (Packet)

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


person Khanmots    schedule 19.06.2012    source источник
comment
ИМХО Не сходите с ума с дженериками, пытаясь описать все, так как это короткий путь к путанице. Постарайтесь, чтобы это было как можно проще, и это, скорее всего, будет иметь смысл. ;)   -  person Peter Lawrey    schedule 20.06.2012
comment
Ну, по крайней мере, мне кажется, что гораздо проще понять архитектуру для этих точек перегиба в дизайне сейчас, чем пытаться разработать только для одного случая сейчас, а затем втиснуть их позже.   -  person Khanmots    schedule 20.06.2012


Ответы (2)


В вашем коде User компилятор не знает, что ? extends Packet<? extends PacketHeader> в поле mParser имеет тот же тип, что и локальная переменная Packet<? extends PacketHeader> packet.

Вам нужно привязать тип пакета, который будет использоваться, сделав User универсальным:

class User<T extends Packet<?>> {
    PacketParser<T> mParser;

    User(PacketParser<T> parser){
        mParser = parser;
    }

    void DoSomething(){
         T packet = parser.obtainPacket();
         // do some stuff with the packet
         mParser.parse(packet);
    }
}

// and then when instantiating your User, specify the packet type:
new User<TypeOfPacket>(new ImplementedPacketParser())

Теперь компилятор знает, что T, которые он видит, представляют один и тот же тип Packet<?>, тогда как каждый раз, когда он видит ? extends Packet<?>, могут быть разные подтипы Packet<?>.

Изменить: Дополнительное примечание: "? extends PacketHeader" не требуется в объявлении интерфейса PacketParser, потому что класс Packet уже ограничивает свой T до PacketHeader и его подтипов.

// ? is implicitly constrained to "extends PacketHeader" because of
// the way Packet's generic is defined
interface PacketParser<T extends Packet<?>> {
person matts    schedule 19.06.2012
comment
Я думаю, что понимаю, что сейчас происходит, и я ценю ваше указание на неявное ограничение. Я надеялся, что, поскольку пакет был получен от mParser, компилятор сможет его понять, но, возможно, на это было слишком много надежд (я бы не стал пытаться написать компилятор, который мог бы это сделать. .) - person Khanmots; 20.06.2012
comment
Кажется, все это реализовано и теперь компилируется без ошибок или предупреждений. Пришлось немного подправить верхний слой, но получилось не так уж и плохо. Баночка спасибо! - person Khanmots; 20.06.2012
comment
Нет проблем, рад, что смог помочь! - person matts; 20.06.2012

Я внес некоторые изменения в ваш код. Сейчас он компилируется. Попробуйте использовать это.

   abstract class PacketHeader {
    // some stuff
   } 
   class TypeOfPacketHeader extends PacketHeader {
        // extended stuff
    }

    abstract class Packet<T extends PacketHeader> {
        T mHeader;
        // some methods treating T as a type of PacketHeader
        // some abstract methods
    }

    class TypeOfPacket extends Packet<TypeOfPacketHeader> {
        static TypeOfPacket obtain() {
            return new TypeOfPacket();
        }
        // overriden abstract functions that call specific TypeOfPacketHeader
        // methods on mHeader
    }

    interface PacketParser<T extends Packet<? extends PacketHeader>> {
        T obtainPacket();

        void parse(T packet);
    }

    class ImplementedPacketParser implements PacketParser<TypeOfPacket> {
        public TypeOfPacket obtainPacket() {
            return TypeOfPacket().obtain();
        }

        private TypeOfPacket TypeOfPacket() {
            // TODO Auto-generated method stub
            return null;
        }

        public void parse(TypeOfPacket packet) {
            // code that relies on TypeOfPacket specific functions
        }
    }

Ваш класс пользователя.

class User{
    private PacketParser mParser;

    User(PacketParser parser) {
        mParser = parser;
    }

    void DoSomething() {
        Packet packet = mParser.obtainPacket();
        // do some stuff with the packet
        mParser.parse(packet);
    }
}
person Akhi    schedule 19.06.2012