3

Is it possible in Java to have a switch-case statement using an object instead of primitive types?

I have a scenario where I have lots of 2d positions (x,y) and I want each of them to behave differently once they're triggered.

So for example, I would like:

Pos pos = getNextPos();

switch (pos) {
    case new Pos(1, 2):
        // do something
        break;
    case new Pos(9, 7):
        // do something else...
        break;
    etc...
}

or perhaps

Pos pos = getNextPos();
Pos[] listOfPos = getListOfPos();

switch (pos) {
    case listOfPos[0]:
        // do something
        break;
    case listOfPos[1]:
        // do something else...
        break;
    etc...
}

I also implemented the .equals() method in my Pos class to return true only if both x and y are equal to the other object.

The Pos class (with auto generated equals and hashCode):

public class Pos {
    public int x;
    public int y;

    public Pos(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pos pos = (Pos) o;
        return x == pos.x && y == pos.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

I've tried all this but when compiling I get "incompatible types: Pos cannot be converted to int".

Nermin
  • 749
  • 7
  • 17
  • 1
    You can't do that. From the [tutorial page](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html): "A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer (discussed in Numbers and Strings)." See [this question](https://stackoverflow.com/questions/31664071/why-cant-we-switch-on-classes-in-java-7) for more of a discussion about switches in Java. – azurefrog Jan 30 '20 at 21:55

4 Answers4

2

For the second example,

int pos = Arrays.asList(getNextPos()).indexOf(getNextPos());

would allow old school switch.

It's a dirty, dirty hack, but with switch-on-string, you could:

 switch (pos.toString()) {
     case "1,2": ....

That does need an appropriate and stable toString method, which probably isn't a terrible idea anyway.

Even terribler:

 switch (pos.x+","+pos.y) {
     case "1,2": ....

Don't try the with user-supplied strings.

There have been noises from Oracle about extending instanceof pattern matching (JEP 305) into full on deconstructors for switch which could include matching on property values.

Another way around it is to use a Map from Pos to an appropriate functional type. Perhaps else-if chains aren't that bad.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    One piece of the pattern matching feature, records, is appearing as a preview feature in JDK 14, but it will probably be a few more versions before pattern matching is ready for use. I've been following the developments a bit, and it looks very interesting. Here's something Brian Goetz and Gavin Bierman wrote about it back in 2018: https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html – David Conrad Jan 30 '20 at 23:26
2

With Java 8 and lambda, you have ways without the switch:

class Foobar {
  private final Map<Pos, Consumer<Pos>> mapping;

  Foobar() {
    mapping = new HashMap<>();
    mapping.put(new Pos(1, 2), this::doSomething1);
    mapping.put(new Pos(5, 2), this::doSomething2);
  }

  private void doSomething1(Pos pos) { ... }
  private void doSomething2(Pos pos) { ... }

  public void handleNextPost() {
    Pos pos = getNextPos();
    Consumer<Pos> consumer = mapping.get(getNextPos());
    if (null != consumer) {
      consumer.accept(pos);
    }
  } 
}

By the way, at bytecode level, a switch on String amount to almost the same as a HashMap: a tableswitch using the hashCode() and then as much equals as needed to go to the next statement using goto.

The code above is simple:

  • I declare a map containing for each Pos the action you want to do.
  • Each action is implemented by a method taking a Pos
  • When you read getNextPos(), you search for the Consumer handling said position.
  • The Consumer is invoked with the nextPos.

You may define it locally but you must ensure the mapping don't get created each time.

If you really have to stick with switch, you don't need anything special: you are using int for Pos x/y and switch naturally work on int:

switch (pos.getX()) {
  case 0: switch (pos.getY()) {
    case 1: { // [0, 1]
      break;
    }
    case 3: { // [0, 3]
       break;
    }     
  }
  break;
  // ... and so on
}

That will easily make your code harder to read, so avoid it.

Also, a map is not tailored for loose matching (for example: an action to take for every Pos where x = 1), you may have to use other alternative.

NoDataFound
  • 11,381
  • 33
  • 59
1

You could go for nested switch-case, but that can be hard to read when trillions of cases arise.

Pos pos = new Pos(2,3);

switch(pos.getX()){
    case "2":
        switch(pos.getY()){
            case "3":
                do something with Pos(2,3);

Or you could look at this answer with polymorphism Alternative to Nested Switch Statements in Java by Nirbhay Mishra.

funky
  • 313
  • 3
  • 8
  • 14
  • I looked at and was interested by the polymorphism answer but don't really know how I would implement it myself, do you know how I could integrate it into my example? – Nermin Jan 30 '20 at 22:30
  • It appears to me that in this case the polymorphism doesn`t seem to easily work. There are a lot of tutorials out there for replacing switch-cases with polymorphism and at least for my understanding it would be too much of a hassle with 10^2 various combinations. – funky Jan 30 '20 at 23:13
0

This somehow resembles the switch statement:

    Pos pos = getNextPos();

    Optional.ofNullable(Map.of(new Pos(1, 2), (Runnable) () -> {

        System.out.println(1);

    }, new Pos(3, 2), (Runnable) () -> {

        System.out.println(2);

    }).get(pos)).orElse((Runnable) () -> { // Default

        System.out.println(3);

    }).run();

A map of Pos-Objects which map to Runnables.

ave4496
  • 2,950
  • 3
  • 21
  • 48