เป็นไปได้ไหมที่จะใช้ตัวดำเนินการ instanceof ในคำสั่ง switch?


267

ฉันมีคำถามเกี่ยวกับการใช้สวิตช์ตัวพิมพ์เล็กสำหรับinstanceofวัตถุ:

ตัวอย่างเช่นปัญหาของฉันสามารถทำซ้ำได้ใน Java:

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

มันจะใช้งานได้switch...caseอย่างไร?


6
หากคุณรู้สึกว่าคุณต้องการสวิตช์คุณสามารถแฮชชื่อคลาสเป็นอินทราสต์และใช้สิ่งนั้นได้ให้ระวังการปะทะที่อาจเกิดขึ้นได้ การเพิ่มเป็นความคิดเห็นมากกว่าคำตอบเพราะฉันไม่ชอบความคิดของสิ่งนี้ที่ถูกนำมาใช้จริง บางทีสิ่งที่คุณต้องการจริงๆคือรูปแบบของผู้เข้าชม
vickirk

1
ในฐานะของ java 7 คุณสามารถเปิดชื่อคลาสที่มีคุณสมบัติครบถ้วนเพื่อหลีกเลี่ยงการแฮชแฮชดังที่ @vickirk ชี้ให้เห็น แต่ก็ยังน่าเกลียดอยู่
Mitja

คำตอบ:


225

นี่เป็นสถานการณ์โดยทั่วไปที่ polymorphism ชนิดย่อยช่วยได้ ทำดังต่อไปนี้

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

หากคุณยังไม่สามารถเปลี่ยนA, BและCคุณสามารถใช้รูปแบบของผู้เข้าชมเพื่อให้บรรลุเดียวกัน


33
รูปแบบของผู้เข้าชมหมายความว่า A, B และ C ต้องใช้ส่วนต่อประสานที่มีวิธีการแบบนามธรรมที่นำผู้เข้าชมมาเป็นพารามิเตอร์ป้อนข้อมูลถ้าคุณไม่สามารถเปลี่ยน A, B, C และไม่มีสิ่งใดที่ใช้อินเทอร์เฟซนั้น
ร้อน

21
ความคิดเห็นล่าสุดเกี่ยวกับรูปแบบของผู้เข้าชมผิด คุณยังคงต้องทำให้ A, B และ C ใช้อินเทอร์เฟซ
Ben Thurley

10
น่าเสียดายที่นี่ใช้ไม่ได้ถ้า do () - Code ต้องการสภาพแวดล้อมของโฮสต์ (เช่นการเข้าถึงตัวแปรที่ไม่มีอยู่ใน do () ตัวมันเอง)
mafu

2
@mafu OP คำถามเกี่ยวกับการเลือกปฏิบัติตามประเภท หากเมธอด do () ของคุณต้องการอินพุตมากขึ้นเพื่อส่งปัญหาของคุณคือ IMHO นอกขอบเขตของคำถามที่กล่าวถึงที่นี่
jmg

3
คำตอบนี้อนุมานว่าคุณสามารถปรับเปลี่ยนคลาส A, B, C ในขณะที่ฉันคิดว่าประเด็นคือวิธีการที่ไม่มีการแก้ไข A, B, C เพราะพวกเขาอาจอยู่ในห้องสมุดส่วนที่สาม
cloudy_weather

96

หากคุณไม่สามารถโค้ดไปยังอินเทอร์เฟซได้คุณสามารถใช้ 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;

}

ขอบคุณฉันต้องทำการเปลี่ยนแปลงบางอย่าง: 1) เริ่มต้นแต่ละ enum id ด้วยการอ้างอิงระดับ; 2) ยืนยันชื่อแบบง่ายของคลาสด้วย enum id .toString (); 3) หา enum ผ่านการอ้างอิง Class ที่เก็บไว้ต่อ enum id ฉันคิดว่านี่เป็นสิ่งที่ทำให้งงงวยปลอดภัยแล้ว
กุมภ์

ถ้า this.getClass (). getSimpleName () ไม่ตรงกับค่าของ CLAZZ มันจะส่งข้อยกเว้น ... ดีกว่าที่จะล้อมรอบด้วยบล็อก catch ลองและข้อยกเว้นจะถือว่าเป็นตัวเลือก "เริ่มต้น" หรือ "อื่น" ของ สวิตช์
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());

// แน่นอนให้ทำการ refactor นี้เพื่อเริ่มต้นครั้งเดียวเท่านั้น

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

หากคุณต้องการตรวจสอบข้อยกเว้นมากกว่าใช้ FunctionInterface ที่ส่งข้อยกเว้นและใช้สิ่งนั้นแทน Runnable


3
ทางออกที่ดีที่สุดเป็นพิเศษเพราะช่วยให้ปรับสภาพได้ง่าย
Feiteira

2
ข้อเสียเพียงอย่างเดียวของวิธีนี้คือมันไม่สามารถใช้ตัวแปร local (method) ใน lambdas ได้ (สมมติว่าจำเป็นต้องใช้แน่นอน)
zapatero

1
@zapatero คุณสามารถเปลี่ยนเป็น Function แทน Runnable ส่งผ่านอินสแตนซ์ในรูปของพารามิเตอร์จากนั้นจึงทำการ cast เมื่อจำเป็น
4285

upvoted; นี้เป็นหนึ่งในไม่กี่คำตอบที่จริงจะช่วยให้ OP ทำในสิ่งที่เขาจะขอ (และใช่ก็มักจะเป็นไปได้ที่จะ refactor รหัสเพื่อไม่ต้องทำinstanceofและไม่มีสถานการณ์ของฉันเป็นที่น่าเสียดายไม่ได้เป็นหนึ่งในผู้ที่ได้อย่างง่ายดายพอดีเข้าไป กล่องนั้น ... )
ต่อ Lundberg

@ SergioGutiérrezขอบคุณ รูปแบบนี้ควรใช้กับไลบรารีภายนอกเท่านั้น และแม้กระทั่งจากนั้นคุณก็สามารถสร้างอินเทอร์เฟซด้วยการใช้อะแดปเตอร์แทน แต่มันมีประโยชน์ที่คุณต้องการ DIFF เชิงพฤติกรรมเพื่อที่จะพูดเพื่อให้ชัดเจนยิ่งขึ้น คล้ายกับการกำหนดเส้นทาง 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 (... )

หมายเหตุ: รูปแบบนี้ได้อธิบายไว้ใน Bloch ของ Joshua "Effective Java (2nd Edition)"


1
ดี! เป็น@Overrideต้องดังกล่าวข้างต้นในแต่ละการดำเนินการdoAction()?
mateuscb

9
นี่เป็นวิธี "ทางออกที่ดีที่สุด"? คุณจะตัดสินใจเลือกactionใช้อย่างไร โดยอินสแตนซ์ภายนอกของน้ำตกที่โทรsomeFunction()ด้วยถูกต้องactionหรือไม่ นี่เป็นเพียงการเพิ่มระดับของการอ้อมอีกระดับหนึ่ง
PureSpider

1
ไม่มันจะทำโดยอัตโนมัติเมื่อรันไทม์ หากคุณเรียกใช้ someFunction (Action.a) จะมีการเรียก a.doAction
se.solovyev

11
ฉันไม่เข้าใจสิ่งนี้ คุณจะรู้ได้อย่างไรว่า enum ที่จะใช้ ดังที่ @PureSpider กล่าวว่านี่เป็นงานอีกระดับที่ต้องทำ
James Manes

2
เป็นเรื่องน่าเศร้าอย่างยิ่งที่คุณไม่ได้เสนอตัวอย่างที่สมบูรณ์เช่นการแมปอินสแตนซ์คลาส a, b หรือ C กับ enum นี้อย่างไร ฉันจะลองใช้อินสแตนซ์กับ Enum นี้
Tom

21

คุณทำไม่ได้ switchคำสั่งสามารถมีcaseงบที่มีค่าคงที่เวลารวบรวมและประเมินผลการศึกษาเป็นจำนวนเต็ม (สูงสุด 6 Java และสตริงใน Java 7)

สิ่งที่คุณกำลังมองหาเรียกว่า "การจับคู่รูปแบบ" ในการเขียนโปรแกรมการทำงาน

ดูเพิ่มเติมการหลีกเลี่ยงอินสแตนซ์ใน Java


1
ไม่ได้ในภาษาที่ใช้งานได้ส่วนใหญ่คุณไม่สามารถจับคู่รูปแบบกับประเภทได้เฉพาะกับตัวสร้างเท่านั้น อย่างน้อยก็เป็นจริงใน ML และ Haskell ใน Scala และ OCaml เป็นไปได้ แต่ไม่ใช่การจับคู่รูปแบบทั่วไป
jmg

แน่นอน แต่การตรวจสอบกับตัวสร้างจะเป็น "เทียบเท่า" กับสถานการณ์ที่อธิบายไว้ข้างต้น
Carlo V. Dango

1
ในบางกรณี แต่ไม่ใช่โดยทั่วไป
jmg

สวิทช์ยังสามารถรองรับ enums
โซโลมอน Ucko

"คุณไม่สามารถ" ดูภาษาอื่นไม่ค่อยได้คำตอบที่เป็นประโยชน์
L. Blanc

17

ตามที่กล่าวไว้ในคำตอบยอดนิยมวิธี OOP แบบดั้งเดิมคือการใช้ polymorphism แทนสวิตช์ แม้จะมีเอกสารที่ดีรูปแบบ refactoring เคล็ดลับนี้: เปลี่ยนเงื่อนไขที่มีความแตกต่าง เมื่อใดก็ตามที่ฉันเข้าถึงวิธีนี้ฉันชอบที่จะใช้วัตถุ Nullเพื่อให้พฤติกรรมเริ่มต้น

เริ่มต้นด้วย Java 8 เราสามารถใช้ lambdas และ generics เพื่อให้บางสิ่งบางอย่างที่โปรแกรมเมอร์ผู้คุ้นเคยกับการทำงาน: การจับคู่รูปแบบ มันไม่ได้เป็นคุณสมบัติภาษาหลัก แต่ห้องสมุด Javaslangมีการใช้งานเพียงครั้งเดียว ตัวอย่างจากjavadoc :

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

นี่ไม่ใช่กระบวนทัศน์ที่เป็นธรรมชาติที่สุดในโลกของ Java ดังนั้นควรใช้ด้วยความระมัดระวัง ในขณะที่วิธีการทั่วไปที่จะช่วยให้คุณไม่ต้อง typecast ค่าที่ตรงกับที่เรากำลังขาดหายไปเป็นวิธีมาตรฐานในการย่อยสลายวัตถุจับคู่เช่นเดียวกับกาลาเรียนกรณีตัวอย่าง


9

ฉันรู้ว่ามันสายมาก แต่สำหรับผู้อ่านในอนาคต ...

ระวังวิธีการข้างต้นที่ใช้ชื่อคลาสA , B , C เท่านั้น :

หากคุณไม่สามารถรับประกันได้ว่าA , B , C ... (คลาสย่อยทั้งหมดหรือตัวดำเนินการของฐาน ) ถือเป็นที่สิ้นสุดดังนั้นคลาสย่อยของA , B , C ... จะไม่ได้รับการจัดการ

ถึงแม้ว่าif, elseif, elseif .. จะช้ากว่าสำหรับ subclasses / implementers จำนวนมาก แต่ก็มีความแม่นยำมากกว่า


แน่นอนคุณไม่ควรใช้ polymorphimsm (aka OOP)
Val

8

น่าเสียดายที่มันเป็นไปไม่ได้นอกกรอบเนื่องจากคำสั่ง switch-case คาดว่านิพจน์คงที่ เพื่อเอาชนะสิ่งนี้วิธีหนึ่งคือใช้ค่า enum กับชื่อคลาสเช่น

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

ด้วยที่เป็นไปได้ที่จะใช้คำสั่งเปลี่ยนเช่นนี้

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 จะถูกนำไปใช้อย่างเต็มที่นี่เป็นวิธีที่สะอาดที่สุดในการรับข้อความสั่งสวิตช์ตามประเภทโดยไม่ต้องแก้ไขคลาสเป้าหมาย
Rik Schaaf


5

การใช้คำสั่งสวิตช์เช่นนี้ไม่ใช่วิธีการวางวัตถุ คุณควรใช้พลังของพหุสัณฐานแทน เพียงแค่เขียน

this.do()

มีก่อนหน้านี้ตั้งค่าคลาสฐาน:

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

ซึ่งเป็นชั้นฐานสำหรับA, Bและ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() }
}

@jmg suggeests ( stackoverflow.com/questions/5579309/switch-instanceof/ … ) โดยใช้ส่วนต่อประสานแทนคลาสฐานนามธรรม ที่สามารถเหนือกว่าในบาง circusmtances
Raedwald

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 เช่นนี้จะทำงานเฉพาะถ้าระดับของการดำเนินการจะใช้ในการสลับ แต่มันจะไม่ทำงานสำหรับการเชื่อมต่อ / abstractclass / superclasses
lifesoordinary

ใช่นี่ไม่ใช่
ไมค์

สำหรับความพยายาม แต่นอกเหนือจากความคิดเห็นของ @ lifesoordinary คุณยังพลาดความปลอดภัยที่คุณมีตามปกติเพราะคำตอบนี้ใช้สตริงที่ฮาร์ดโค้ดแทนที่จะอ้างอิงคลาส มันค่อนข้างง่ายที่จะทำให้การพิมพ์ผิดโดยเฉพาะอย่างยิ่งถ้าคุณจะต้องขยายการทำงานนี้กับมาตรฐานชื่อเต็มถ้านี่คือการทับซ้อนใด ๆ ในชื่อชั้นกับ names.Edit แพคเกจที่แตกต่างกัน: typo คงที่ (ซึ่งค่อนข้างพิสูจน์จุดของฉัน)
Rik Schaaf

4

คุณไม่สามารถสลับใช้งานได้เฉพาะกับไบต์, สั้น, ถ่าน, int, สตริงและประเภทที่ระบุ (และรุ่นวัตถุของดั้งเดิมก็ยังขึ้นอยู่กับรุ่นจาวาของคุณ Strings สามารถแก้ไขได้switchใน java 7)


คุณไม่สามารถเปิด Strings ใน Java 6 และคุณไม่สามารถเปิด "รุ่นพื้นฐานของวัตถุ"
Lukas Eder

@Bozho ฉันบอกว่ามันขึ้นอยู่กับรุ่นจาวาของคุณใน Java 7 คุณสามารถเปิด Strings
Tnem

@Lukas Eder ตรวจสอบจาวาสเปคของคุณที่คุณสามารถทำได้
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

โค้ดตัวอย่างใช้ Strings แต่คุณสามารถใช้ชนิดวัตถุใดก็ได้รวมถึง 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);
}

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

หากคุณสามารถจัดการกับอินเตอร์เฟสทั่วไปคุณสามารถเพิ่มใน enum และให้แต่ละคลาสส่งคืนค่าที่ไม่ซ้ำกัน คุณไม่ต้องการอินสแตนซ์หรือรูปแบบผู้เยี่ยมชม

สำหรับฉันแล้วตรรกะจำเป็นต้องอยู่ในคำสั่ง switch ในการเขียนไม่ใช่วัตถุเอง นี่คือทางออกของฉัน:

ClassA, ClassB, and ClassC implement CommonClass

อินเตอร์เฟซ:

public interface CommonClass {
   MyEnum getEnumType();
}

enum:

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 และบล็อกตัวสลับ 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

พักเมื่อมันมาถึงการบีบอัดรหัส (→ ProGuard)
Matthias Ronge

1

ฉันคิดว่ามีเหตุผลที่จะใช้คำสั่งเปลี่ยน หากคุณใช้รหัส xText ที่สร้างขึ้นอาจจะ หรือคลาสอื่นที่สร้างคลาส EMF

instance.getClass().getName();

ส่งคืนสตริงของชื่อการใช้งานคลาส ie: org.eclipse.emf.ecore.util.EcoreUtil

instance.getClass().getSimpleName();

ส่งคืนการพิมพ์ซ้ำอย่างง่ายเช่น: EcoreUtil


คุณไม่สามารถใช้งานได้switchตามcaseเงื่อนไขเนื่องจากไม่ใช่ค่าคงที่
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คุณสามารถใช้ enum และรับประกันได้อย่างมีเอกลักษณ์ (ตามที่ทำในคำตอบนี้ ) อย่างไรก็ตามด้วยวิธีการใด ๆ ที่คุณจะต้อง refactor ในสองสถานที่เมื่อคุณเปลี่ยนชื่อ
user905686

@ user905686 เปลี่ยนชื่อของอะไร ในตัวอย่างปัจจุบันประเภท "A" ถูกกำหนดไว้ในคลาส Something เพื่อลดปริมาณของรหัส แต่ในชีวิตจริงคุณควรกำหนดไว้อย่างชัดเจนภายนอก (ในสถานที่ทั่วไป) และไม่มีปัญหาใด ๆ กับการปรับโครงสร้างเพิ่มเติม
Sergey Krivenkov

ฉันหมายถึงการเปลี่ยนชื่อคลาส A. การเปลี่ยนโครงสร้างอัตโนมัติอาจไม่รวมตัวแปรTYPE = "A"เมื่อเปลี่ยนชื่อ โดยเฉพาะอย่างยิ่งถ้ามันอยู่นอกคลาสที่เกี่ยวข้องเราอาจลืมมันได้ด้วยตนเอง IntelliJ จริง ๆ แล้วยังพบการเกิดขึ้นของชื่อคลาสในสายอักขระหรือความคิดเห็น แต่นั่นเป็นเพียงการค้นหาข้อความ (แทนที่จะดูที่โครงสร้างไวยากรณ์) และรวมถึงผลบวกที่ผิดพลาด
user905686

@ user905686 เป็นเพียงตัวอย่างเพื่อแสดงความคิด อย่าใช้ String สำหรับคำจำกัดความประเภทในโครงการจริงประกาศเจ้าของคลาส MyTypes บางส่วนที่มีค่าคงที่จำนวนเต็ม (หรือ enum) และใช้ในคลาสที่ใช้ ClassTypeInterface
Sergey Krivenkov

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 แต่มันก็เป็นไปได้ที่จะแยกออกไปประมวลผลเฉพาะสำหรับแต่ละประเภทที่กำหนด วิธีหนึ่งในการทำเช่นนี้คือการใช้กลไกการจัดส่งแบบคู่มาตรฐาน ตัวอย่างที่เราต้องการ "สลับ" ตามชนิดคือ mapper การยกเว้นของ Jersey ที่เราต้องการแมปข้อยกเว้นมากมายกับการตอบสนองข้อผิดพลาด ในขณะที่กรณีเฉพาะนี้อาจเป็นวิธีที่ดีกว่า (เช่นการใช้วิธี polymorphic ที่แปลแต่ละข้อยกเว้นเพื่อตอบสนองข้อผิดพลาด) การใช้กลไกการจัดส่งคู่ยังคงมีประโยชน์และใช้งานได้จริง

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

ดูเหมือนว่ารูปแบบของผู้เข้าชมซึ่งถูกกล่าวถึงในคำตอบนี้แล้ว: stackoverflow.com/a/5579385
typeracer

0

สร้างEnumด้วยชื่อคลาส

public enum ClassNameEnum {
    A, B, C
}

ค้นหาชื่อคลาสของวัตถุ เขียนเคสสวิตช์บน enum

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
ในทางตรงกันข้ามกับวิธีการนี้ที่การมีเพศสัมพันธ์ระหว่างค่าคงที่ enum และคลาสจะทำอย่างชัดเจนคุณจะมีเพศสัมพันธ์โดยนัยโดยชื่อชั้น สิ่งนี้จะทำลายรหัสของคุณเมื่อคุณเปลี่ยนชื่อค่าคงที่ enum หรือคลาสอย่างใดอย่างหนึ่งเท่านั้นในขณะที่วิธีอื่นยังคงใช้ได้
user905686

0

Eclipse Modeling Framework มีแนวคิดที่น่าสนใจที่พิจารณาการสืบทอด แนวคิดพื้นฐานถูกกำหนดไว้ในส่วนต่อประสาน Switch : การสลับทำโดยเรียกใช้doSwitchเมธอด

สิ่งที่น่าสนใจจริงๆคือการนำไปใช้ สำหรับความสนใจแต่ละประเภทก

public T caseXXXX(XXXX object);

วิธีการจะต้องดำเนินการ การประยุกต์ใช้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;

กรอบงานที่แท้จริงใช้จำนวนเต็มสำหรับแต่ละชั้นเรียนดังนั้นตรรกะจึงเป็นสวิตช์บริสุทธิ์:

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

มีวิธีที่ง่ายกว่าในการเลียนแบบโครงสร้างสวิตช์ที่ใช้อินสแตนซ์ของคุณทำสิ่งนี้โดยการสร้างบล็อคโค้ดในวิธีการของคุณและตั้งชื่อมันด้วยฉลาก จากนั้นคุณใช้ถ้าโครงสร้างเลียนแบบคำสั่ง 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;
            }
        }

สิ่งนี้ดีกว่ารหัสif... ที่else ifกำหนดโดย OP อย่างไร
typeracer

สิ่งที่คุณเสนอคือการแทนที่if... else ifด้วยคำสั่ง "goto" ซึ่งเป็นวิธีที่ผิดในการปรับใช้การควบคุมในภาษาเช่น Java
typeracer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.