web-dev-qa-db-ja.com

switch文でinstanceof演算子を使用することは可能ですか?

instanceofオブジェクトにswitch caseを使用することについて質問があります。

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

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

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

231
olidev

これは、サブタイプ多型が役立つ典型的なシナリオです。以下をせよ

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

それならthisdo()を呼び出すだけです。

AB、およびCを自由に変更できない場合は、訪問者パターンを適用して同じことを実現できます。

203
jmg

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

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;

}
83
Nico

万が一誰かが読んだ場合に備えて:

Javaでの最適な解決策は次のとおりです。

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

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

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

    };

    abstract void doAction (...);
}

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

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

    void someFunction ( Action action ) {
        action.doAction(...);   
    }
    
  2. "d"という名前の新しいActionを追加する場合は、doAction(...)メソッドを実装する必要があります。

注:このパターンは、Joshua's Blochの "Effective Java(2nd Edition)"で説明されています。

34
se.solovyev

クラスがキーであり、機能性、つまりラムダなどが値であるMapを作成するだけです。

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

//もちろん、一度だけ初期化するためにこれをリファクタリングする

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

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

26
Novaterata

できません。 switchステートメントには、コンパイル時定数であり、整数に評価されるcaseステートメントのみを含めることができます(Java 6までおよびJava 7ではストリング)。

あなたが探しているものは、関数型プログラミングでは "パターンマッチング"と呼ばれています。

Javaではinstanceofを回避する もご覧ください。

17
Carlo V. Dango

一番上の答えで議論したように、伝統的なOOPアプローチはswitchの代わりに多態性を使うことです。このトリックのためによく文書化されたリファクタリングパターンさえあります: ConditionalからPolymorphismへの置き換え 。このアプローチに到達するたびに、デフォルトの動作を提供するために Null object も実装します。

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のケースクラス のようにマッチしたオブジェクトを分解する標準的な方法がありません。

16
Pavel

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

Aのクラスののみに基づく上記のアプローチに注意してください/、BC...:

ABと保証できない限り_ c _...(Baseのすべてのサブクラスまたは実装者)はfinalthenです。AB_ c _のサブクラス...は処理されません。

if、elseif、elseif ..のアプローチは、多数のサブクラス/実装者にとっては遅くなりますが、より正確です。

7
JohnK

いいえ、これを行う方法はありません。あなたがしたいと思うかもしれないけれどもこれらの種類の問題を扱う方法として多態性を考慮することです。

5

このようにswitch文を使用することは、オブジェクト指向の方法ではありません。代わりに多態性の力を使うべきです。単純に書く

this.do()

以前に基本クラスを設定したことがあります。

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

これはABおよびCの基本クラスです。

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

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

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

私は個人的には次の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

サンプルコードではStringを使用していますが、Classを含む任意のオブジェクトタイプを使用できます。例えば.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);
}
3
Feiteira

Byte、short、char、int、String、および列挙型(およびプリミティブのオブジェクトバージョン。これはJavaのバージョンにも依存します。Java7ではStringはswitchedにすることができます)でのみ動作することはできません

3
Tnem

もしあなたが共通のインターフェースを操作できるのであれば、enumを追加して各クラスに固有の値を返させることができます。 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;
  }

Impl:

...
  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ブロックは機能します。

3
Gaʀʀʏ

これはどう ?

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



public static <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

残念ながら、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文を使うことが可能です

MyEnumType 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
}
1
Murat Karagöz

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

しかし、あなたが他の変数に "switch"を適用する必要があるならば。私は別の解決策を提案するでしょう。以下のインタフェースを定義します。

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"ステートメントに対してコンパイル時エラーが発生するためです。

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

Switchステートメントを使用する理由があると思います。もしあなたがxTextが生成したコードを使っているのであれば、おそらく。または別の種類のEMF生成クラス。

instance.getClass().getName();

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

instance.getClass().getSimpleName();

単純な表現を返します。すなわち、EcoreUtil

1
Thomas Haarhoff

これは、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)
        );
    }
0
Viswanath

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

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";
    }
}
0
Ravi Sanwal

Eclipse Modeling Frameworkには、継承も考慮する興味深いアイデアがあります。基本的な概念はSwitch interface で定義されています。切り替えはdoSwitchメソッドを呼び出すことによって行われます。

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

public T caseXXXX(XXXX object);

methodを実装する必要があります(デフォルトの実装は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 の完全な実装を見ることができます。

0
Arcanefoam

クラス名でEnumを作成します。

public enum ClassNameEnum {
    A, B, C
}

オブジェクトのクラス名を見つけます。列挙型の上にswitch caseを書きます。

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

お役に立てれば。

0
Siva Kumar

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

いずれにせよ、彼らはスイッチでかっこいいことをしてきました。

0
A_Arnold