4
$\begingroup$

Students first see switch/case as a second syntax for if/else, but eventually they should see its strength in creating cascades. I know of the traditional example of printing a cumulative song, like "Twelve Days of Christmas", and below I've placed some code I use to illustrate the difference in use-case between if statements and switch cases. It nicely prints out all 108 lines of the song Alouette in 44 lines of code.

The only other motivating example I can think of is a graduated tax calculator (based on the US Federal Income Tax), where cascades can be a nice way to approach the problem.

These examples are fine, but it's not enough to get the lightbulb to flick on for most of my students. What I need is a few more motivating examples that really show students why switch/case is useful. What other examples show this clearly?

    System.out.println("Alouette, gentille alouette,\n"
            + "Alouette, je te plumerai.\n");

    for(int verse = 1; verse <= 8; verse++){
        if(verse == 1){
            System.out.println("Je te plumerai la tête.\nJe te plumerai la tête.");
        } else if (verse == 2){
            System.out.println("Je te plumerai le bec.\nJe te plumerai le bec.");
        } else if (verse == 3){
            System.out.println("Je te plumerai les yeux.\nJe te plumerai les yeux.");
        } else if (verse == 4){
            System.out.println("Je te plumerai le cou.\nJe te plumerai le cou.");
        } else if (verse == 5){
            System.out.println("Je te plumerai les ailes.\nJe te plumerai les ailes.");
        } else if (verse == 6){
            System.out.println("Je te plumerai les pattes.\nJe te plumerai les pattes.");
        } else if (verse == 7){
            System.out.println("Je te plumerai la queue.\nJe te plumerai la queue.");
        } else if (verse == 8){
            System.out.println("Je te plumerai le dos.\nJe te plumerai le dos.");
        }

        switch(verse){
            case 8:
                System.out.println("Et le dos! Et le dos!");
            case 7:
                System.out.println("Et la queue! Et la queue!");
            case 6:
                System.out.println("Et les pattes! Et les pattes!");
            case 5:
                System.out.println("Et les ailes! Et les ailes!");
            case 4:
                System.out.println("Et le cou! Et le cou!");
            case 3:
                System.out.println("Et les yeux! Et les yeux!");
            case 2:
                System.out.println("Et le bec! Et le bec!");
            case 1: 
                System.out.println("Et la tête! Et la tête!");
            default: 
                System.out.println("Alouette! Alouette!\n"
                        + "Ah-ah-ah-ah\n");
        }

        System.out.println("Alouette, gentille alouette,\n"
                + "Alouette, je te plumerai.\n");
$\endgroup$
6
  • $\begingroup$ urrm. why don't you have break; in each case? I thought it was necessary... $\endgroup$
    – ItamarG3
    Jun 12, 2017 at 15:04
  • $\begingroup$ The choice about where (and if) to use breaks is the great strength of the structure. If you put a break in every case, you will be back to an if/else structure. But if you skip some... try running the code =D $\endgroup$
    – Ben I.
    Jun 12, 2017 at 15:06
  • $\begingroup$ It will create chains, won't it? $\endgroup$
    – ItamarG3
    Jun 12, 2017 at 15:09
  • 1
    $\begingroup$ I could tell you, but that would spoil the surprise. (SPOILER ALERT: yes, it does.) $\endgroup$
    – Ben I.
    Jun 12, 2017 at 15:14
  • 1
    $\begingroup$ If the students have been introduced to the "clean code" concept, the switch/case construct is easier to read even if there's a break for each case. Makes it obvious that it is a multi-branch decision point. $\endgroup$ Jun 12, 2017 at 15:23

2 Answers 2

3
$\begingroup$

Consider why you might prefer to use a switch in a production environment:

  1. Fall through processing. The blackjack example given by @Peter is a good example of this because it mirrors how we think about the problem: all of the face cards fall into the same case & the code follows suit. Be careful with this though, it's easy to abuse this to get terser code at the expense of readability
  2. Default handlers. Personally, I find this most useful not in a 'single moment of code', but over a series of refinements. For instance, it's common to not have all your logic to handle multiple situations in place when you start coding, but it's also easy to miss a special case that you meant to add later. A default handler is a convenient & concise way to stub out code that you haven't yet gotten to. Default handlers for that throw an exceptions or log the event can help avoid silent failures & are less likely to get 'coded out' by an mistakenly adding or removing an if or else.
  3. Maintainability. I find it safer to add & remove code from a switch statement than an if / else if / else block as I'm more likely to accidentally omit an else from an else if than I am to omit a break from a switch.
  4. As per Joshua Bloch's Effective Java: "Switches on enums are good for augmenting external enums types with constant-specific behavior." For example if you cannot change the source for an Operation enum, but need add support for an inverse method:

    public static Operation inverse(Operation op){
       switch(op){
          case PLUS:   return Operation.MINUS;
          case MINUS:  return Operation.PLUS;
          case TIMES:  return Operation.DIVIDE;
          case DIVIDE: return Operation.TIMES;
       }
    }
    
  5. Readability. I find a moderately sized switch statement easier to grok than an equivalent if / else if / else block.

When possible, avoid contrived examples for motivating usage. Instead, show where you (or someone else) used a switch & discuss why it was preferable.

$\endgroup$
2
$\begingroup$

The example that instantly comes to mind is Blackjack. See the code excerpt below for an extended example in C (even though it's C, the syntax will pretty much hold true for Java as well):

int cardValue(char card)
{
    int aceValue = -1;

    if (card == 'A')
    {
        do
        {
            printf("What value would you like Ace to be?\n");
            printf("Type 1 or 11: ");
            aceValue = GetInt();
        } while (aceValue != 1 && aceValue != 11);
    }

    switch (card)
    {
        case '2':
            return 2;
        case '3':
            return 3;
        case '4':
            return 4;
        case '5':
            return 5;
        case '6':
            return 6;
        case '7':
            return 7;
        case '8':
            return 8;
        case '9':
            return 9;
        case 'T':
        case 'J':
        case 'Q':
        case 'K':
            return 10;
        case 'A':
            return aceValue;
        default:
            return -1;
    }
} 

This function takes in a character corresponding to the card's value. I used a single char as the data type for each card, so I needed a function that took in a card's name (i.e. letter) and returned its value. Were I to design this again, I would probably reduce it down to one return statement and have a variable represent something like cardValue (and in that case I'd need to use break statements, which I currently omit thanks to return). That said, the switch statement is a clear example where you don't want a bunch of if-else statements. For the face cards that would be redundant. There's a nice mix of those cases that "fall through" and those that don't.

Using card games where cards share the same value is a fun, but realistic example for students to see the value of a switch statement.

I'll share one other short example where students have to implement a command-line calculator in C and used switch to identify which operation to perform. Here's the switch from there:

    switch (argv[2][0]) {
        case 'x':
            printf("%f\n", numOne * numTwo);
            break;
        case '/':
            printf("%f\n", numOne / numTwo);
            break;
        case '+':
            printf("%f\n", numOne + numTwo);
            break;
        case '-':
            printf("%f\n", numOne - numTwo);
            break;
        case '%':
            mod = numOne - (numTwo * modNum);
            printf("%f\n", mod);
            break;
        default:
            printf("Usage: <operation> must be x, /, +, -, or %%\n");
            return 1;
    }

With this example the clarity of the code is made much stronger by virtue of the case statements for each operation. From a readability perspective, it makes it clear how the operations are separated out by showing so visually.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.