Use an array as a case statement in switch

I am trying to do something like this, i.e., use an array in a switch statement. Is it possible in Java? If it isn't, please explain a possible solution.

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;
}
130207 次浏览

The answer is NO. The best explain is learn how to use the switch statement.

NO, simply you cannot.

SwitchStatement:
switch ( Expression ) SwitchBlock

The type of the Expression must be char, byte, short, int, Character, Byte, Short, Integer, String, or an enum type (§8.9), or a compile-time error occurs.

http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.11

Try this solution:

    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 {
...
}

See docs here.

No, you cannot, however you can replace the above with the following (dirty I admit) code:

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

I'd compute a value based on the sequence of the elements in the boolean array, i.e. [true, false, true, true] would evaluate to 1011 and then based on this integer value you can use switch statement.

You can't switch on whole arrays. But you could convert to a bit set at the expense of some readability of the switch itself:

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

and use binary literals in your case statements: case 0b0101 is your first one.

@sᴜʀᴇsʜ ᴀᴛᴛᴀ is right. But I wanted to add something. Since Java 7, switch statements support Strings, so you could do something with that. It is really dirty and I do not recommend, but this works:

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

For those concerned about performance: you are right, this is not super fast. This will be compiled into something like this:

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

Yes, you can pass an array to a switch. The catch is that I'm not talking about Java arrays, but a data structure.

An array is a systematic arrangement of objects, usually in rows and columns.

What you are trying to do is implement a system that recognizes different flags and depending on the flags that are turned on or off you take different actions.

Example

A popular implementation of such mechanism is Linux file permissions. Where you have rwx as the "array of flags".

If the whole array is true, you'll see rwx, which means that you have all the permissions. If you are not allowed to perform any action on a file, the whole array is false, you'll see ---.

Implementation

Guess what, you can think of integers as arrays. An integer is represented by an "array of bits".

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

That is why the permission rwx can be represented as a 7

Java snippet:

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

To know more about using a binary format, check this question: In Java, can I define an integer constant in binary format?

Performance

  • Saves memory
  • You don't have to do extra processing, switches or any other type of juggling.

C programs that require to be as efficient as possible use this type of mechanism; they use flags represented with single bits.

If you're trying to determine if a set of conditions is true, I'd use bitwise fields instead.

For example,

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

Here is another approach requiring no imports nor libraries:

boolean[] values = new boolean[4];


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


int mask = buildMask(values);


if (areEquals(mask, true, false, true, false)) {
// ...
} else if (areEquals(mask, false, false, true, false)) {
// ...
} else {
// ...
}


private int buildMask(boolean... values) {
int n = 0;
for (boolean b : values) {
n = (n << 1) | (b ? 1 : 0);
}
return n;
}


private boolean areEquals(int mask, boolean... values) {
return mask == buildMask(values);
}

As of JRE 1.7, you will need to use a hack, I recommend:

  • Assume values.length <= 64

  • Convert values to a long representing bitflags

  • Switch against hexadecimal magic numbers

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

This answer is not Java, but Haxe because it is possible in it, thanks to pattern matching and has interesting output, which might be useful for you to find a switch that does what you are asking for. Arrays can be matched on fixed length.

I created an demo that compiles to Javascript and Flash. You can see the js-output in the right column.

Demo: 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"
}
}

This is the outputted switch, it uses nested switches. If you play with the cases, you see how the js-ouput changes to have a efficient switch.

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

Another interesting pattern that you can use underscores. a _ pattern matches anything, so case _: is equal to default, which makes you able to do this:

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

You can also take a look at how Groovy implements the isCase() methods in Java, use a simpler version that fits your needs. It's possible to put that in an interface and create a DSL to compare any two object in your application.

return isCase(DefaultTypeTransformation.asCollection(caseValue), switchValue);

The relevant code is covered in Lines 877 through Lines 982

I would use constant int values that represent the boolean state.

If you use Java 1.7 or above you can use binary literals that are more readable.

public static final int TRUE_FALSE_TRUE_FALSE = 0b1010;
public static final int FALSE_FALSE_TRUE_FALSE = 0b0010;

for Java 1.6 and below use any other int literals, e.g. hex

public static final int TRUE_FALSE_TRUE_FALSE = 0xA;
public static final int FALSE_FALSE_TRUE_FALSE = 0x2;

then create a method that converts a boolean array to an integer bitset. E.g.

public static int toIntBitSet(boolean...values){
int bitset = 0;
for (boolean value : values) {
bitset = (bitset << 1) | (value ? 1 : 0);
}
return bitset;
}

Finally use the constants in your switch statement

boolean[] values = new boolean[]{true, false, true, false};


int bitset = toIntBitSet(values);


switch (bitset) {
case TRUE_FALSE_TRUE_FALSE:
System.out.println(Integer.toBinaryString(bitset));
break;
case FALSE_FALSE_TRUE_FALSE:
System.out.println(Integer.toBinaryString(bitset));
break;
default:
break;
}

Another approach might be to use a java BitSet and a Map that maps to the logic that should be executed depending on the bitset's value.

public static void main(String[] args) throws Exception {
Map<BitSet, Callable<String>> bitSetMap = new HashMap<>();


bitSetMap.put(bitSetValueOf(true, false, true, false), new TrueFalseTrueFalseCallable());
bitSetMap.put(bitSetValueOf(false, false, true, false), new FalseFalseTrueFalseCallable());


boolean[] values = new boolean[]{true, false, true, false};


BitSet bitset = bitSetValueOf(values);


Callable<String> callable = bitSetMap.get(bitset);
if (callable == null) {
callable = new DefaultCallable();
}


String result = callable.call();
System.out.println(result);
}


public static BitSet bitSetValueOf(boolean... values) {
BitSet bitSet = new BitSet();
for (int i = 0; i < values.length; i++) {
bitSet.set(i, values[i]);
}
return bitSet;
}

and implement your logic

class FalseFalseTrueFalseCallable implements Callable<String> {


@Override
public String call() throws Exception {
return "0010";
}


}


class TrueFalseTrueFalseCallable implements Callable<String> {


@Override
public String call() throws Exception {
return "1010";
}


}


class DefaultCallable implements Callable<String> {


@Override
public String call() throws Exception {
return "default value";
}


}

@Todor Yes, THIS IS POSSIBLE IN JAVA.

boolean[] values = new boolean[4];


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


String values_asString = Arrays.toString(values);
 

switch (values_asString) {
case "[true, false, true, false]":
break;
case "[false, false, true, false]":
break;
case "[true, false, false, true]":
System.out.println("YAAAAAAAAAA GOT IT");
break;
default:
break;
}

Here I just converted an array to a string format and then matched it in the switch case.