2010-10-20 39 views
3

Je veux déclarer une variable qui contient une classe qui implémente une interface spécifique. Spécifiquement, j'essaye de stocker un SocketChannel et un DatagramChannel dans la même propriété ainsi je peux les employer de manière interchangeable. Ces deux classes étendent SelectableChannel et implémentent également ByteChannel, et je souhaite appeler des méthodes des deux. Je ne veux pas stocker cela dans deux variables séparées, parce que moi, ils doivent être le même objet. Je veux passer cet objet dans une variable au constructeur.Comment déclarer une variable qui contient une sous-classe d'une classe qui implémente une interface?

Est-il même possible de faire cela? Si non, quelles sont les solutions de contournement communes qui supportent encore ce type de modèle? Pour plus de clarté, voici les déclarations (incorrectes) qui pourrait décrire ce que je suis en train de faire, étaient-ils valides:

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

S'il vous plaît Note:

Je ne cherche pas d'autres façons de gérer mise en réseau qui évite ce problème, ou d'autres moyens de mettre en œuvre le réseautage. Cette question concerne la déclaration d'une variable pour contenir une sous-classe d'une classe qui implémente également une interface spécifique. J'ai seulement donné les détails pour que vous sachiez que je ne peux pas créer une nouvelle interface ou sous-classe parce que dans ce cas toutes les classes impliquées font partie du paquet java.nio.

Répondre

3

Il n'y a aucun moyen en Java pour déclarer la variable comme vous le souhaitez.

Vous pouvez utiliser SelectableChannel pour le type de la variable (puisque c'est un super-type à la fois SocketChannel et DatagramChannel), et jette un ByteChannel chaque fois que vous avez besoin d'appeler des méthodes de cette interface. Exemple simple:

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

(Ou l'inverse: déclarer la variable comme ByteChannel et jeté à un SelectableChannel si nécessaire - si cette date est plus pratique dans votre cas).

+0

Je suppose que je pourrais tester dans le constructeur et jette un '' InvalidArgumentException' si le SelectableChannel' est pas un instance de 'ByteChannel'. Ensuite, je pourrais stocker l'objet dans deux propriétés et utiliser chacune d'elles pour accéder à la méthode que je souhaite. –

+1

Si vous obtenez le canal de "outside" (c'est-à-dire que la classe n'a pas de contrôle direct sur la création du canal), alors le vérifier dans le constructeur serait en effet une bonne idée. – Jesper

1

Je ne pense pas qu'il existe un moyen direct de faire ce que vous voulez. Vous pouvez définir une interface et deux classes de proxy pour exposer les méthodes que vous souhaitez utiliser:

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

ou

class GenericWrapper<C extends SelectableChannel & ByteChannel> implements GenericChannel { 
    private final C channel; 
    public DatagramWrapper(C channel) { 
    this.channel = channel; 
    } 
} 
2

Les champs ne peut pas être d'un type de disjonction, vous ne pouvez utiliser cette syntaxe comme partie d'un type lié dans une liste de paramètres de type. Vous pouvez faire de votre classe générique:

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

Cela met un peu de pression supplémentaire sur le code client, bien que, pour que vous puissiez cacher ce genre de choses derrière une méthode de fabrication et une classe de mise en œuvre privée qui est générique:

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

Le type d'une variable dans une déclaration de variable Java doit être un type défini ou le nom d'un paramètre de type. Donc ce que vous essayez d'exprimer n'est pas exprimable en Java.

Je crains que vos seuls choix sont:

  • utilisent deux variables avec les types d'interface respectifs contenant la même référence, ou
  • utiliser une variable avec un super-type commun et utiliser typecasts.

Ce dernier n'est pas aussi mauvais que tout cela. Les typecasts ne sont pas si chers, et le compilateur JIT peut être en mesure d'optimiser certains ou tous.

(Soit dit en passant, votre « syntaxe » ressemble plutôt à la syntaxe pour déclarer une TypeParameter ou TypeArgument en Java. Mais ce n'est pas vrai.)

+0

Oui, j'ai emprunté la syntaxe aux génériques, bien que je réalise que ce n'est pas réel. –

0

N'a pas coulée prendre soin de tout cela?

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

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

Malheureusement, appeler cette fonction avec un non-SocketChannel non DatagramChannel SelectableChannel serait une exception d'exécution au lieu d'une erreur de compilation. Mais vous pouvez le rendre privé et avoir deux méthodes publiques, une qui accepte un SocketChannel et une qui accepte un DatagramChannel. Même si l'interface est plus complexe que cela, vous pourriez avoir votre setters configuration de la même manière:

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