web-dev-qa-db-ja.com

switchステートメントを使用して、値ではなく「instanceof」をチェックする

Javaでswitchステートメントを使用して、オブジェクトがクラスのインスタンスであるかどうかを確認することを可能にする構文(一連のifステートメント以外)はありますか?つまり、次のようなものです。

switch (object) {
     case instanceof SecondObject:
        break;
     case instanceof ThirdObject:
        break;
}

補足:デザインパターンとして、instanceofをチェックし、それに基づいて決定を行うことは、継承を使用する場合ほど好ましくないことを認識しています。なぜこれが私に役立つのかを説明するために、これが私の状況です。

kryonet クライアント/サーバーを含むプロジェクトに取り組んでいます。 KryoNetを介した通信では、kryoNetを使用してシリアル化するオブジェクトを渡します。オブジェクトに何かを追加すると、これらのパケットのサイズが大きくなります。1 したがって、ネットワークトラフィックを最小限に抑えるには、これらのオブジェクトをできるだけ軽量に保ちます。パケットを受信するとき、それをどうするかを知る必要があるので、私は渡されたオブジェクトがinstanceofであるパケットオブジェクトのタイプに基づいてこの決定を行っています。このアプローチは、Kryonetの配布に含まれている例で行われているものと同じです。これらのパケットをアクションに変換する恐ろしいクラスをクリーンアップし、switchステートメントで少なくとも適度に組織的に見えるようにしたいと思います。


1上記のステートメントを書いたとき、私が言っていることが正しいかどうか疑問に思いました。 以下の回答 は同意しないようです:

メソッドはオブジェクトの実行時サイズに何も追加しません。メソッドコードは送信されず、メソッドについては、オブジェクトの状態での表現は必要ありません。これは、インスタンスのテストなどの正当な理由ではありません。

本当の問題は、Kryoがオブジェクトをシリアル化するためのフードの下で何が起こるかわからないことだと思います。帯域幅を増やす何かをすることに神経質になりました。あなたの答えは、継承でそれを行い、適切なメソッドをオブジェクトに追加するように私を奮い立たせました。ありがとうございます!

8
Cameron Fredman

私はパーティーに2年遅れていることを十分に理解しています:-)しかし、他の開発者に役立つ説明はほとんどありません。

他の回答が対処し損ねたのは、問題に追加のひねりを加える「Kryonet」の言及でした:通常、クライオネットが使用される場合、それは

a)クライアントはAndroidアプリであり、

b)クライアントはゲームです

両方の条件が満たされている場合、instanceof if-elseシーケンスを使用してIMHOを実行する方法です。

私たちのケースでクライアント/サーバーアプリを構築する通常の方法Android game + "pure" Javaサーバーは(少なくとも)3つのプロジェクトを使用します:

  • "common"-サーバーとクライアントに共通のコードが含まれます。最も重要なのはすべてのメッセージクラスです(通常、いくつかのタグ/マーカーインターフェイスをGameMessageとして実装します)。
  • server-サーバーを含み、commonプロジェクトを含みます。通常、すべてのメッセージはKryonetに単純に登録されます。つまり、プーリングは使用されません(サーバーGCでは問題と見なされないため)。
  • Androidクライアント-Androidアプリ(ゲーム)を含み、commonプロジェクトを含みます。Javaゲーム ゲームがGCをトリガーする可能性があります これは最大の大罪と見なされます(これは、見逃されたフレームの地獄に直接つながります)。1 ゲームメッセージにオブジェクトプーリングを使用する(これは、オブジェクトプールを使用するためにKryonet/kryoをほとんどカスタマイズする必要がない)。すべてのメッセージは、機能するためにサーバー上と同じ順序でkryonetに登録されている必要があります(登録する必要があります)。

最初に、代替案を見て、それらが上記の構造/実装に適合しない理由を見てみましょう。

1。彼らは「ポリモーフィズムとメソッドのオーバーライドを使用する」と言います]

提案されたソリューションの例については、 ここ をご覧ください(jmgによる承認済みの回答)。とてもエレガントに見えますが、少なくとも2つの大きな問題があります。

  1. do()のようなパラメーターなしのオーバーライドされたメソッドを使用することはできません。実際の実装では、処理する外部データが必要になる場合があるためです。たとえば、サーバーでPlayerAbandonedGameメッセージを受信する場合、removePlayer(Player pl)メソッドを呼び出すには、パラメーターとして少なくとも特定のゲームオブジェクトが必要です。 NewGameStateメッセージには、更新される特定のGameState変数への参照が必要になります。非常に醜い解決策の1つは、TheWholeThingのような単一のパラメーターが必要なすべてのオブジェクトを含むようにオーバーライドされたメソッドです。言うまでもなく、これはカプセル化の原則に違反するだけでなく、ゲームロジックを複数のメッセージクラスに「広げる」ことにもなります。

  2. commonプロジェクトがあり、すべてのメッセージがそこで定義されていることに注意してください。サーバーからクライアントへのメッセージが完全に別個のクライアントからサーバーへのメッセージに設定されている場合でも、サーバーまたはクライアントのロジックをcommonプロジェクトに配置するのは単純に愚かです。

ゲームメッセージオブジェクトは単なるデータコンテナです。特定の方法でデータを処理する(または、UDPメッセージの順序が乱れている場合によく発生するため、データを無視する)ことは、サーバーまたはクライアントの責任です。

2。彼らは「リフレクションを使う」と言います

私たちのコンテキストは「a)クライアントはAndroidアプリ」です。すべてのAndroid開発者がUse reflectionを聞いたときの反応は、立ち上がってクリアすることです。喉と彼/彼女の最高の英国のアクセントで言う: 'あなたは完全にmAdですか?!' :-)

古いバージョンのリフレクションが非常に遅いAndroidバージョン、現在(5.0)でも遅いです。また、リフレクションを使用したIMOは、この特定の問題だけでなく、最も醜い解決策です。スタック、つまりアプリ開発。

行く方法

元の質問に戻ります。本当にswitchの使用を主張する場合、解決策は次のような基本クラスを持つことです。

public abstract class GameMessage {
    byte mCode; // not private because of kryo

    public GameMessage(byte code) {
        super();
        mCode = code;
    }

    public byte getCode() {
        return mCode;
    }
}

具体的なメッセージは次のようになります。

public class PlayerAbandonedGame extends GameMessage {
    // some fields

    public PlayerAbandonedGame() {
        super((byte) 42);
    }
}

メッセージハンドラは次のようになります。

public synchronized void onMessageReceived(GameMessage msg) {
    switch(msg.getCode()) {
        case 42:
            // do something
            break;
        default:
            mLogger.error("Unknown message code {}", msg.getCode());
            // may be even throw an exception?
            break;
    }

    // ...
}

もちろん、ハードコードされた数値の代わりに列挙値を使用することをお勧めします...

新しいメッセージクラスを作成し、それをいくつかのswitchステートメントに追加し忘れた場合、潜在的な欠点は(他の回答で述べたように)です。コンパイラはこの問題をキャッチできず、実行時のみ(defaultの場合)に問題が発生します。

Real(小さい)欠点は、codeフィールドが毎回送信され、トラフィックに追加されることです。実際には、これによりメッセージのサイズが約1〜10%増加します(コードフィールドにbyteを使用する場合)。

方法

私は個人的にif-elseシーケンスをinstanceof testで使用しています:

public synchronized void onMessageReceived(GameMessage msg) {
    if (msg instanceof MyLetterAttemptResult) {
        handleMyLetterAttemptResult((MyLetterAttemptResult) msg);
        mPools.release(msg);
    } else if (msg instanceof InAutoMatchQueue) {
        handleInAutoMatchQueue((InAutoMatchQueue) msg);
    } else if (msg instanceof AutoMatchRetry) {
        handleAutomatchRetry();
    // more else-ifs
    } else {
        mLogger.error("Unknown message {}", msg.getClass().getSimpleName());
        // throw exception may be?
    }
}

その「コードのにおい」ではありませんか?確かにそうですが、他のすべての代替案を試した結果、それが最善のように思えます。においを減らすには、上記のようにコードをifsで短く保つことをお勧めします。 handle *メソッドを呼び出し、メッセージを解放してプールに戻します(プールされている場合)。

遅くないですか?switch(msg.getCode())より少し遅いですが、それほどではありません。 ここにいくつかのベンチマークがあります (Dan Mihai Ileによる回答)。利点は、codeフィールドをネットワーク経由で毎回送信する必要がないことです。最も頻繁に使用されるメッセージを先頭にして、最初に見つけられるようにします。

そのエラーが発生しやすいですか?新しいメッセージクラスを作成し、対応するelse ifを追加し忘れると、リスクが発生します。重要なのは、サーバー上とクライアント上に1つずつ、合計2つの個別のメッセージ処理メソッドがあることです。どちらも、サーバーとクライアントでそれぞれ1つの最も重要なコードです。あなたは単にそれを台無しにしてはいけません。 onMessageReceived()をテストする単体テストに新しいメッセージクラスをすぐに追加するだけです。

これはOOPではありません!私はより良い解決策を持っています!私はそれについて議論してそれを使用することがとても幸せです:-)。ところで、@ Cameron(この質問の作成者)はより良い解決策を見つけたのでしょうか。結局、この質問は2歳です...


1 私にとって、GCを回避することは、「ベストプラクティス」というよりも「まったくがらくた」に似ています。 Eonilが 1 でコメントしたように:I regard avoiding GC on GC based language is a kind of insane trial.

6
Ognyan

答えはノーだ。

しかし、Java 7を使用すると、これを行うことができます:

switch (object.getClass().getName()) {
  case "some.pkg.SecondObject":
    break;
  case "some.pkg.ThirdObject":
    break;
}

...まったく同じではありませんが、十分近いかもしれません。

問題は、if ... instanceofテストを使用しても、上記のswitchハッカーを使用しても、これが「コードのにおい」であることです。

どうして?

新しいクラスを追加するときはいつでも、このクラスのテストを行うすべての場所を再訪する必要があるためです!!また、クラスの名前を変更すると、コードbreaksになるという追加の問題があります。

推奨されるOOアプローチは、ポリモーフィズムを使用することです。

オブジェクトに追加すると、これらのパケットのサイズが大きくなるため、ネットワークトラフィックを最小限に抑えるために、これらのオブジェクトをできるだけ軽量に保ちます。

メソッドはオブジェクトの実行時サイズに何も追加しません。メソッドコードは送信されず、メソッドについては、オブジェクトの状態での表現は必要ありません。これはinstanceofテストなどの正当な理由ではありません。

19
Stephen C

いいえ、そのようなものが追加されることはほとんどありません。

オブジェクトのタイプをチェックし、さまざまなタイプに対してさまざまなことを行うことは、継承とオーバーライドされたメソッドの使用という、はるかに優れたパターンがすでにあるため、非常に洗練されていないと考えられます。 (確かに、これは「何かを行う」がメソッドコールchecked Objectとして定式化できる場合にのみ使用できますが、通常は可能です。)

6
Kilian Foth

Javaでは、プリミティブをオンにすることのみが可能です。しかし、あなたは同様のことをすることができます。
見つけられなかったのですが、それはある種のデザインパターンだと思います...

If-then-else要素(この場合は必要です)を非表示にして、一種のスイッチクラスでラップすることができます。

public abstract MySwitch{
    public MySwitch(MyObject obj){

        if (obj instanceof SecondObject)
            secondObject((SecondObject)obj);
        else if (obj instanceof ThirdObject)
            thirdObject((ThirdObject)obj);
        else
            onDefault(obj);
    }

    protected abstract void secondObject(SecondObject obj);
    protected abstract void thirdObject(ThirdObject obj);
    protected abstract void onDefault(MyObject obj);
}

コードで次のように使用できます。

new MySwitch(obj){

    @Override protected void secondObject(SecondObject obj){

    }
    @Override protected void thirdObject(ThirdObject obj){

    }
    @Override protected void onDefault(MyObject obj){

    }
};

通常、コンストラクターから抽象メソッドを呼び出すのは悪い考えです...この場合、これはステートレスなクラスであり、常にステートレスなクラスであるため、問題ない可能性があると思います。気に入らない場合は、追加のメソッドを追加してください。

これには、継承ツリーに新しいクラスを追加して新しいケースを追加する場合、すべてのオカレンスを更新しなければならず、どこかでスイッチを忘れないようにするという利点もあります。

2
wrm

私たちもこのようにできると思います:

public enum Switch {
  BED_ROOM, BATH_ROOM, LIVING_ROOM 
}

次に、インターフェースSwitchableを作成します。

public interface Switchable {
  public Switch getSwitch();
}

次に、スイッチタイプをconstrictorパラメータとして取るスイッチを実装します。

public BedRoom implements Switchable {

  private Switch switch;

  public BedRoom(Switch switch) {
    this.switch = switch;
  }  

  public Switch getSwitch() {
    return switch;
  }

  public int getBedCapacity() {
    return 2; // implement the way your wish
  }
}

次に、以下のように使用します:

Switchable switch = new BedRoom(Switch.BED_ROOM)
switch(switch.getSwitch()) {
  case BED_ROOM:
    BedRoom br = (BedRoom) switch;

    br.getBedCapacity(); // do anything with bedroom methods.
    break;
}
0
ameet