ฉันควรหลีกเลี่ยงการใช้ enums บน Android อย่างเคร่งครัดหรือไม่


93

ฉันเคยกำหนดชุดของค่าคงที่ที่เกี่ยวข้องเช่นBundleคีย์ด้วยกันในอินเทอร์เฟซดังต่อไปนี้:

public interface From{
    String LOGIN_SCREEN = "LoginSCreen";
    String NOTIFICATION = "Notification";
    String WIDGET = "widget";
}

นี่เป็นวิธีที่ดีกว่าในการจัดกลุ่มค่าคงที่ที่เกี่ยวข้องเข้าด้วยกันและใช้โดยการนำเข้าแบบคงที่ (ไม่ใช่การใช้งาน) ฉันรู้ว่าAndroidกรอบการทำงานยังใช้ค่าคงที่ในลักษณะเดียวกันเช่น,Toast.LENTH_LONGView.GONE

อย่างไรก็ตามฉันมักรู้สึกว่าJava Enumsวิธีนี้ให้วิธีที่ดีกว่าและมีประสิทธิภาพมากในการแสดงค่าคงที่

แต่มีปัญหาในการใช้งานenumsบนAndroid?

ด้วยการค้นคว้าเล็กน้อยฉันจบลงด้วยความสับสน จากคำถามนี้ "หลีกเลี่ยง Enums ในที่ที่คุณต้องการเพียง Ints" ถูกลบออกจากเคล็ดลับประสิทธิภาพของ Android เป็นที่ชัดเจนว่าGoogleได้ลบ"หลีกเลี่ยง enums"ออกจากเคล็ดลับการปฏิบัติงาน แต่จากเอกสารการฝึกอบรมอย่างเป็นทางการโปรดทราบส่วนค่าใช้จ่ายของหน่วยความจำจะระบุไว้อย่างชัดเจนว่า: "Enums มักต้องการหน่วยความจำมากกว่าค่าคงที่มากกว่าสองเท่า คุณควรหลีกเลี่ยงการใช้ enums บน Android อย่างเคร่งครัด "สิ่งนี้ยังใช้ได้ดีอยู่หรือไม่ (พูดในJavaเวอร์ชันหลัง 1.6)

อีกปัญหาหนึ่งที่ฉันสังเกตเห็นคือการส่งenumsข้ามintentsโดยใช้Bundleฉันควรส่งโดยการทำให้เป็นอนุกรม (เช่นputSerializable()ฉันคิดว่าการดำเนินการที่มีราคาแพงเมื่อเทียบกับputString()วิธีดั้งเดิมแม้ว่าจะenumsให้ฟรี)

ใครช่วยอธิบายได้Androidไหมว่าวิธีใดเป็นวิธีที่ดีที่สุดในการแสดงความคิดเห็น ฉันควรจะหลีกเลี่ยงการใช้อย่างเคร่งครัดenumsในAndroid?


5
คุณควรใช้เครื่องมือที่คุณมี ในความเป็นจริงกิจกรรมหรือส่วนย่อยต้องใช้หน่วยความจำและการใช้งาน cpu เป็นจำนวนมาก แต่นั่นไม่ใช่เหตุผลที่จะหยุดใช้ ใช้ int แบบคงที่หากคุณต้องการเพียงแค่นั้นและใช้ enums เมื่อคุณต้องการ
Patrick

11
ฉันเห็นด้วย. สิ่งนี้มีกลิ่นเหมือนการเพิ่มประสิทธิภาพก่อนเวลาอันควร เว้นแต่คุณจะมีปัญหาด้านประสิทธิภาพและ / หรือหน่วยความจำและสามารถพิสูจน์ได้ผ่านการทำโปรไฟล์ว่า enums เป็นสาเหตุให้ใช้ในที่ที่เหมาะสม
GreyBeardedGeek

2
เคยเป็นที่เชื่อกันว่า enums ได้รับการลงโทษด้านประสิทธิภาพที่ไม่สามารถเอาชนะได้ แต่เกณฑ์มาตรฐานล่าสุดแสดงให้เห็นว่าไม่มีประโยชน์ที่จะใช้ค่าคงที่แทน ดูstackoverflow.com/questions/24491160/…และstackoverflow.com/questions/5143256/…
Benjamin Sergent

1
เพื่อหลีกเลี่ยงผลเสียจากการทำให้ Enum เป็นอนุกรมใน Bundle คุณสามารถส่งต่อเป็น int Enum.ordinal()แทน
BladeCoder

1
ในที่สุดก็มีคำอธิบายเกี่ยวกับปัญหาด้านประสิทธิภาพของ Enum ที่นี่ youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

คำตอบ:


116

ใช้enumเมื่อคุณต้องการคุณสมบัติ อย่าหลีกเลี่ยงได้อย่างเคร่งครัด

Java enum มีประสิทธิภาพมากกว่า แต่ถ้าคุณไม่ต้องการคุณสมบัติให้ใช้ค่าคงที่พวกมันใช้พื้นที่น้อยลงและสามารถเป็นแบบดั้งเดิมได้

เมื่อใดควรใช้ enum:

  • พิมพ์การตรวจสอบ - คุณสามารถยอมรับเฉพาะค่าที่ระบุไว้และไม่ต่อเนื่อง (ดูด้านล่างสิ่งที่ฉันเรียกว่าต่อเนื่องที่นี่)
  • วิธีการโอเวอร์โหลด - ค่าคงที่ทุกค่ามีการใช้วิธีการของตัวเอง

    public enum UnitConverter{
        METERS{
            @Override
            public double toMiles(final double meters){
                return meters * 0.00062137D;
            }
    
            @Override
            public double toMeters(final double meters){
                return meters;
            }
        },
        MILES{
            @Override
            public double toMiles(final double miles){
                return miles;
            }
    
            @Override
            public double toMeters(final double miles){
                return miles / 0.00062137D;
            }
        };
    
        public abstract double toMiles(double unit);
        public abstract double toMeters(double unit);
    }
    
  • ข้อมูลเพิ่มเติม - ค่าคงที่หนึ่งของคุณมีข้อมูลมากกว่าหนึ่งข้อมูลที่ไม่สามารถใส่ในตัวแปรเดียวได้

  • ข้อมูลที่ซับซ้อน - วิธีการที่คุณต้องการอย่างต่อเนื่องในการดำเนินการกับข้อมูล

เมื่อไม่ใช้ enum:

  • คุณสามารถยอมรับค่าทั้งหมดของประเภทเดียวและค่าคงที่ของคุณมีเพียงค่าที่ใช้มากที่สุดเท่านั้น
  • คุณสามารถรับข้อมูลต่อเนื่องได้

    public class Month{
        public static final int JANUARY = 1;
        public static final int FEBRUARY = 2;
        public static final int MARCH = 3;
        ...
    
        public static String getName(final int month){
            if(month <= 0 || month > 12){
                throw new IllegalArgumentException("Invalid month number: " + month);
            }
    
            ...
        }
    }
    
  • สำหรับชื่อ (เช่นในตัวอย่างของคุณ)
  • สำหรับทุกสิ่งทุกอย่างที่ไม่จำเป็นต้องมี enum

Enums ใช้พื้นที่มากขึ้น

  • การอ้างอิงเดียวไปยังค่าคงที่ enum ใช้พื้นที่4 ไบต์
  • ค่าคงที่ทุกค่าจะใช้พื้นที่ซึ่งเป็นผลรวมของขนาดของฟิลด์ที่สอดคล้องกับ 8 ไบต์ + ค่าโสหุ้ยของวัตถุ
  • คลาส enum นั้นใช้พื้นที่บางส่วน

ค่าคงที่ใช้พื้นที่น้อย

  • ค่าคงที่ไม่มีการอ้างอิงดังนั้นจึงเป็นข้อมูลที่บริสุทธิ์ (แม้ว่าจะเป็นการอ้างอิง แต่อินสแตนซ์ enum จะเป็นการอ้างอิงไปยังการอ้างอิงอื่น)
  • ค่าคงที่อาจถูกเพิ่มลงในคลาสที่มีอยู่ - ไม่จำเป็นต้องเพิ่มคลาสอื่น
  • ค่าคงที่อาจอยู่ในบรรทัด มันนำคุณสมบัติเวลาคอมไพล์แบบขยาย (เช่นการตรวจสอบโมฆะการค้นหารหัสตายเป็นต้น)

2
คุณสามารถใช้คำอธิบายประกอบ @IntDef เพื่อจำลองการตรวจสอบประเภทสำหรับค่าคงที่ int เป็นอาร์กิวเมนต์น้อยกว่าหนึ่งข้อที่สนับสนุน Java Enums
BladeCoder

3
@BladeCoder ไม่คุณไม่จำเป็นต้องอ้างอิงถึงค่าคงที่
Kamil Jarosz

1
นอกจากนี้โปรดทราบว่าการใช้ enums ส่งเสริมการใช้การสะท้อนกลับ และเป็นที่ทราบกันดีว่าการใช้การสะท้อนกลับเป็นการเพิ่มประสิทธิภาพอย่างมากสำหรับ Android ดูที่นี่: blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark

3
@ w3bshark enums ส่งเสริมการใช้การสะท้อนอย่างไร?
Kevin Krumwiede

3
สิ่งที่ควรทราบอีกประการหนึ่งคือฮีปดัมพ์จากมาตรฐานว่างเปล่า "Hello, world!" โครงการประกอบด้วยคลาสมากกว่า 4,000 คลาสและวัตถุ 700,000 ชิ้น แม้ว่าคุณจะมี enum ขนาดใหญ่ที่น่าหัวเราะและมีค่าคงที่นับพัน แต่คุณสามารถมั่นใจได้ว่าเอฟเฟกต์ประสิทธิภาพจะไม่สำคัญถัดจากการขยายตัวของเฟรมเวิร์ก Android
Kevin Krumwiede

58

หาก enums มีค่าคุณควรลองใช้ IntDef / StringDef ดังที่แสดงไว้ที่นี่:

https://developer.android.com/studio/write/annotations.html#enum-annotations

ตัวอย่าง: แทน:

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS} 

คุณใช้:

@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

และในฟังก์ชันที่มีเป็นพารามิเตอร์ / ค่าที่ส่งคืนให้ใช้:

@NavigationMode
public abstract int getNavigationMode();

public abstract void setNavigationMode(@NavigationMode int mode);

ในกรณีที่ enum มีความซับซ้อนให้ใช้ enum มันไม่ได้แย่ขนาดนั้น

ในการเปรียบเทียบ enums เทียบกับค่าคงที่คุณควรอ่านที่นี่:

http://hsc.com/Blog/Best-Practices-For-Memory-Optimization-on-Android-1

ตัวอย่างของพวกเขาคือ enum ที่มี 2 ค่า ใช้เวลา 1112 ไบต์ในไฟล์ dex เทียบกับ 128 ไบต์เมื่อใช้จำนวนเต็มคงที่ มีเหตุผลเนื่องจาก enums เป็นคลาสจริงเมื่อเทียบกับวิธีการทำงานบน C / C ++


แม้ว่าฉันจะพอใจกับคำตอบอื่น ๆ ที่ให้ข้อดี / ข้อเสียของ enums แต่คำตอบนี้ควรเป็นคำตอบที่ยอมรับได้เนื่องจากเป็นทางออกที่ดีที่สุดสำหรับ Android โดยเฉพาะ คำอธิบายประกอบการสนับสนุนเป็นวิธีที่จะไป เราในฐานะนักพัฒนา Android ควรคิดว่า "เว้นแต่ฉันจะมีเหตุผลที่ชัดเจนในการใช้ enums ฉันจะใช้ค่าคงที่พร้อมคำอธิบายประกอบ" แทนที่จะใช้วิธีคิด "คำตอบอื่น ๆ " เว้นแต่ฉันจะเริ่มกังวลเกี่ยวกับหน่วยความจำ / ประสิทธิภาพในภายหลัง ฉันสามารถใช้ enums ". หยุดตัวเองไม่ให้มีปัญหาด้านประสิทธิภาพในภายหลัง!
w3bshark

3
@ w3bshark หากคุณมีปัญหาด้านประสิทธิภาพ enums ไม่น่าจะเป็นสิ่งแรกที่คุณควรคิดเพื่อแก้ปัญหาเหล่านี้ คำอธิบายประกอบมีข้อเสียของตัวเอง: พวกเขาไม่มีการสนับสนุนคอมไพเลอร์ไม่มีฟิลด์และวิธีการของตัวเองพวกเขาน่าเบื่อที่จะเขียนคุณสามารถลืมใส่คำอธิบายประกอบ int และอื่น ๆ ได้อย่างง่ายดาย โดยรวมแล้วไม่ใช่วิธีแก้ปัญหาที่ดีกว่า แต่มีไว้เพื่อประหยัดพื้นที่ฮีปเมื่อคุณต้องการ
Malcolm

1
@androiddeveloper คุณไม่สามารถทำผิดพลาดโดยส่งค่าคงที่ที่ไม่ได้กำหนดไว้ในการประกาศ enum รหัสนี้จะไม่รวบรวม หากคุณส่งค่าคงที่ผิดด้วยIntDefคำอธิบายประกอบก็จะ สำหรับนาฬิกาฉันคิดว่ามันก็ค่อนข้างชัดเจนเช่นกัน อุปกรณ์ใดมีพลังงาน CPU, RAM และแบตเตอรี่มากกว่า: โทรศัพท์หรือนาฬิกา และซอฟต์แวร์ใดที่จำเป็นต้องได้รับการปรับแต่งให้มีประสิทธิภาพมากขึ้นด้วยเหตุนี้?
Malcolm

3
@androiddeveloper ตกลงถ้าคุณยืนยันในคำศัพท์ที่เข้มงวดโดยความผิดพลาดฉันหมายถึงข้อผิดพลาดซึ่งไม่ใช่ข้อผิดพลาดในการคอมไพล์ (ข้อผิดพลาดรันไทม์หรือตรรกะของโปรแกรม) คุณหลอกฉันหรือไม่ได้รับความแตกต่างระหว่างข้อผิดพลาดและประโยชน์ของข้อผิดพลาดเวลาคอมไพล์ นี่เป็นหัวข้อที่มีการพูดคุยกันเป็นอย่างดีดูได้จากเว็บ เกี่ยวกับนาฬิกา Android มีอุปกรณ์มากกว่า แต่อุปกรณ์ที่มีข้อ จำกัด มากที่สุดจะเป็นตัวหารร่วมที่ต่ำที่สุด
Malcolm

2
@androiddeveloper ฉันได้ตอบสนองต่อข้อความทั้งสองแล้วการทำซ้ำอีกครั้งไม่ทำให้สิ่งที่ฉันพูดไป
Malcolm

12

นอกเหนือจากคำตอบก่อนหน้านี้ฉันจะเพิ่มว่าหากคุณใช้ Proguard (และคุณควรทำเพื่อลดขนาดและทำให้โค้ดของคุณสับสน) จากนั้นรหัสของคุณEnumsจะถูกแปลงโดยอัตโนมัติ@IntDefทุกที่ที่เป็นไปได้:

https://www.guardsquare.com/en/proguard/manual/optimizations

คลาส / unboxing / enum

ลดความซับซ้อนของประเภท enum ให้เป็นค่าคงที่จำนวนเต็มเมื่อทำได้

ดังนั้นหากคุณมีค่าที่ไม่ต่อเนื่องและวิธีการบางอย่างควรอนุญาตให้ใช้เฉพาะค่านี้ไม่ใช่ค่าอื่น ๆ ที่เป็นประเภทเดียวกันฉันจะใช้Enumเพราะ Proguard จะทำให้โค้ดเพิ่มประสิทธิภาพด้วยตนเองนี้สำหรับฉัน

และนี่คือ โพสต์ดีๆเกี่ยวกับการใช้ enums จาก Jake Wharton ลองดูที่มัน

ในฐานะนักพัฒนาไลบรารีฉันตระหนักดีว่าการเพิ่มประสิทธิภาพเล็ก ๆ เหล่านี้ควรทำตามที่เราต้องการให้มีผลกระทบต่อขนาดหน่วยความจำและประสิทธิภาพของแอปที่ใช้งานน้อยที่สุดเท่าที่จะทำได้ แต่สิ่งสำคัญคือต้องตระหนักว่า [... ] การใส่ enum ใน API สาธารณะของคุณเทียบกับค่าจำนวนเต็มตามความเหมาะสมนั้นดีอย่างสมบูรณ์แบบ การรู้ความแตกต่างเพื่อตัดสินใจอย่างชาญฉลาดคือสิ่งที่สำคัญ


11

ฉันควรหลีกเลี่ยงการใช้ enums บน Android อย่างเคร่งครัดหรือไม่

ไม่ " อย่างเคร่งครัด " หมายความว่าพวกเขาแย่มากไม่ควรใช้เลย เป็นไปได้ว่าปัญหาด้านประสิทธิภาพอาจเกิดขึ้นในสถานการณ์ที่รุนแรงเช่นการดำเนินการจำนวนมาก (หลายพันหรือหลายล้านครั้ง) ที่มี enums (ต่อเนื่องกันในเธรด UI) โดยทั่วไปแล้วการดำเนินการ I / O ของเครือข่ายที่ควรเกิดขึ้นในเธรดพื้นหลังอย่างเคร่งครัด การใช้ enums ที่พบบ่อยที่สุดน่าจะเป็นการตรวจสอบประเภทหนึ่งไม่ว่าวัตถุจะเป็นสิ่งนี้หรือสิ่งที่เร็วมากคุณจะไม่สามารถสังเกตเห็นความแตกต่างระหว่างการเปรียบเทียบ enums เดียวกับการเปรียบเทียบจำนวนเต็ม

ใครช่วยอธิบายได้ไหมว่าวิธีใดเป็นวิธีที่ดีที่สุดในการแสดงสิ่งเดียวกันใน Android

ไม่มีกฎทั่วไปสำหรับเรื่องนี้ ใช้อะไรก็ได้ที่เหมาะกับคุณและช่วยเตรียมแอปให้พร้อม เพิ่มประสิทธิภาพในภายหลัง - หลังจากที่คุณสังเกตเห็นว่ามีปัญหาคอขวดที่ทำให้แอปของคุณทำงานช้าลง


1
ทำไมคุณถึงปรับให้เหมาะสมในภายหลังในเมื่อมันง่ายพอ ๆ กับการใช้ค่าคงที่พร้อมคำอธิบายประกอบการสนับสนุนและเพิ่มประสิทธิภาพในตอนนี้ ดูคำตอบของ @ android_developer ที่นี่
w3bshark

11

ด้วย Android P Google ไม่มีข้อ จำกัด / คัดค้านในการใช้ enums

เอกสารมีการเปลี่ยนแปลงที่ก่อนหน้านี้แนะนำให้ใช้ความระมัดระวัง แต่ไม่ได้กล่าวถึงตอนนี้ https://developer.android.com/reference/java/lang/Enum


1
มีหลักฐานอื่น ๆ นอกเหนือจากนี้ผมพบว่าคำสั่งเกี่ยวกับที่ @JakeWharton: twitter.com/jakewharton/status/1067790191237181441 ทุกคนสามารถตรวจสอบ bytecode
โซเชียล

4

ข้อเท็จจริงสองประการ

1, Enum เป็นหนึ่งในคุณสมบัติที่ทรงพลังที่สุดใน JAVA

2, โทรศัพท์ Android มักจะมีหน่วยความจำมากมาย

ดังนั้นคำตอบของฉันคือไม่ ฉันจะใช้ Enum ใน Android


2
หาก Android มีหน่วยความจำมากนั่นไม่ได้หมายความว่าแอปของคุณควรใช้มันทั้งหมดและไม่ปล่อยให้แอปอื่น ๆ คุณควรปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดเพื่อปกป้องแอปของคุณจากการถูก Android OS ฆ่า ฉันไม่ได้บอกว่าอย่าใช้ enums แต่ใช้เฉพาะเมื่อคุณไม่มีทางเลือกอื่น
Varundroid

1
ฉันเห็นด้วยกับคุณว่าเราควรปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดอย่างไรก็ตามแนวทางปฏิบัติที่ดีที่สุดไม่ได้หมายถึงการใช้ความจำน้อยที่สุดเสมอไป การรักษารหัสให้สะอาดเข้าใจง่ายมีความสำคัญมากกว่าการบันทึกหน่วยความจำ k หลายเท่า คุณต้องหาจุดสมดุล
Kai Wang

1
ฉันเห็นด้วยกับคุณว่าเราต้องหาจุดสมดุล แต่การใช้ TypeDef จะไม่ทำให้โค้ดของเราดูแย่หรือบำรุงรักษาได้น้อยลง ฉันใช้มันมานานกว่าหนึ่งปีแล้วและไม่เคยรู้สึกว่ารหัสของฉันไม่เข้าใจง่ายแถมยังไม่มีเพื่อนร่วมงานคนไหนบ่นว่า TypeDefs นั้นเข้าใจยากเมื่อเทียบกับ enums คำแนะนำของฉันคือใช้ enums เฉพาะเมื่อคุณไม่มีทางเลือกอื่น แต่ควรหลีกเลี่ยงถ้าทำได้
Varundroid

2

ฉันต้องการเพิ่มว่าคุณไม่สามารถใช้ @Annotations เมื่อคุณประกาศรายการ <> หรือแผนที่ <> โดยที่คีย์หรือค่าเป็นหนึ่งในอินเทอร์เฟซคำอธิบายประกอบของคุณ คุณได้รับข้อผิดพลาด "ไม่อนุญาตให้ใช้คำอธิบายประกอบที่นี่"

enum Values { One, Two, Three }
Map<String, Values> myMap;    // This works

// ... but ...
public static final int ONE = 1;
public static final int TWO = 2;
public static final int THREE = 3;

@Retention(RetentionPolicy.SOURCE)
@IntDef({ONE, TWO, THREE})
public @interface Values {}

Map<String, @Values Integer> myMap;    // *** ERROR ***

ดังนั้นเมื่อคุณต้องการบรรจุลงในรายการ / แผนที่ให้ใช้ enum เนื่องจากสามารถเพิ่มได้ แต่ @annotated int / string groups ไม่สามารถทำได้

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