我应该严格避免在Android上使用枚举吗?


92

我曾经Bundle在如下所示的接口中一起定义了一组相关的常量(如键):

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

这为我提供了一种更好的方式将相关常量分组在一起,并通过进行静态导入(而非实现)来使用它们。我知道Android框架也使用像一样的常量Toast.LENTH_LONGView.GONE

但是,我经常感到,Java Enums提供代表常数的更好,更强大的方法。

但是使用enumson 会有性能问题Android吗?

经过一番研究,我最终陷入了混乱。从这个问题中, 从Android的性能提示中删除了“避免枚举,您只需要整数”吗?很明显,Google它已从性能提示中删除了“避免枚举”,但是从官方培训文档中请注意内存开销部分,它明确指出:“枚举通常需要的内存是静态常量的两倍以上。您应严格避免在Android上使用枚举。”这仍然有效吗?(例如Java1.6以后的版本)

还有一个问题,我观察到的是发送enumsintents使用Bundle我应该序列化给他们(即putSerializable(),我认为一个昂贵的操作比较原始的putString()方法沉绵enums免费提供的话)。

有人可以说清楚哪一种是代表同一个人的最佳方法Android吗?我应该严格避免使用enumson Android吗?


5
您应该使用可用的工具。实际上,一个活动或片段会占用大量内存和CPU使用率,但这并不是停止使用它们的理由。如果只需要一个静态整数,则在需要时使用枚举。
Patrick

11
我同意。这闻起来像过早的优化。除非您遇到性能和/或内存问题,并且可以通过分析证明枚举是原因,否则请在合理的地方使用它们。
GreyBeardedGeek 2015年

2
过去曾有人认为枚举会带来非常规的性能损失,但最近的基准测试表明使用常量代替没有好处。见stackoverflow.com/questions/24491160/...以及stackoverflow.com/questions/5143256/...
本杰明·塞尔让

1
为了避免对Bundle中的Enum进行序列化带来的性能损失,您可以Enum.ordinal()改为使用int将其作为int传递。
BladeCoder

1
终于在这里对Enum的性能问题做了一些解释 youtube.com/watch?v=Hzs6OBcvNQE
nvinayshetty

Answers:


115

enum需要其功能时使用。不要严格避免它

Java枚举功能更强大,但是如果您不需要它的功能,请使用常量,它们会占用较少的空间,并且它们本身可以是原始的。

何时使用枚举:

  • 类型检查-你可以接受列出的值,而且他们是不连续的(见下面就是我所说的连续点击这里)
  • 方法重载-每个枚举常量都有自己的方法实现

    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);
    }
  • 更多数据-您的一个常数包含多个不能放在一个变量中的信息

  • 复杂的数据-您不断需要的方法来处理数据

何时使用枚举:

  • 您可以接受一种类型的所有值,并且您的常数仅包含这些最常用的值
  • 您可以接受连续数据

    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);
            }
    
            ...
        }
    }
  • 用于名称(例如您的示例)
  • 对于真的不需要枚举的其他所有内容

枚举占用更多空间

  • 一个枚举常量的单个引用占用4个字节
  • 每个枚举常量占用的空间是其字段大小总和,对齐方式是8字节+ 对象的开销
  • 枚举类本身占用一些空间

常量占用更少的空间

  • 常量没有引用,因此它是纯数据(即使是引用,枚举实例也将是对另一个引用的引用)
  • 常量可以添加到现有类中-不必添加其他类
  • 常量可以内联;它带来了扩展的编译时功能(例如,空检查,查找无效代码等)

2
您可以使用@IntDef批注来模拟int常量的类型检查。支持Java枚举的说法少了一个。
BladeCoder

3
@BladeCoder不,您不需要引用常量
Kamil Jarosz

1
另外,请注意,使用枚举可促进反射的使用。众所周知,反射的使用对Android而言是巨大的性能影响。看到这里:blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
w3bshark

3
@ w3bshark枚举如何促进反射的使用?
凯文·克鲁姆维德

3
要记住的另一件事是,从标准的空“ Hello,world!”中进行堆转储。该项目包括4,000多个类和700,000个对象。即使您有一个包含数千个常量的可笑的巨大枚举,您也可以确定,在Android框架本身膨胀之后,性能影响可以忽略不计。
凯文·克鲁姆维德

57

如果枚举仅包含值,则应尝试使用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);

如果枚举很复杂,请使用枚举。它没有那么坏。

要比较枚举与常量值,应在此处阅读:

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

他们的示例是一个具有2个值的枚举。与使用常量整数时的128个字节相比,它在dex文件中需要1112个字节。有意义,因为枚举是真实的类,而不是它在C / C ++上的工作方式。


尽管我很欣赏其他提供枚举优点/缺点的答案,但该答案应为公认的答案,因为它特别为Android提供了最佳解决方案。支持注释是必经之路。作为Android开发人员,我们应该考虑“除非我有充分的理由使用枚举,否则我将使用带注释的常量”,而不是其他答案的思考方式“除非我稍后开始担心内存/性能,我可以使用枚举”。立即阻止您以后再遇到性能问题!
w3bshark '16

3
@ w3bshark如果遇到性能问题,枚举不太可能是您应该首先解决的问题。注释有其自身的取舍:它们没有编译器支持,它们没有自己的字段和方法,它们繁琐的编写,您很容易忘记注释int,依此类推。它们并不是整体上更好的解决方案,它们只是在需要时节省堆空间。
马尔科姆

1
@androiddeveloper您不能通过传递未在enum声明中定义的常量来犯错。此代码永远不会编译。如果您传递带有IntDef注释的错误常量,它将会。至于手表,我认为也很清楚。哪个设备具有更多的CPU电源,RAM和电池:电话还是手表?因此,哪些软件需要针对性能进行更优化的软件?
Malcolm

3
@androiddeveloper好吧,如果您坚持使用严格的术语,那么我所指的错误不是编译时错误(运行时或程序逻辑错误)。您是在骗我还是真的没有得到两者之间的区别以及编译时错误的好处?这是一个讨论充分的话题,请在网上查找。关于手表,Android确实包含更多设备,但受约束最大的设备将是最低公分母。
马尔科姆

2
@androiddeveloper我已经响应了这两个语句,再次重复它们不会使我所说的无效。
Malcolm'1

12

除了以前的答案,我还要补充一点,如果您使用的是Proguard(并且您绝对应该这样做以减小大小并混淆代码),那么您Enums将被自动转换@IntDef为可能的位置:

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

类/拆箱/枚举

尽可能将枚举类型简化为整数常量。

因此,如果您有一些离散值,并且某些方法只允许采用此值,而不允许采用其他相同类型的值,那么我将使用Enum,因为Proguard会为我进行优化代码的手动工作。

这里是 如何使用枚举从杰克沃顿好的帖子,看看它。

作为库开发人员,我认识到应该进行这些小的优化,因为我们希望对使用的应用程序的大小,内存和性能影响尽可能小。但是,重要的是要意识到,在适当的情况下将枚举放入公共API与整数值中是完全可以的。了解差异以做出明智的决定很重要


11

我应该严格避免在Android上使用枚举吗?

不会。“ 严格 ”表示它们太糟糕了,根本不要使用它们。可能在极端情况下会出现性能问题,例如使用枚举(在ui线程上连续执行)的许多许多(数千或数百万)操作。网络I / O操作非常常见,应严格在后台线程中进行。枚举的最常见的用法可能是某种类型的检查-一个对象是否是这个那个它是如此之快,你将无法注意到枚举的比较单一和整数的比较之间的差异。

有人可以说明哪种方法是在Android中代表同一方法的最佳方法吗?

没有一般的经验法则。使用对您有用的任何东西,并帮助您准备好应用程序。稍后进行优化-在您发现瓶颈会减慢应用程序的某些方面之后。


1
当常量与支持注释一起使用并立即进行优化时,为什么以后还要优化呢?在这里查看@android_developer的答案。
w3bshark '16


4

两个事实。

1,枚举是JAVA中最强大的功能之一。

2,Android手机通常有很多内存。

所以我的回答是不。我将在Android中使用Enum。


2
如果Android拥有大量内存,那并不意味着您的应用程序应该消耗掉所有内存,而不会剩下任何内存。您应遵循最佳做法,以免您的应用被Android OS杀死。我并不是说不要使用枚举,而只能在没有其他选择的情况下使用。
Varundroid

1
我同意您的看法,我们应该遵循最佳实践,但是,最佳实践并不总是意味着使用最少的内存。保持代码整洁,易于理解比保存几千个内存重要得多。您需要找到一个平衡点。
Kai Wang

1
我同意您的意见,我们需要找到一个平衡点,但是使用TypeDef不会使我们的代码看起来很糟糕或难以维护。我使用它已经一年多了,从来没有觉得我的代码不容易理解,而且我的同龄人都没有抱怨过与枚举相比TypeDefs很难理解。我的建议是仅在没有其他选择的情况下使用枚举,但如果可以的话,请避免使用它。
Varundroid

2

我想补充一点,当您声明List <>或Map <>时,如果键或值是您的注释接口之一,则不能使用@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组则不能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.