Как мне объявить переменную, которая содержит подкласс класса, который реализует интерфейс? - PullRequest
3 голосов
/ 20 октября 2010

Я хочу объявить переменную, которая содержит класс, который реализует определенный интерфейс.В частности, я пытаюсь сохранить SocketChannel и DatagramChannel в одном свойстве, чтобы я мог использовать их взаимозаменяемо.Оба эти класса расширяют SelectableChannel и также реализуют ByteChannel, и я хочу вызывать методы из обоих.Я не хочу хранить это в двух отдельных переменных, потому что они должны быть одним и тем же объектом.Я хочу передать этот объект в одной переменной конструктору.

Возможно ли это сделать?Если нет, то каковы общие обходные пути, которые все еще поддерживают этот тип модели?Для ясности вот (неправильные) объявления, которые могли бы описать то, что я пытаюсь сделать, были ли они действительными:

private SelectableChannel & ByteChannel byteChannel;
private SelectableChannel implements ByteChannel byteChannel;
private ? extends SelectableChannel implements ByteChannel byteChannel;

Обратите внимание:

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

Ответы [ 5 ]

3 голосов
/ 20 октября 2010

В Java нет способа объявить переменную так, как вы хотели бы это сделать.

Вы можете использовать SelectableChannel для типа переменной (поскольку это супертип как SocketChannel, так и DatagramChannel), и приводить его к ByteChannel всякий раз, когда вам нужно вызвать методы из этого интерфейса. Простой пример:

class MyClass {
    private SelectableChannel channel; // either a SocketChannel or a DatagramChannel

    public int readStuff(ByteBuffer buffer) {
        // Cast it to a ByteChannel when necessary
        return ((ByteChannel) channel).read(buffer);
    }
}

(Или наоборот: объявите переменную как ByteChannel и приведите к SelectableChannel при необходимости - в зависимости от того, что удобнее в вашем случае).

2 голосов
/ 20 октября 2010

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

class Foo<C extends SelectableChannel & ByteChannel> {
    private C selectableByteChannel;
}

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

abstract class Foo {
    private Foo(){}
    public abstract void doSomethingWithASelectableByteChannel();
    public static <C extends SelectableChannel & ByteChannel> 
            Foo createFoo(C channel) {
        return new FooImpl<C>(channel);
    }
    private static final class FooImpl<C extends SelectableChannel & ByteChannel> 
            extends Foo 
    {
        private final C selectableByteChannel;
        private FooImpl(C channel){
            selectableByteChannel = channel;
        }
        public void doSomethingWithASelectableByteChannel(){
            // Do stuff with your selectableByteChannel
        }
    }
}
1 голос
/ 20 октября 2010

Тип переменной в объявлении переменной Java должен быть либо определенного типа, либо именем параметра типа. То, что вы пытаетесь выразить, не выражается в Java.

Боюсь, вы можете выбрать только:

  • использовать две переменные с соответствующими типами интерфейса, содержащие одну и ту же ссылку, или
  • используйте одну переменную с общим супертипом и используйте типы типов.

Последнее не так плохо, как все это. Типы не так уж дороги, и JIT-компилятор может оптимизировать некоторые или все из них.

(Между прочим, ваш "синтаксис" выглядит скорее как синтаксис для объявления TypeParameter или TypeArgument в Java. Но это не реально.)

1 голос
/ 20 октября 2010

Я не думаю, что есть прямой способ сделать то, что вы хотите. Вы можете определить интерфейс и два прокси-класса для предоставления методов, которые вы хотите использовать:

interface GenericChannel {
  // ... methods you want to use ...
}

class SocketWrapper implements GenericChannel {
  private final SocketChannel channel;
  public SocketWrapper(SocketChannel channel) {
    this.channel = channel;
  }
  // ... pass through calls to this.channel ...
}

class DatagramWrapper implements GenericChannel {
  private final DatagramChannel channel;
  public DatagramWrapper(DatagramChannel channel) {
    this.channel = channel;
  }
  // ... pass through calls to this.channel ...
}

или

class GenericWrapper<C extends SelectableChannel & ByteChannel> implements GenericChannel {
  private final C channel;
  public DatagramWrapper(C channel) {
    this.channel = channel;
  }
}
0 голосов
/ 20 октября 2010

Разве кастинг не позаботится об этом?

public void doWhatever(SelectableChannel foo) {
    // Call a SocketChannelMethod
    ((SocketChannel)foo).someSocketChannelMethod();

    // Call a DatagramChannel method
    ((DatagramChannel)foo).someByteChannelMethod();
}

К сожалению, вызов этой функции с не-SocketChannel-не-DatagramChannel SelectableChannel будет исключением времени выполнения вместо ошибки времени компиляции. Но вы можете сделать его закрытым и иметь два открытых метода, один из которых принимает SocketChannel, а другой - DatagramChannel. Даже если ваш интерфейс более сложен, вы можете настроить сеттеры аналогичным образом:

public void setChannel(SelectableChannel foo) {
    this.channel = foo;    
}
public void setChannel(SocketChannel foo) {
    setChannel((SelectableChannel) foo);
}
public void setChannel(DatagramChannel foo) {
    setChannel((SelectableChannel) foo);
}
...