web-dev-qa-db-ja.com

Javaに列挙が存在するかどうかを確認してください

とにかく列挙型が存在するかどうかを特定の文字列と比較することで確認する方法はありますか?私はそのような機能を見つけることができないようです。 valueOfメソッドを使用して例外をキャッチしようとすることはできますが、ランタイム例外をキャッチするのは良い習慣ではないことを教えられました。誰にもアイデアはありますか?

52
Danny

例外をキャッチせずにそれを行う組み込みの方法はないと思います。代わりに次のようなものを使用できます。

_public static MyEnum asMyEnum(String str) {
    for (MyEnum me : MyEnum.values()) {
        if (me.name().equalsIgnoreCase(str))
            return me;
    }
    return null;
}
_

編集: Jon Skeetが述べているように、values()は呼び出されるたびにプライベートのバッキング配列を複製することで機能します。パフォーマンスが重要な場合は、values()を1回だけ呼び出し、配列をキャッシュして、それを反復処理することができます。

また、enumに膨大な数の値がある場合、Jon Skeetのマップの代替は、配列の反復よりもパフォーマンスが高い可能性があります。

46
Michael Myers

これを行う必要がある場合は、名前の_Set<String>_、または自分の_Map<String,MyEnum>_を作成することもあります。それを確認するだけです。

注目に値するいくつかのポイント:

  • そのような静的コレクションを静的初期化子に設定します。 Do n't変数初期化子を使用し、enumコンストラクターの実行時に実行されたことに依存します。 (enumコンストラクターは、静的初期化子の前に実行される最初のものです。)
  • values()を頻繁に使用しないようにしてください-毎回新しい配列を作成して設定する必要があります。すべての要素を反復処理するには、_EnumSet.allOf_を使用します。これは、多数の要素を持たない列挙型に対してはるかに効率的です。

サンプルコード:

_import Java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    private static final Map<String, SampleEnum> nameToValueMap =
        new HashMap<String, SampleEnum>();

    static {
        for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }

    public static SampleEnum forName(String name) {
        return nameToValueMap.get(name);
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}
_

もちろん、少数の名前しかない場合、これはおそらくやり過ぎです。nが十分に小さい場合、O(n)ソリューションがO(1)ソリューションよりも勝つことがよくあります。別のアプローチを次に示します。

_import Java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    // We know we'll never mutate this, so we can keep
    // a local copy.
    private static final SampleEnum[] copyOfValues = values();

    public static SampleEnum forName(String name) {
        for (SampleEnum value : copyOfValues) {
            if (value.name().equals(name)) {
                return value;
            }
        }
        return null;
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}
_
61
Jon Skeet

私のお気に入りのライブラリの1つ:Apache Commons。

EnumUtils で簡単にできます。

そのライブラリでEnumを検証する例に従ってください:

public enum MyEnum {
    DIV("div"), DEPT("dept"), CLASS("class");

    private final String val;

    MyEnum(String val) {
    this.val = val;
    }

    public String getVal() {
    return val;
    }
}


MyEnum strTypeEnum = null;

// test if String str is compatible with the enum 
// e.g. if you pass str = "div", it will return false. If you pass "DIV", it will return true.
if( EnumUtils.isValidEnum(MyEnum.class, str) ){
    strTypeEnum = MyEnum.valueOf(str);
}
34
рüффп

ランタイム例外のキャッチが悪いと誰かが言った理由はわかりません。

valueOfを使用し、IllegalArgumentExceptionをキャッチすると、文字列を列挙型に変換/チェックするのに適しています。

6
nos

ジョン・スキートの答えに基づいて、私は仕事で簡単にそれを行うことを許可するクラスを作りました:

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import Java.util.EnumSet;
import Java.util.HashSet;
import Java.util.Map;
import Java.util.Set;

/**
 * <p>
 * This permits to easily implement a failsafe implementation of the enums's valueOf
 * Better use it inside the enum so that only one of this object instance exist for each enum...
 * (a cache could solve this if needed)
 * </p>
 *
 * <p>
 * Basic usage exemple on an enum class called MyEnum:
 *
 *   private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
 *   public static MyEnum failSafeValueOf(String enumName) {
 *       return FAIL_SAFE.valueOf(enumName);
 *   }
 *
 * </p>
 *
 * <p>
 * You can also use it outside of the enum this way:
 *   FailSafeValueOf.create(MyEnum.class).valueOf("EnumName");
 * </p>
 *
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOf<T extends Enum<T>> {

    private final Map<String,T> nameToEnumMap;

    private FailSafeValueOf(Class<T> enumClass) {
        Map<String,T> map = Maps.newHashMap();
        for ( T value : EnumSet.allOf(enumClass)) {
            map.put( value.name() , value);
        }
        nameToEnumMap = ImmutableMap.copyOf(map);
    }

    /**
     * Returns the value of the given enum element
     * If the 
     * @param enumName
     * @return
     */
    public T valueOf(String enumName) {
        return nameToEnumMap.get(enumName);
    }

    public static <U extends Enum<U>> FailSafeValueOf<U> create(Class<U> enumClass) {
        return new FailSafeValueOf<U>(enumClass);
    }

}

そしてユニットテスト:

import org.testng.annotations.Test;

import static org.testng.Assert.*;


/**
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOfTest {

    private enum MyEnum {
        TOTO,
        TATA,
        ;

        private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
        public static MyEnum failSafeValueOf(String enumName) {
            return FAIL_SAFE.valueOf(enumName);
        }
    }

    @Test
    public void testInEnum() {
        assertNotNull( MyEnum.failSafeValueOf("TOTO") );
        assertNotNull( MyEnum.failSafeValueOf("TATA") );
        assertNull( MyEnum.failSafeValueOf("TITI") );
    }

    @Test
    public void testInApp() {
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TOTO") );
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TATA") );
        assertNull( FailSafeValueOf.create(MyEnum.class).valueOf("TITI") );
    }

}

私はグアバを使用してImmutableMapを作成しましたが、実際にはマップが返されないため、通常のマップを使用できると思います...

5

ほとんどの回答では、equalsを含むループを使用してenumが存在するかどうかを確認するか、try.catchをenum.valueOf()で使用することを推奨しています。どちらの方法が速いかを知りたくて試してみました。私はベンチマークがあまり得意ではないので、間違いを犯した場合は修正してください。

メインクラスのコードは次のとおりです。

    package enumtest;

public class TestMain {

    static long timeCatch, timeIterate;
    static String checkFor;
    static int corrects;

    public static void main(String[] args) {
        timeCatch = 0;
        timeIterate = 0;
        TestingEnum[] enumVals = TestingEnum.values();
        String[] testingStrings = new String[enumVals.length * 5];
        for (int j = 0; j < 10000; j++) {
            for (int i = 0; i < testingStrings.length; i++) {
                if (i % 5 == 0) {
                    testingStrings[i] = enumVals[i / 5].toString();
                } else {
                    testingStrings[i] = "DOES_NOT_EXIST" + i;
                }
            }

            for (String s : testingStrings) {
                checkFor = s;
                if (tryCatch()) {
                    ++corrects;
                }
                if (iterate()) {
                    ++corrects;
                }
            }
        }

        System.out.println(timeCatch / 1000 + "us for try catch");
        System.out.println(timeIterate / 1000 + "us for iterate");
        System.out.println(corrects);
    }

    static boolean tryCatch() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        try {
            TestingEnum.valueOf(checkFor);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        } finally {
            timeEnd = System.nanoTime();
            timeCatch += timeEnd - timeStart;
        }

    }

    static boolean iterate() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        TestingEnum[] values = TestingEnum.values();
        for (TestingEnum v : values) {
            if (v.toString().equals(checkFor)) {
                timeEnd = System.nanoTime();
                timeIterate += timeEnd - timeStart;
                return true;
            }
        }
        timeEnd = System.nanoTime();
        timeIterate += timeEnd - timeStart;
        return false;
    }
}

つまり、各メソッドは、10、20、50、および100の列挙定数を使用して、このテストを複数回実行した列挙の長さの50000回実行されます。結果は次のとおりです。

  • 10:試行/キャッチ:760ms |繰り返し:62ms
  • 20:試行/キャッチ:1671ms |繰り返し:177ms
  • 50:試行/キャッチ:3113ms |反復:488ms
  • 100:試行/キャッチ:6834ms |繰り返し:1760ms

これらの結果は正確ではありませんでした。再度実行すると、結果に最大10%の差がありますが、特に小さな列挙型ではtry/catchメソッドの効率がはるかに低いことを示すのに十分です。

4
Alexander Daum

Java 8であるため、forループの代わりに streams を使用できます。また、enumが Optional そのような名前のインスタンスはありません。

列挙型を検索する方法について、次の3つの選択肢を思いつきました。

private enum Test {
    TEST1, TEST2;

    public Test fromNameOrThrowException(String name) {
        return Arrays.stream(values())
                .filter(e -> e.name().equals(name))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("No enum with name " + name));
    }

    public Test fromNameOrNull(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst().orElse(null);
    }

    public Optional<Test> fromName(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst();
    }
}
2
Magnilex

Guavaを使用して、次のようなこともできます。

// This method returns enum for a given string if it exists, otherwise it returns default enum.
private MyEnum getMyEnum(String enumName) {
  // It is better to return default instance of enum instead of null
  return hasMyEnum(enumName) ? MyEnum.valueOf(enumName) : MyEnum.DEFAULT;
}

// This method checks that enum for a given string exists.
private boolean hasMyEnum(String enumName) {
  return Iterables.any(Arrays.asList(MyEnum.values()), new Predicate<MyEnum>() {
    public boolean apply(MyEnum myEnum) {
      return myEnum.name().equals(enumName);
    }
  }); 
}

2番目の方法では、非常に便利なIterablesクラスを提供するguava( Google Guava )ライブラリを使用します。 Iterables.any()メソッドを使用して、リストオブジェクトに特定の値が存在するかどうかを確認できます。このメソッドには、リストとPredicateオブジェクトの2つのパラメーターが必要です。まず、Arrays.asList()メソッドを使用して、すべての列挙型を含むリストを作成しました。その後、新しいPredicateオブジェクトを作成しました。このオブジェクトは、特定の要素(この場合はenum)がapplyメソッド。その場合、メソッドIterables.any()は真の値を返します。

1
Dawid Stępień