在switch语句中使用数组作为case选项

78

我正在尝试做类似于这样的事情,即在一个switch语句中使用数组。在Java中是否可能?如果不可能,请解释一种可能的解决方案。

boolean[] values = new boolean[4];

values[0] = true;
values[1] = false;
values[2] = false;
values[3] = true;

switch (values) {
    case [true, false, true, false]:
        break;
    case [false, false, true, false]:
        break;
    default:
        break;
}

5
最好使用if语句,避免使用switch。 - Mukul Goel
56
我认为你应该问自己为什么执行应该依赖于布尔类型的数组。也许一个类可以更好地包含这些数据,并具有更多语义和更易于测试的方法?按照你的写法,它看起来像是未来维护的噩梦。 - Eric Wilson
1
使用循环根据数组索引在 int 上设置位,并关闭该位。 - octal9
15
你想要的被称为“模式匹配”,但是在Java中不能做到。 - Ingo
11
听起来你正在尝试实现位标志 - 参见这个问题:使用Java枚举实现位字段 - T. Kiley
显示剩余3条评论
15个回答

73

@sᴜʀᴇsʜ ᴀᴛᴛᴀ 是正确的。但是我想要补充一些内容。自从Java 7以来,switch语句支持字符串,所以你可以使用它。 这确实很脏,我不建议这样做,但是这是可行的:

boolean[] values = new boolean[4];

values[0] = true;
values[1] = false;
values[2] = false;
values[3] = true;

switch (Arrays.toString(values)) {
    case "[true, false, true, false]":
        break;
    case "[false, false, true, false]":
        break;
    default:
        break;
}

对于那些关注性能的人:你们是正确的,这并不是非常快速的。这将被编译成类似于以下内容:


String temp = Arrays.toString(values)
int hash = temp.hashCode();
switch (hash)
{
    case 0x23fe8da: // Assume this is the hashCode for that
                    // original string, computed at compile-time
        if (temp.equals("[true, false, true, false]"))
        {

        }
        break;
    case 0x281ddaa:
        if (temp.equals("[false, false, true, false]"))
        {

        }
        break;

    default: break;
}

我的最初想法是使用位运算,但我不喜欢它作为解决方案。 - Todor Grudev
77
既有创意又很可怕。+1 - David
3
如果数组的大小受到过度限制,当需要更改数组大小时,维护起来会非常麻烦。 - Jonathan Drapeau
1
对于性能存在一种漠不关心的态度。这种类型的问题通常可以通过在 int 中设置位来解决,但是您却要将其转换为字符串并进行比较。在 Java 中。 - bobobobo
1
@bobobobo:你说得对。我永远不会为自己写这个。这只是一个非常接近 OP 所需的有趣代码片段。相信我,我知道这很糟糕 :) - Martijn Courteaux
显示剩余6条评论

67

就我个人而言,这是正确的,尽管其他答案中也有一些努力来找到一个好的解决方案。 - Code Whisperer
8
老实说,这是最好的答案。我不确定鼓励解决原问题所需的花式操作是否应该被提倡。 - NotMe
这真的很老了,我甚至都记不起最初的问题是什么了,但回顾代码,似乎我做了一些糟糕的架构决策。这就是为什么我认为这是正确的答案——它更像是在发现问题而不是找到解决方案的信号。 - Todor Grudev

49
您不能直接对整个数组进行开关操作。但是您可以将其转换为位集,这样可以牺牲一些 switch 本身的可读性:

switch (values[0] + 2 * values[1] + 4 * values[2] + 8 * values[3])

并在您的情况语句中使用二进制字面量:case 0b0101 是您的第一个情况。


哇,这当然是可能的,但这是否是解决实际问题的好方法呢? - Eric Wilson
5
为了使此代码可扩展,我会将其放入一个循环中:int switchVal = 0; for (int i = 0; i < values.length(); i++) { switchVal += values[i] ? (2 << i) : 0; },然后使用 switchVal 变量进行开关控制。 - Darrel Hoffman
3
为了使转换更易读/有意义,请考虑将这些二进制文字常量变成有意义的常量,这样你就可以在常量上进行转换,而不是在二进制文字常量上进行转换。 - rrauenza
3
我会尽力为您翻译。这句话的意思是将[true, false, true, false]转换成位序列1010,然后可以使用switch打开它,这很明显是可行的。在我看来,这绝对是正确的做法。 - Izkata
我想展示在Java SE 8中使用Lambda的二进制示例,但不确定是否会因此受到批评。我会等到三月份再展示。 - ialexander
+1 和 @Eric:当然,在精神上,这是原问题的一个很好的解决方案,尽管不是该问题的通用版本。OP有一个布尔数组。将其转换为位集不仅在一般情况下有用,而且对于此问题非常有价值。当然,它可能更易读(例如使用函数)。 - Travis Wilson

46

试试这个解决方案:

    boolean[] values = new boolean[4];
    values[0] = true;
    values[1] = false;
    values[2] = false;
    values[3] = true;

    if (ArrayUtils.isEquals(values, new boolean[] {true, false, true, false})) {
    ...
    }
    else if (ArrayUtils.isEquals(values, new boolean[] {false, false, true, false})) {
    ...
    }
    else {
    ...
    }

查看此处文档。


50
这个解决方案非常依赖于数组的大小,一旦数组的大小发生变化,你将会面临维护上的噩梦。 - Jonathan Drapeau
99
他们给一个函数起名为isEquals - Sebastian Mach
10
无需使用apache-commons-lang库 - 只需使用JDK中的java.util.Arrays.equals(arr1,arr2)方法。 - Ed Staub
10
@drigoangelo: 我知道我知道。但我不明白为什么他们不只是使用“isEqual”,因此可以使用“动词+形容词”或“动词+名词”,而不是“动词+动词”。就个人而言,我更喜欢在布尔函数中使用“形容词”,在改变状态的程序中使用“动词”。 - Sebastian Mach
2
@JonathanDrapeau,你可能会因为这种设计而遇到维护的噩梦... - MEMark
显示剩余7条评论

22

是的,你可以将一个数组传递给switch语句。需要注意的是,我所说的不是Java数组,而是一种数据结构。

数组是通常以行和列方式系统地排列对象的数据结构。

你正在尝试实现一个系统,该系统识别不同的标志,并根据打开或关闭的标志采取不同的操作。

示例

此类机制的流行实现是Linux文件权限。在那里,您有rwx作为“标志数组”。

如果整个数组为真,则会看到rwx,这意味着您拥有所有权限。如果您不被允许对文件执行任何操作,则整个数组为false,您将看到---

实现

猜猜看,您可以将整数视为数组。整数由“位数组”表示。

001 // 1, if on, set x 
010 // 2, if on, set w 
100 // 4, if on, set r
// putting it all together in a single "array" (integer)
111 // 2^2 + 2^1 + 2^0 = 4 + 2 + 1 = 7
那就让我们开始吧:rwx 权限可以表示为 7,这就是原因。
Java 代码片段:
class Flags {                                                                    
public static void main(String args[]) {         
        /** 
         * Note the notation "0b", for binary; I'm using it for emphasis.
         * You could just do: 
         * byte flags = 6;
         */                     
        byte flags = 0b110; // 6                     
        switch(flags) {                                                          
            case 0: /* do nothing */ break;                                      
            case 3: /* execute and write */ break;                       
            case 6: System.out.println("read and write\n"); break;         
            case 7: /* grant all permissions */ break;                           
            default:                                                             
                System.out.println("invalid flag\n");           
        }                                                                        
    }                                                                            
}

要了解如何使用二进制格式,请查看此问题:在Java中,我可以以二进制格式定义整数常量吗?

性能

  • 节省内存
  • 您无需执行额外的处理、开关或任何其他类型的操作。

需要尽可能高效的C程序使用这种机制;它们使用用单个位表示的标志。


21

不可以,但您可以用以下(我承认有点不正规)的代码替换上面的内容:

boolean[] values = new boolean[4];

values[0] = true;
values[1] = false;
values[2] = false;
values[3] = true;

switch(makeSuitableForSwitch(values)) {
   case 1010: 
     break;
   case 10: 
     break;
   default:
     break;
} 

private int makeSuitableForSwitch( boolean[] values) {
    return (values[0]?1:0)*1000+(values[1]?1:0)*100+(values[2]?1:0)*10+(values[3]?1:0);
}

3
我会将求和公式放入一个方法中,而不是直接放在switch语句里。 - Jonathan Drapeau
8
除了它丑陋之外,它是错误的:0010被解释为八进制... - Gyro Gearless
2
@Alex 的辅助方法应该是“private”。而且对于大型数组来说,返回值为“int”可能会导致溢出,这不是一个好主意。 - Maroun
1
我喜欢这个,因为它似乎比被接受的大而复杂的if/else语句更容易维护。 - WernerCD
1
另外,如果你可以使用values[0]?1000:0,为什么要使用(values[0]?1:0)*1000呢? - Cruncher
显示剩余2条评论

10

如果你想确定一组条件是否为真,我会使用位字段。

例如,

public class HelloWorld
{
  // These are the options that can be set.
  // They're final so treated as constants.
  static final int A=1<<0, B=1<<1, C=1<<2, D=1<<3 ;

  public static void main(String []args)
  {
    // Now I set my options to have A=true, B=true, C=true, D=false, effectively
    int options = A | B | C ;

    switch( options )
    {
      case (A):
        System.out.println( "just A" ) ;
        break ;
      case (A|B):
        System.out.println( "A|B" ) ;
        break ;
      case (A|B|C): // Final int is what makes this work
        System.out.println( "A|B|C" ) ;
        break ;
      default:
        System.out.println( "unhandled case" ) ;
        break ;
    }
  }
}

6

我会根据布尔数组中元素的顺序计算一个值,例如[true, false, true, true]将被计算为1011,然后根据这个整数值可以使用switch语句。


确保如果您更改数组的长度(或顺序),它仍然可以正常工作,如果您想避免重写每个“case”语句。如果Java不允许为每个“case”调用函数,则可能需要一个大型if链,可能需要为每个替代案例使用关联数组,并为每个if测试调用函数。 - Phil Perry

2
自JRE 1.7起,您需要使用一个hack,我建议:
  • 假设values.length <= 64

  • 将values转换为代表位标志的long

  • 使用十六进制魔数进行switch

Java代码Hack:

if(values.length > 64)
  throw new IllegalStateException();

long bitflags = 0x0L;

for(int i=0; i< values.length; ++i)
  if(values[i])
    bitflags |= 0x01L << i;

switch(bitflags) {
  case 0xEL: // represents [true,  true,  true, false]
    break;
  case 0xAL: // represents [true,  false, true, false]
    break;
  case 0x2L: // represents [false, false, true, false]
    break;
  default:
    break;
}

2
这个回答是关于Haxe的,而不是Java,因为它允许此操作,这要归功于模式匹配,并具有有趣的输出结果,这可能对您查找相应的开关很有用。数组可以根据固定长度进行匹配。
我创建了一个演示文稿,可以编译成Javascript和Flash。您可以在右列中看到js输出。
演示文稿: http://try.haxe.org/#86314
class Test {
  static function main(){

    var array=[true,false,true];

    var result=switch(array){
      case [true,true,false]: "no";
      case [true,false,true]: "yes";
      default:"??";
    }

    #if js
      new js.JQuery("body").html(result);
    #elseif flash
      trace(result);
    #end

    // ouputs: "yes"
  }
}

这是输出开关,它使用嵌套开关。如果您尝试更改情况,您会看到js-output如何更改以具有高效的开关。

(function () { "use strict";
var Test = function() { };
Test.main = function() {
    var array = [true,false,true,false];
    var result;
    switch(array.length) {
    case 4:
        switch(array[0]) {
        case true:
            switch(array[1]) {
            case false:
                switch(array[2]) {
                case true:
                    switch(array[3]) {
                    case false:
                        result = "no";
                        break;
                    default:
                        result = "??";
                    }
                    break;
                default:
                    result = "??";
                }
                break;
            default:
                result = "??";
            }
            break;
        case false:
            switch(array[1]) {
            case false:
                switch(array[2]) {
                case true:
                    switch(array[3]) {
                    case false:
                        result = "yes";
                        break;
                    default:
                        result = "??";
                    }
                    break;
                default:
                    result = "??";
                }
                break;
            default:
                result = "??";
            }
            break;
        }
        break;
    default:
        result = "??";
    }
    new js.JQuery("body").html(result);
};
var js = {};
var q = window.jQuery;
js.JQuery = q;
Test.main();
})();

另一个有趣的模式是使用下划线。一个 _ 模式匹配任何内容,所以 case _: 等同于 default,这使您能够做到以下操作:

var myArray = [1, 6];
var match = switch(myArray) {
    case [2, _]: "0";
    case [_, 6]: "1";
    case []: "2";
    case [_, _, _]: "3";
    case _: "4";
}
trace(match); // 1

http://haxe.org/manual/pattern_matching#array-matching


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接