【问题标题】:How do I declare a variable that contains a subclass of a class which implements an interface?如何声明包含实现接口的类的子类的变量?
【发布时间】:2010-10-20 21:20:56
【问题描述】:

我想声明一个变量,该变量包含一个实现特定接口的类。具体来说,我试图将SocketChannel 和DatagramChannel 存储在同一个属性中,以便可以互换使用它们。这两个类都扩展了SelectableChannel 并实现了ByteChannel,我希望从这两个类中调用方法。我不想将它存储在两个单独的变量中,因为它们需要是同一个对象。我想将这个对象在一个变量中传递给构造函数。

甚至有可能做到这一点吗?如果没有,仍然支持这种类型的模型的常见解决方法是什么?为清楚起见,以下是(不正确的)声明,它们可以描述我正在尝试做的事情,它们是否有效:

私有 SelectableChannel 和 ByteChannel byteChannel; 私有 SelectableChannel 实现 ByteChannel byteChannel; 私人的 ?扩展 SelectableChannel 实现 ByteChannel byteChannel;

请注意:

我不是在寻找其他方法来处理避免这个问题的网络,或者其他方法来实现网络。这个问题是关于声明一个变量来保存一个类的子类,该类也实现了一个特定的接口。我只提供了细节,以便您知道我无法创建新的接口或子类,因为在这种情况下,所涉及的所有类都是 java.nio 包的一部分。

【问题讨论】:

    标签: java variables interface subclass nio


    【解决方案1】:

    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 - 以您的情况更方便)。

    【讨论】:

    • 如果SelectableChannel 不是ByteChannel 的实例,我想我可以在构造函数中进行测试并抛出InvalidArgumentException。然后我可以将对象存储在两个属性中,并根据需要使用每个属性来访问我想要的方法。
    • 如果您从“外部”获取通道(即该类无法直接控制通道的创建),那么在构造函数中检查它确实是一个好主意。跨度>
    【解决方案2】:

    字段不能是析取类型,您只能将该语法用作类型参数列表中绑定的类型的一部分。你可以让你的类通用:

    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
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      我认为没有直接的方法可以做你想做的事。您可以定义一个接口和两个代理类来公开您要使用的方法:

      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;
        }
      }
      

      【讨论】:

        【解决方案4】:

        Java 变量声明中的变量类型必须是确定类型或类型参数的名称。所以你想要表达的东西在 Java 中是无法表达的。

        恐怕你唯一的选择是:

        • 使用具有相同引用的各自接口类型的两个变量,或
        • 使用一个具有通用超类型的变量并使用类型转换。

        后者并没有那么糟糕。类型转换并没有那么昂贵,JIT 编译器可能能够优化其中的一部分或全部。

        (顺便说一句,您的“语法”看起来很像在 Java 中声明 TypeParameter 或 TypeArgument 的语法。但它不是真实的。)

        【讨论】:

        • 是的,我从泛型那里借用了语法,虽然我意识到这不是真的。
        【解决方案5】:

        演员不会处理这个问题吗?

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

        不幸的是,使用非 SocketChannel-non-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);
        }
        

        【讨论】:

          猜你喜欢
          • 2010-10-07
          • 2012-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-06
          • 1970-01-01
          相关资源
          最近更新 更多