8

古い方法では、複雑なビットマスクを使用したい場合は、次のswitchように簡単に実行できます (問題を示すためだけに頭のてっぺんからランダムに例を挙げました)。

private static final int   MAN = 0x00000001;
private static final int WOMAN = 0x00000002;
// ...alive, hungry, blind, etc.
private static final int  DEAD = 0xFF000000;

public void doStuff(int human) {
    switch (human) {
    case MAN | DEAD:
        // do something
        break;
    // more common cases
    }
}

最近ではenumsandを使っているのでEnumSets、私は時々似たようなことをしたいと思います:

enum Human {
    MAN, WOMAN, DEAD; // etc.
}

public void doStuff(EnumSet human) {
    switch (human) {
    case Human.MAN | Human.DEAD:
        // do something
        break;
    // more common cases
    }
}

switch、または値intでしかできないため、これは機能しません。この時点で、その値は基本的に単なる隠し整数であるにもかかわらず、実行できないことに気付きました。しかし、私は掘り下げるのが好きで、機能は非常に便利に見えるので、次のようにします。enumStringenum

private static final EnumSet<Human> DEAD_MAN = EnumSet.of(Human.MAN, Human.DEAD);

public void doStuff(EnumSet human) {
    switch (human) {
    case DEAD_MAN:
        // do something
        break;
    // more common cases
    }
}

まだ運がありません。文字列を切り替えるためのトリックと、EnumSets が実際には 64 ビット フィールド (またはそれらの配列) であることを知っているので、次のことも試してみます。

    switch (human.hashCode()) {
    case (Human.MAN.hashCode() | Human.DEAD.hashCode()):
        // do something
        break;
    // more common cases
    }

Human hashCode()が適切に実装されて一貫した結果が得られれば、機能する可能性があると考えています。いいえ:

java.lang.Error:未解決のコンパイルの問題: ケース式は定数式でなければなりません


さて、なぜこれを行う可能性がないのだろうか。私は常に、Java で古い学校のビットフィールドの適切な代替品のように考えてenumsEnumSetsましたが、ここでは、新しい方法ではより複雑なケースを処理できないようです。

適切なソリューションの種類は、可能性のいずれかと比較して最悪ですswitch

public void doStuff(EnumSet human) {
    if (human.contains(Human.MAN) && human.contains(Human.DEAD)) {
        // do something
    } else {
        // more common cases
    }
}

特に、 on が導入されて以来、switchonStringsには少なくとも 2 つの可能な実装があると思いswitchますEnumSets

  1. 式では、単純に列挙型自体の代わりにcase (Human.MAN | Human.DEAD)コンパイル時の型チェックを使用します。ordinal()
  2. Strings と同じトリックを使用します。
    • コンパイル時に、enum 値の を計算しhashCode() ますname(そして、おそらく追加の何か - enum の値の数ordinal()など - すべてはコンパイル時から静的で一定です)。はい、これはクラスまたはクラスのhashCode()いずれかを変更することを意味します。EnumSetEnum
    • 列挙型自体の代わりに使用

さて、これを簡単に実装することを不可能にする深刻な障害はありますか? それとも、これは実際に可能ですが、あまり頻繁に使用されないため、オラクルが実装するほど望ましくないというのは正しいでしょうか?


また、これは純粋に学術的な質問であり、適切な答えがない可能性があることを述べさせてください(わかりません。別の方法で質問することはありません)。答えられないことが判明した場合は、コミュニティ wiki にするかもしれません。しかし、私はどこにも答え(またはそれについて話し合っている人でさえ)を見つけることができなかったので、ここに行きます.

4

3 に答える 3

6

Javaおよびオブジェクト指向の世界では、オブジェクトにセッターとゲッターを持つクラスがあり、それらを使用します

public void doStuff(Human human) {
    if(human.isDead()) {
       if(human.isMale()) {
           // something
       } else if (human.isFemale()) {
           // something else
       } else {
           // neither
       }
    }
}

注: switch は完全一致しか取得しないため、お勧めできません。たとえば、死ぬ前にお腹が空いていなかった人だけと一致させたい場合を除き、一致しませんcase MAN | DEAD:MAN | HUNGRY | DEAD;)


私はあなたの「絶対に十分な」ベンチマークを見て、別の欠陥のあるベンチマークを提示します。これは、クロック サイクルの数分の 1 しかかからないことを「示しています」

public static void main(String... args) {
    Human human = new Human();
    human.setMale(true);
    human.setDead(true);
    for(int i=0;i<5;i++) {
        long start = System.nanoTime();
        int runs = 100000000;
        for(int j=0;j< runs;j++)
            doStuff(human);
        long time = System.nanoTime() - start;
        System.out.printf("The average time to doStuff was %.3f ns%n", (double) time / runs);
    }
}

public static void doStuff(Human human) {
    if (human.isDead()) {
        if (human.isMale()) {
            // something
        } else if (human.isFemale()) {
            // something else
        } else {
            // neither
        }
    }
}

static class Human {
    private boolean dead;
    private boolean male;
    private boolean female;

    public boolean isDead() {
        return dead;
    }

    public boolean isMale() {
        return male;
    }

    public boolean isFemale() {
        return female;
    }

    public void setDead(boolean dead) {
        this.dead = dead;
    }

    public void setMale(boolean male) {
        this.male = male;
    }

    public void setFemale(boolean female) {
        this.female = female;
    }
}

版画

The average time to doStuff was 0.031 ns
The average time to doStuff was 0.026 ns
The average time to doStuff was 0.000 ns
The average time to doStuff was 0.000 ns
The average time to doStuff was 0.000 ns

完全に最適化される前に、私のマシンでは 0.1 クロック サイクルです。

于 2012-12-28T18:16:29.680 に答える
5

Setのメソッドを使用するのはどうですかEnumSet

private static final EnumSet<Human> DEAD_MAN = 
  EnumSet.of(Human.MAN, Human.DEAD);

public void doStuff(EnumSet human) {
    if ( human.containsAll( DEAD_MAN ) )
    {
            // do something
            break;
    }
    else
    {
        // more common cases
    }
}

実際、EnumSet のSetインターフェイス メソッドの実装は非常に効率的であり、その下には探しているビットフィールドの比較があります。

于 2012-12-28T18:20:00.167 に答える
-5

次の手順を実行します (例に基づいて)。

enum Human {
    MAN, WOMAN, DEAD; // etc.
}

public void doStuff(Human human) {
    switch (human) {
        case MAN:
        case DEAD:
            // do something
            break;
        // more common cases
    }
}

が必要な場合EnumSetは使用できずswitch、リファクタリングする必要がありますif

public void doStuff(EnumSet<Human> human) {
    if( human.containsAll(EnumSet.<Human>of(Human.MAN, Human.DEAD) {
            // do something
    }
}

後者のバリアントは、内部でビットごとの比較を行います。

于 2012-12-28T18:20:22.853 に答える