switchステートメントでinstanceof演算子を使用することは可能ですか?


267

instanceofオブジェクトにスイッチケースを使用することについて質問があります。

たとえば、私の問題はJavaで再現できます。

if(this instanceof A)
    doA();
else if(this instanceof B)
    doB();
else if(this instanceof C)
    doC():

それを使用してどのように実装されswitch...caseますか?


6
本当にスイッチが必要だと思うなら、クラス名をintにハッシュしてそれを使うことができますが、衝突の可能性に注意してください。私はこれが実際に使用されているという考えが好きではないので、回答ではなくコメントとして追加します。多分あなたが本当に必要なのはビジターパターンでしょう。
vickirk

1
Java 7以降では、@ vickirkが指摘したようなハッシュの衝突を回避するために完全修飾クラス名に切り替えることもできましたが、それでも醜いです。
Mitja、2014年

回答:


225

これは、サブタイプのポリモーフィズムが役立つ典型的なシナリオです。以下をせよ

interface I {
  void do();
}

class A implements I { void do() { doA() } ... }
class B implements I { void do() { doB() } ... }
class C implements I { void do() { doC() } ... }

そして、あなたは簡単に呼び出すことができますdo()this

あなたは自由に変更できない場合はAB、そしてC、あなたは同じことを達成するためにビジターパターンを適用することができます。


33
Visitorパターンは、A、B、およびCが入力パラメーターとしてVisitorを受け取る抽象メソッドでインターフェースを実装する必要があることを意味します。A、B、Cを変更できず、どれもそのインターフェースを実装しない場合はどうなりますか?
thermz 2014

21
ビジターパターンに関する最後のコメントは間違っています。A、B、Cにインターフェースを実装させる必要があります。
Ben Thurley、2015年

10
悲しいことに、do()コードがホストの環境(つまり、do()自体に存在しない変数へのアクセス)を必要とする場合、これは機能しません。
mafu

2
@mafu OPの質問は、型ベースのディスパッチングに関するものでした。do()メソッドがディスパッチするために問題よりも多くの入力を必要とする場合、ここで説明する質問の範囲外の問題です。
jmg

3
この答えは、私はポイントは、彼らが第三部ライブラリであるかもしれないので、修正A、B、Cなしていることを行う方法であると考えているときに、クラスA、B、Cを変更することができることを前提として
cloudy_weather

96

絶対にインターフェースにコーディングできない場合は、列挙型を仲介として使用できます。

public A() {

    CLAZZ z = CLAZZ.valueOf(this.getClass().getSimpleName());
    switch (z) {
    case A:
        doA();
        break;
    case B:
        doB();
        break;
    case C:
        doC();
        break;
    }
}


enum CLAZZ {
    A,B,C;

}

thx、私もいくつかの変更を加える必要がありました:1)Class参照で各列挙IDを初期化します。2)enum id .toString();を使用してクラスの単純名をアサートします。3)列挙IDごとに保存されたクラス参照を通じて列挙を見つけます。この場合も難読化は安全だと思います。
アクエリアスパワー

this.getClass()。getSimpleName()がCLAZZの値と一致しない場合は、例外がスローされます... try catchブロックで囲むことをお勧めします。例外は、「デフォルト」または「else」オプションとして扱われます。スイッチ
tetri

40

クラスをキーにして、ラムダなどの機能が値となるマップを作成するだけです。

Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());

//もちろん、これをリファクタリングして1回だけ初期化する

doByClass.get(getClass()).run();

例外をチェックする必要がある場合は、例外をスローするFunctionalInterfaceを実装し、Runnableの代わりにそれを使用します。


3
特に、簡単にリファクタリングできるため、最善の解決策です。
フェイテイラ2017年

2
このソリューションの唯一の欠点は、ラムダでローカル(メソッド)変数を使用できないことです(もちろん、それが必要であると想定)。
zapatero

1
@zapatero RunnableではなくFunctionに変更し、インスタンスをパラメーターとして渡し、必要に応じてキャストすることができます
Novaterata

賛成。これは、OPが彼が求めていることを実際に実行するのに役立つ数少ない回答の1つです(そして、はい、多くの場合、コードをリファクタリングする必要がないようにすることが可能ですinstanceof。そのボックス...)
Lundbergによる

@SergioGutiérrezありがとう。まあ、このパターンは外部ライブラリでのみ必要です。その場合でも、代わりにアダプター実装を使用してインターフェースを作成できますが、いわば、振る舞いのDIFFをもっと明確にしたい場合に役立ちます。流暢vs注釈APIルーティングに似ていると思います。
Novaterata

36

誰かがそれを読む場合に備えて:

Javaでの最良のソリューションは次のとおりです。

public enum Action { 
    a{
        void doAction(...){
            // some code
        }

    }, 
    b{
        void doAction(...){
            // some code
        }

    }, 
    c{
        void doAction(...){
            // some code
        }

    };

    abstract void doAction (...);
}

このようなパターンの大きな利点は次のとおりです。

  1. あなたはそれを好きです(スイッチはまったくありません):

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
    
  2. 「d」という新しいアクションを追加する場合は、doAction(...)メソッドを実装する必要があります。

注:このパターンは、ジョシュアのブロック「Effective Java(2nd Edition)」で説明されています


1
いいね!の@Override各実装の上で必要doAction()ですか?
mateuscb 14

9
これは「BEST」ソリューションですか?どちらactionを使用するかをどのように決定しますか?someFunction()正しいで呼び出す外側のinstanceof-cascadeによってaction?これにより、別のレベルの間接参照が追加されます。
PureSpider 2016年

1
いいえ、実行時に自動的に行われます。someFunction(Action.a)を呼び出すと、a.doActionが呼び出されます。
se.solovyev 2016年

11
分かりません。使用する列挙型をどのようにして知っていますか?@PureSpiderが言ったように、これはやるべき仕事の別のレベルのようです。
James Manes、2016年

2
完全な例を提供していなかったこと非常に悲しいことです。たとえば、a、b、またはCのクラスインスタンスをこの列挙型にどのようにマッピングするかなどです。インスタンスをこの列挙型にキャストしてみます。
トム

21

できません。switch声明は含めることができcase、コンパイル時定数であり、整数(Javaの6までとJava 7の文字列)を評価する発言を。

関数型プログラミングでは、探しているものを「パターンマッチング」と呼びます。

Javaでのinstanceofの回避も参照してください。


1
いいえ、ほとんどの関数型言語では、型に対してパターンマッチングを行うことはできません。コンストラクターに対してのみです。これは、MLとHaskellでは少なくとも当てはまります。ScalaとOCamlでは、パターンマッチングを行うことは可能ですが、通常のアプリケーションではできません。
jmg

確かですが、コンストラクターに対するチェックは、上記のシナリオと「同等」です。
カルロV.ダンゴ

1
場合によっては、一般的ではありません。
jmg

スイッチは列挙型もサポートできます。
ソロモンウッコ2016

「できない」別の言語を見ても、役に立つ回答になることはめったにありません。
L.ブラン

17

上位の回答で説明したように、従来のOOPアプローチは、スイッチの代わりにポリモーフィズムを使用することです。このトリックには、十分に文書化されたリファクタリングパターンもあります。条件付きをポリモーフィズムに置き換えます。このアプローチに到達するときはいつでも、デフォルトの動作を提供するためにNullオブジェクトも実装するのが好きです。

Java 8以降では、ラムダとジェネリックを使用して、関数型プログラマーが非常に精通しているもの、つまりパターンマッチングを使用できるようになりました。これはコア言語機能ではありませんが、Javaslangライブラリは1つの実装を提供します。javadocの例:

Match.ofType(Number.class)
    .caze((Integer i) -> i)
    .caze((String s) -> new BigDecimal(s))
    .orElse(() -> -1)
    .apply(1.0d); // result: -1

これはJavaの世界で最も自然なパラダイムではないので、注意して使用してください。ジェネリックメソッドを使用すると、一致した値を型キャストする必要がなくなりますが、たとえば、Scalaのcaseクラスのように、一致したオブジェクトを分解する標準的な方法がありません。


9

私はこれが非常に遅いことを知っていますが、将来の読者のために...

ABC ...のクラスの名前のみに基づく上記のアプローチに注意してください。

あなたがいることを保証することができない限り、ABC ...(のすべてのサブクラスまたは実装ベースで)している最終の後、サブクラスABC ...に扱われることはありません。

にもかかわらず場合、ELSEIF、ELSEIF ... アプローチは、より正確で、サブクラス/実装者の数が多いために遅くなります。


確かに、多態性(別名OOP)を使用しないでください
Val

8

残念ながら、switch-caseステートメントは定数式を想定しているため、そのままでは不可能です。これを克服するための1つの方法は、クラス名で列挙値を使用することです。

public enum MyEnum {
   A(A.class.getName()), 
   B(B.class.getName()),
   C(C.class.getName());

private String refClassname;
private static final Map<String, MyEnum> ENUM_MAP;

MyEnum (String refClassname) {
    this.refClassname = refClassname;
}

static {
    Map<String, MyEnum> map = new ConcurrentHashMap<String, MyEnum>();
    for (MyEnum instance : MyEnum.values()) {
        map.put(instance.refClassname, instance);
    }
    ENUM_MAP = Collections.unmodifiableMap(map);
}

public static MyEnum get(String name) {
    return ENUM_MAP.get(name);
 }
}

それで、このようなswitch文を使用することが可能です

MyEnum type = MyEnum.get(clazz.getName());
switch (type) {
case A:
    ... // it's A class
case B:
    ... // it's B class
case C:
    ... // it's C class
}

JEP課題8213076が完全に実装されるまで、これは、ターゲットクラスを変更せずに、タイプに基づいてswitchステートメントを取得する最もクリーンな方法だと思います。
Rik Schaaf


5

このようなswitchステートメントの使用は、オブジェクト指向の方法ではありません。代わりに、ポリモーフィズムの力を使用する必要があります。単に書く

this.do()

以前に基本クラスを設定したことがある:

abstract class Base {
   abstract void do();
   ...
}

これはの基本クラスでAありBC

class A extends Base {
    void do() { this.doA() }
}

class B extends Base {
    void do() { this.doB() }
}

class C extends Base {
    void do() { this.doC() }
}

@jmg は、抽象基本クラスの代わりにインターフェースを使用してsuggeests(stackoverflow.com/questions/5579309/switch-instanceof/…)を提案します。それはいくつかの状況で優れていることができます。
Raedwald、2011

5

これはより速く動作し、ケース
-比較的多くの「ケース」がある
-でパフォーマンスを重視するプロセスが実行されます

public <T> T process(Object model) {
    switch (model.getClass().getSimpleName()) {
        case "Trade":
            return processTrade();
        case "InsuranceTransaction":
            return processInsuranceTransaction();
        case "CashTransaction":
            return processCashTransaction();
        case "CardTransaction":
            return processCardTransaction();
        case "TransferTransaction":
            return processTransferTransaction();
        case "ClientAccount":
            return processAccount();
        ...
        default:
            throw new IllegalArgumentException(model.getClass().getSimpleName());
    }
}

1
これは、instanceofを実行するのと同じではありません。これは、実装クラスを使用して切り替えた場合にのみ機能しますが、インターフェース/抽象クラス/スーパークラスでは機能しません
lifesoordinary

ええ、これはそうではありません
マイク

努力は必要ですが、@ lifesoordinaryのコメントは別として、この回答ではクラス参照の代わりにハードコードされた文字列を使用するため、通常持っているタイプセーフも見逃してしまいます。それはあなたがここに異なるパッケージnames.Editとクラス名のいずれかの重複した場合は、完全な正規名で、この機能を拡張する必要があるだろう場合は特に、タイプミスをするために非常に簡単です:(ちょっと私のポイントを証明している)固定タイプミスが
のRikシャーフ

4

スイッチは、byte、short、char、int、String、列挙型(およびプリミティブのオブジェクトバージョン、Javaバージョンにも依存し、StringはswitchJava 7で編集可能)でのみ機能しません。


Java 6では文字列をオンにすることはできません。また、「プリミティブのオブジェクトバージョン」をオンにすることもできません。
Lukas Eder、

@BozhoそれはあなたのJavaバージョンに依存すると私は言った、Java 7では文字列をオンにすることができます。
Tnem

@Lukas Ederが可能なJava仕様を確認してください
Tnem

4

私は次のJava 1.8コードを個人的に気に入っています。

    mySwitch("YY")
            .myCase("AA", (o) -> {
                System.out.println(o+"aa");
            })
            .myCase("BB", (o) -> {
                System.out.println(o+"bb");
            })
            .myCase("YY", (o) -> {
                System.out.println(o+"yy");
            })
            .myCase("ZZ", (o) -> {
                System.out.println(o+"zz");
            });

出力されます:

YYyy

サンプルコードは文字列を使用しますが、クラスを含む任意のオブジェクトタイプを使用できます。例えば.myCase(this.getClass(), (o) -> ...

次のスニペットが必要です:

public Case mySwitch(Object reference) {
    return new Case(reference);
}

public class Case {

    private Object reference;

    public Case(Object reference) {
        this.reference = reference;
    }

    public Case myCase(Object b, OnMatchDo task) {
        if (reference.equals(b)) {
            task.task(reference);
        }
        return this;
    }
}

public interface OnMatchDo {

    public void task(Object o);
}

4

Javaでは、OPの方法で切り替えることができるようになりました。彼らはそれをスイッチのパターンマッチングと呼んでいます。それは現在ドラフトの下にありますが、彼らが最近スイッチに入れている仕事量を見て、私はそれが通り抜けると思います。JEPでの例は次のとおりです。

String formatted;
switch (obj) {
    case Integer i: formatted = String.format("int %d", i); break;
    case Byte b:    formatted = String.format("byte %d", b); break;
    case Long l:    formatted = String.format("long %d", l); break;
    case Double d:  formatted = String.format("double %f", d); break;
    case String s:  formatted = String.format("String %s", s); break
    default:        formatted = obj.toString();
}  

またはラムダ構文を使用して値を返す

String formatted = 
    switch (obj) {
        case Integer i -> String.format("int %d", i)
        case Byte b    -> String.format("byte %d", b);
        case Long l    -> String.format("long %d", l); 
        case Double d  -> String.format("double %f", d); 
        case String s  -> String.format("String %s", s); 
        default        -> obj.toString();
    };

どちらにせよ、彼らはスイッチを使ってクールなことをやっています。


3

共通インターフェースを操作できる場合は、列挙型を追加して、各クラスに一意の値を返すようにすることができます。instanceofやビジターパターンは必要ありません。

私にとって、ロジックは、オブジェクト自体ではなく、switchステートメントで記述する必要がありました。これは私の解決策でした:

ClassA, ClassB, and ClassC implement CommonClass

インターフェース:

public interface CommonClass {
   MyEnum getEnumType();
}

列挙型:

public enum MyEnum {
  ClassA(0), ClassB(1), ClassC(2);

  private int value;

  private MyEnum(final int value) {
    this.value = value;
  }

  public int getValue() {
    return value;
  }

インプリ:

...
  switch(obj.getEnumType())
  {
    case MyEnum.ClassA:
      ClassA classA = (ClassA) obj;
    break;

    case MyEnum.ClassB:
      ClassB classB = (ClassB) obj;
    break;

    case MyEnum.ClassC:
      ClassC classC = (ClassC) obj;
    break;
  }
...

Java 7を使用している場合は、enumに文字列値を置くことができ、switch caseブロックは引き続き機能します。


valueあなたが唯一のenum定数を区別したい場合、このフィールドは冗長である-あなたは(あなたがそうであるように)直接定数を使用することができます。
user905686

2

これはどう ?

switch (this.name) 
{
  case "A":
    doA();
    break;
  case "B":
    doB();
    break;
  case "C":
    doC();
    break;
  default:
    console.log('Undefined instance');
}

3
これはJava 7でのみ機能することを指摘する必要があります。またthis.getSimpleName()、投稿者がJSと混同されているかどうかを確認する必要があります(そう、彼はコンソールを使用しています)。
pablisco

5
これは、ソースコードの参照透過性から外れるという問題があります。つまり、IDEは参照整合性を維持できません。名前を変更したいとします。反射は悪です。
Val

1
素晴らしいアイデアではありません。複数のクラスローダーがある場合、クラス名は一意ではありません。
Doradus、2015年

コード圧縮(→ProGuard)に関しては壊れる
Matthias Ronge

1

switch文を使用する理由はあると思います。xTextで生成されたコードを使用している場合。または別の種類のEMF生成クラス。

instance.getClass().getName();

クラス実装名の文字列を返します。例:org.eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

簡単な表現を返します:EcoreUtil


定数ではないためswitchcase条件として使用できません
B-GangsteR

1

「this」オブジェクトのクラスタイプを介して「切り替える」必要がある場合、この答えが最適ですhttps://stackoverflow.com/a/5579385/2078368

ただし、他の変数に「スイッチ」を適用する必要がある場合。私は別の解決策を提案します。次のインターフェイスを定義します。

public interface ClassTypeInterface {
    public String getType();
}

このスイッチを、「切り替え」たいすべてのクラスに実装します。例:

public class A extends Something implements ClassTypeInterface {

    public final static String TYPE = "A";

    @Override
    public String getType() {
        return TYPE;
    }
}

その後、次のように使用できます。

switch (var.getType()) {
    case A.TYPE: {
        break;
    }
    case B.TYPE: {
        break;
    }
    ...
}

注意すべき唯一のことは、ClassTypeInterfaceを実装するすべてのクラスにわたって「型」を一意に保つことです。交差が発生すると、「switch-case」ステートメントのコンパイル時エラーが発生するため、これは大きな問題ではありません。


にStringを使用する代わりにTYPE、列挙型を使用でき、一意性が保証されます(この回答で行ったように)。ただし、どの方法でも、名前を変更するときに2か所でリファクタリングする必要があります。
user905686

@ user905686何の名前を変更しますか?現在の例では、タイプ「A」はコードの量を最小限にするためにSomethingクラス内で定義されています。しかし実際には、明らかにそれを(一般的な場所で)外部で定義する必要があり、それ以上のリファクタリングに問題はありません。
セルゲイクリベンコフ2018

クラスAの名前を変更することを意味します。自動リファクタリングには、TYPE = "A"名前の変更時に変数が含まれない場合があります。特に、対応するクラスの外にある場合は、手動で行うときに忘れることもあります。IntelliJは、実際にはクラス名の出現を文字列またはコメントでも検出しますが、これは単なるテキスト検索であり(構文ツリーを調べるのではなく)、したがって誤検出が含まれます。
user905686 2018

@ user905686これは、アイデアを視覚化するための単なる例です。実際のプロジェクトのタイプ定義にはStringを使用せず、整数定数(または列挙型)を使用してMyTypesクラスホルダーを宣言し、ClassTypeInterfaceを実装するクラスでそれらを使用します。
セルゲイクリベンコフ2018

1

これは、Java 8でhttp://www.vavr.io/を使用して機能を実現する方法です

import static io.vavr.API.*;
import static io.vavr.Predicates.instanceOf;
public Throwable liftRootCause(final Throwable throwable) {
        return Match(throwable).of(
                Case($(instanceOf(CompletionException.class)), Throwable::getCause),
                Case($(instanceOf(ExecutionException.class)), Throwable::getCause),
                Case($(), th -> th)
        );
    }

1

switch文を書くことはできませんが、特定のタイプごとに特定の処理に分岐することは可能です。これを行う1つの方法は、標準の二重ディスパッチメカニズムを使用することです。タイプに基づいて「切り替え」たい例は、多数の例外をエラー応答にマップする必要があるジャージー例外マッパーです。この特定のケースではおそらくより良い方法があります(つまり、各例外をエラー応答に変換する多態的なメソッドを使用する)が、ダブルディスパッチメカニズムを使用することは依然として有用で実用的です。

interface Processable {
    <R> R process(final Processor<R> processor);
}

interface Processor<R> {
    R process(final A a);
    R process(final B b);
    R process(final C c);
    // for each type of Processable
    ...
}

class A implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

class B implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

class C implements Processable {
    // other class logic here

    <R> R process(final Processor<R> processor){
        return processor.process(this);
    }
}

次に、「スイッチ」が必要な場合は、次のように実行できます。

public class LogProcessor implements Processor<String> {
    private static final Logger log = Logger.for(LogProcessor.class);

    public void logIt(final Processable base) {
        log.info("Logging for type {}", process(base));
    }

    // Processor methods, these are basically the effective "case" statements
    String process(final A a) {
        return "Stringifying A";
    }

    String process(final B b) {
        return "Stringifying B";
    }

    String process(final C c) {
        return "Stringifying C";
    }
}

これは、すでにこの回答で説明しましたVisitorパターン、よく似ています:stackoverflow.com/a/5579385を
typeracer

0

クラス名を持つ列挙型を作成します。

public enum ClassNameEnum {
    A, B, C
}

オブジェクトのクラス名を見つけます。ライトスイッチ列挙型の上にケースを。

private void switchByClassType(Object obj) {

        ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());

        switch (className) {
            case A:
                doA();
                break;
            case B:
                doB();
                break;
            case C:
                doC();
                break;
        }
    }
}

お役に立てれば。


2
列挙定数とクラスの間の結合が明示的に行われるこのアプローチとは対照的に、クラス名によって暗黙的に結合を行います。これにより、列挙定数またはクラスのいずれか1つのみの名前を変更するとコードが壊れますが、他のアプローチは引き続き機能します。
user905686

0

Eclipseモデリングフレームワークには、継承も考慮した興味深いアイデアがあります。基本的な概念は、Switch インターフェースで定義されています。切り替えは、doSwitchメソッドを呼び出すことによって行われます。

本当に興味深いのは実装です。関心のあるタイプごとに、

public T caseXXXX(XXXX object);

メソッドを実装する必要があります(デフォルトの実装ではnullを返します)。doSwitchの実装では、アル呼び出しを試みますcaseXXXのすべてのタイプの階層のオブジェクトのメソッドを。次のようなもの:

BaseType baseType = (BaseType)object;
T result = caseBaseType(eAttribute);
if (result == null) result = caseSuperType1(baseType);
if (result == null) result = caseSuperType2(baseType);
if (result == null) result = caseSuperType3(baseType);
if (result == null) result = caseSuperType4(baseType);
if (result == null) result = defaultCase(object);
return result;

実際のフレームワークは各クラスに整数IDを使用するため、ロジックは実際には純粋なスイッチです。

public T doSwitch(Object object) {
    return doSwitch(object.class(), eObject);
}

protected T doSwitch(Class clazz, Object object) {
    return doSwitch(getClassifierID(clazz), object);
}

protected T doSwitch(int classifierID, Object theObject) {
    switch (classifierID) {
    case MyClasses.BASETYPE:
    {
      BaseType baseType = (BaseType)object;
      ...
      return result;
    }
    case MyClasses.TYPE1:
    {
      ...
    }
  ...

ECoreSwitchの完全な実装を見て、より良いアイデアを得ることができます。


-1

instanceofを使用するスイッチ構造をエミュレートするさらに簡単な方法があります。これを行うには、メソッドでコードブロックを作成し、ラベルに名前を付けます。次に、if構造を使用して、caseステートメントをエミュレートします。ケースが当てはまる場合は、ブレークLABEL_NAMEを使用して、その場しのぎのスイッチ構造から抜け出します。

        DEFINE_TYPE:
        {
            if (a instanceof x){
                //do something
                break DEFINE_TYPE;
            }
            if (a instanceof y){
               //do something
                break DEFINE_TYPE;
            }
            if (a instanceof z){
                // do something
                break DEFINE_TYPE;
            }
        }

これは、OPによって提供されるif... else ifコードよりも優れていますか?
タイプレーサー

私の以前のコメントを詳しく説明します:あなたが提案しているのは、基本的に「goto」ステートメントで置き換えることifです。else ifこれは、Javaなどの言語で制御フローを実装するための間違った方法です。
タイプレーサー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.