ฉันจะค้นหา Java enum จากค่า String ได้อย่างไร


164

ฉันต้องการค้นหา enum จากค่าสตริง (หรืออาจเป็นค่าอื่น ๆ ) ฉันลองใช้รหัสต่อไปนี้แล้ว แต่ไม่อนุญาตให้มีการเริ่มต้นในแบบคงที่ มีวิธีง่ายๆไหม?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};

IIRC ที่ให้ NPE เนื่องจากการเริ่มต้นแบบคงที่ทำจากบนลงล่าง (เช่นค่าคงที่ enum ที่ด้านบนถูกสร้างขึ้นก่อนที่จะลงไปที่stringMapค่าเริ่มต้น) วิธีแก้ไขปัญหาปกติคือใช้คลาสที่ซ้อนกัน
Tom Hawtin - tackline

ขอบคุณทุกคนสำหรับการตอบสนองที่รวดเร็วเช่นนี้ (FWIW ฉันไม่พบ Sun Javadocs ที่มีประโยชน์มากสำหรับปัญหานี้)
peter.murray.rust

มันเป็นปัญหาทางภาษามากกว่าเรื่องห้องสมุด อย่างไรก็ตามฉันคิดว่าเอกสาร API นั้นอ่านได้มากกว่า JLS (แม้ว่าอาจไม่ใช่นักออกแบบภาษา) ดังนั้นสิ่งแบบนี้น่าจะมีความโดดเด่นมากกว่าในเอกสาร java.lang
Tom Hawtin - tackline

คำตอบ:


241

ใช้valueOfวิธีการที่สร้างขึ้นโดยอัตโนมัติสำหรับแต่ละ Enum

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

สำหรับค่านิยมเริ่มต้นด้วย:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

ย้ายไปที่ภายหลังเพื่อใช้งานแผนที่เท่านั้นหากผู้สร้างโปรไฟล์ของคุณบอกคุณ

ฉันรู้ว่ามันวนซ้ำทุกค่า แต่มีเพียง 3 ค่า enum มันแทบจะไม่คุ้มค่ากับความพยายามใด ๆ ในความเป็นจริงเว้นแต่ว่าคุณจะมีค่ามากมายฉันจะไม่รบกวนแผนที่มันจะเร็วพอ


ขอบคุณ - และฉันสามารถใช้การแปลงกรณีถ้าฉันรู้ว่าค่ายังคงแตกต่างกัน
peter.murray.rust

คุณสามารถกำหนดทิศทางการเข้าถึงสมาชิกคลาส 'abbr' แทน "v.abbr ()" คุณสามารถใช้ "v.abbr.equals ... "
Amio.io

7
นอกจากนี้สำหรับค่าตามอำเภอใจ (กรณีที่สอง) ให้พิจารณาการขว้างIllegalArgumentExceptionแทนการคืนค่าว่าง ( เช่นเมื่อไม่พบข้อมูลที่ตรงกัน) - นี่เป็นวิธีที่ JDK Enum.valueOf(..)ทำเช่นนั้น
Priidu Neemre

135

คุณสนิทแล้ว สำหรับค่าที่กำหนดเองลองทำสิ่งต่อไปนี้:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}

4
เนื่องจากปัญหาเกี่ยวกับ classloader วิธีนี้จะไม่สามารถทำงานได้อย่างน่าเชื่อถือ ฉันไม่แนะนำและฉันเห็นว่ามันล้มเหลวเป็นประจำเพราะแผนที่การค้นหายังไม่พร้อมก่อนที่จะเข้าถึง คุณต้องวาง "ค้นหา" นอก enum หรือในคลาสอื่นเพื่อให้โหลดได้ก่อน
Adam Gent

7
@ Adam Gent: มีลิงค์ด้านล่างลงไปที่ JLS ที่การสร้างที่แน่นอนนี้ถูกวางตัวอย่าง มันบอกว่าเริ่มต้นคงที่เกิดขึ้นจากบนลงล่าง: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
เซเลน่า

3
ใช่จาวาสมัยใหม่เช่น java 8 ได้แก้ไขรูปแบบหน่วยความจำแล้ว ที่ถูกกล่าวว่าตัวแทนแลกเปลี่ยนความร้อนเช่น jrebel ยังคงสับสนถ้าคุณฝังการเริ่มต้นเนื่องจากการอ้างอิงแบบวงกลม
Adam Gent

@ Adam Gent: อืมม ฉันเรียนรู้สิ่งใหม่ทุกวัน [กระพริบอย่างเงียบ ๆ เพื่อปรับโครงสร้าง enums ของฉันด้วย bootstrap singletons ... ] ขออภัยถ้านี่เป็นคำถามที่โง่ แต่นี่เป็นปัญหาส่วนใหญ่กับการเริ่มต้นแบบคงที่หรือไม่?
เซเลนา

ฉันไม่เคยเห็นปัญหาเกี่ยวกับการบล็อกโค้ดแบบสแตติกที่ไม่ได้เริ่มต้น แต่ฉันใช้ Java 8 ตั้งแต่ฉันเริ่มต้นใน Java ในปี 2558 ฉันเพิ่งทำเกณฑ์มาตรฐานเล็กน้อยโดยใช้ JMH 1.22 และใช้ a HashMap<>เพื่อเก็บ enums ที่พิสูจน์แล้วว่าเป็น เร็วกว่า 40% เมื่อเทียบกับการวนซ้ำ
cbaldan

21

ด้วย Java 8 คุณสามารถทำได้ด้วยวิธีนี้:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}

nullฉันจะโยนยกเว้นแทนที่จะกลับ แต่มันก็ขึ้นอยู่กับกรณีการใช้งาน
alexander

12

@ คำตอบของ Lyle ค่อนข้างอันตรายและฉันเห็นว่ามันใช้งานไม่ได้โดยเฉพาะถ้าคุณทำให้ Enum เป็นคลาสภายในที่คงที่ แต่ฉันได้ใช้สิ่งนี้ซึ่งจะโหลดแผนที่ BootstrapSingleton ก่อนที่จะ enums

แก้ไข นี้ไม่ควรจะมีปัญหาใด ๆ กับ JVMs ทันสมัย (JVM 1.6 หรือสูงกว่า) แต่ฉันไม่คิดว่ายังคงมีปัญหาเกี่ยวกับ JRebel แต่ฉันไม่ได้มีโอกาสที่จะสอบซ่อมมัน

โหลดฉันก่อน:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

ตอนนี้โหลดไว้ในตัวสร้าง Enum:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

หากคุณมี enum ภายในคุณสามารถกำหนด Map ด้านบนนิยาม enum และควรโหลดก่อน (ในทางทฤษฎี)


3
จำเป็นต้องกำหนด "อันตราย" และสาเหตุที่การใช้งานในชั้นเรียนมีความสำคัญ มันเป็นมากกว่าของปัญหาความหมายคงที่จากบนลงล่าง แต่มีรหัสการทำงานในคำตอบที่เกี่ยวข้องอื่นที่มีการเชื่อมโยงกลับไปที่คำถามนี้ที่ใช้แผนที่แบบคงที่ในอินสแตนซ์ Enum ตัวเองด้วยวิธี "รับ" จากค่าที่ผู้ใช้กำหนด ประเภท Enum stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague

2
@DarrellTeague ปัญหาเดิมเกิดจาก JVMs เก่า (ก่อน 1.6) หรือ JVMs (IBMs) อื่น ๆ หรือ swappers hotcode (JRebel) ปัญหาไม่ควรเกิดขึ้นใน JVM สมัยใหม่ดังนั้นฉันอาจลบคำตอบของฉัน
Adam Gent

ดีที่จะต้องทราบว่าแน่นอนมันเป็นไปได้เนื่องจากการใช้งานคอมไพเลอร์ที่กำหนดเองและการเพิ่มประสิทธิภาพรันไทม์ ... การสั่งซื้ออาจ resequenced ทำให้เกิดข้อผิดพลาด อย่างไรก็ตามสิ่งนี้ดูเหมือนว่าจะละเมิดข้อกำหนด
Darrell Teague

7

และคุณไม่สามารถใช้valueOf () ?

แก้ไข: Btw ไม่มีอะไรหยุดคุณจากการใช้คง {} ใน enum


หรือสังเคราะห์valueOfชั้น enum Classเพื่อให้คุณไม่จำเป็นต้องระบุระดับ
Tom Hawtin - tackline

แน่นอนว่ามันไม่ได้มีรายการ javadoc ดังนั้นจึงเป็นการยากที่จะเชื่อมโยง
Fredrik

1
คุณสามารถใช้ valueOf () แต่ชื่อจะต้องตรงกับชุดที่ระบุในการประกาศ enum ทั้งสองวิธีการค้นหาด้านบนสามารถแก้ไขได้เพื่อใช้. equalsIgnoringCase () และมีความทนทานต่อข้อผิดพลาดเล็กน้อย

@leonardo True ถ้ามันเพิ่มความทนทานหรือเพียงแค่การยอมรับข้อผิดพลาดเป็นที่ถกเถียงกัน ในกรณีส่วนใหญ่ฉันจะบอกว่ามันเป็นหลังและในกรณีนั้นมันจะดีกว่าที่จะจัดการกับมันที่อื่นและยังคงใช้ valueOf ของ Enum ()
Fredrik

4

ในกรณีที่ช่วยผู้อื่นตัวเลือกที่ฉันชอบซึ่งไม่ได้อยู่ในรายการนี้จะใช้ฟังก์ชั่นแผนที่ของ Guava :

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    private static ImmutableMap<String, Verbosity> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

ด้วยค่าเริ่มต้นที่คุณสามารถใช้ได้nullคุณสามารถthrow IllegalArgumentExceptionหรือคุณfromStringสามารถคืนค่าOptionalได้ไม่ว่าคุณจะมีพฤติกรรมแบบใด


3

ตั้งแต่ java 8 คุณสามารถเริ่มต้นแผนที่ในบรรทัดเดียวและไม่มีบล็อกคงที่

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));

+1 ยอดเยี่ยมในการใช้สตรีมและรัดกุมมากโดยไม่ทำให้อ่านง่าย! ฉันไม่เคยเห็นการใช้งานแบบนี้Function.identity()มาก่อนเลยฉันพบว่ามันน่าดึงดูดใจกว่าเดิมe -> eมาก
Matsu Q.

0

ลองดูที่นี่สิ มันใช้งานได้สำหรับฉัน วัตถุประสงค์นี้เพื่อค้นหา 'RED' ด้วย '/ red_color' การประกาศ a static mapและการโหลดenums ลงไปเพียงครั้งเดียวจะก่อให้เกิดประโยชน์ด้านประสิทธิภาพบางประการหากenums นั้นมีมากมาย

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

กรุณาใส่ใน Verifyons ของคุณ


-1

คุณสามารถกำหนด Enum ของคุณเป็นรหัสต่อไปนี้:

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

   public int getValue()
   {
     return this.value;
   } 

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

ดูลิงค์ต่อไปนี้สำหรับการชี้แจงเพิ่มเติม


-1

หากคุณต้องการค่าเริ่มต้นและไม่ต้องการสร้างแผนที่การค้นหาคุณสามารถสร้างวิธีการแบบคงที่เพื่อจัดการกับมัน ตัวอย่างนี้จัดการกับการค้นหาที่ชื่อที่คาดว่าจะเริ่มต้นด้วยตัวเลข

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

หากคุณต้องการในค่ารองคุณเพียงแค่สร้างแผนที่การค้นหาก่อนในคำตอบอื่น ๆ


-1
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}


-2

คุณสามารถใช้Enum::valueOf()ฟังก์ชั่นตามที่ Gareth Davis & Brad Mace แนะนำข้างต้น แต่ให้แน่ใจว่าคุณจัดการกับสิ่งIllegalArgumentExceptionที่จะถูกโยนถ้าสตริงที่ใช้ไม่ได้อยู่ใน enum

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.