【问题标题】:Should I strictly avoid using enums on Android?我应该严格避免在 Android 上使用枚举吗?
【发布时间】:2015-05-24 21:18:38
【问题描述】:

我曾经在如下界面中定义了一组相关的常量,例如Bundle 键:

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

这为我提供了一种更好的方式来将相关常量组合在一起并通过静态导入(而不是实现)来使用它们。我知道Android 框架也以相同的方式使用常量,例如Toast.LENTH_LONGView.GONE

但是,我经常觉得Java Enums 提供了更好更强大的方式来表示常量。

但是在Android 上使用enums 是否存在性能问题?

通过一些研究,我最终感到困惑。从这个问题 "Avoid Enums Where You Only Need Ints” removed from Android's performance tips? 很明显,Google 已从其性能提示中删除了 "Avoid enums",但在其官方培训文档 Be aware of memory overhead 部分中明确表示:“枚举通常需要超过内存是静态常量的两倍。您应该严格避免在 Android 上使用枚举。" 这仍然成立吗? (比如Java 1.6 之后的版本)

我观察到的另一个问题是使用Bundleintents 发送enums 我应该通过序列化发送它们(即putSerializable(),我认为与原始putString() 方法相比,这是一个昂贵的操作,尽管enums免费提供)。

有人可以澄清在Android 中哪个是代表相同的最佳方式吗?我应该严格避免在Android 上使用enums 吗?

【问题讨论】:

  • 您应该使用现有的工具。事实上,一个活动或片段会占用大量内存和 cpu 使用率,但这不是停止使用它们的理由。如果您只需要它,请使用静态 int,并在需要时使用枚举。
  • 我同意。这闻起来像是过早的优化。除非您遇到性能和/或内存问题,并且可以通过分析证明枚举是原因,否则请在有意义的地方使用它们。
  • 过去人们认为枚举会导致非平凡的性能损失,但最近的基准测试表明使用常量没有任何好处。见stackoverflow.com/questions/24491160/…stackoverflow.com/questions/5143256/…
  • 为了避免在 Bundle 中序列化 Enum 的性能损失,您可以使用 Enum.ordinal() 将其作为 int 传递。
  • 最后这里有一些关于 Enum 性能问题的解释youtube.com/watch?v=Hzs6OBcvNQE

标签: java android enums


【解决方案1】:

当您需要它的功能时,请使用enum不要避免它严格

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

  • 复杂的数据 - 您不断需要对数据进行操作的方法

使用枚举时:

  • 您可以接受一种类型的所有值,而您的常量只包含这些最常用的值
  • 你可以接受连续数据

    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 个字节 + 对象开销
  • 枚举类本身占用了一些空间

常量占用的空间更少

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

【讨论】:

  • 您可以使用@IntDef 注解来模拟int 常量的类型检查。支持 Java 枚举的论据少了一个。
  • @BladeCoder 不,你不需要引用常量
  • 另外,值得注意的是,使用枚举可以促进反射的使用。众所周知,反射的使用对 Android 的性能造成巨大影响。见这里:blog.nimbledroid.com/2016/02/23/slow-Android-reflection.html
  • @w3bshark 枚举如何促进反射的使用?
  • 要记住的另一件事是来自标准空“Hello, world!”的堆转储。项目包括超过 4,000 个类和 700,000 个对象。即使你有一个包含数千个常量的可笑的巨大枚举,你也可以肯定,除了 Android 框架本身的膨胀之外,性能影响可以忽略不计。
【解决方案2】:

如果枚举只是有值,你应该尝试使用 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 个值的枚举。 dex 文件需要 1112 字节,而使用常量整数时需要 128 字节。有道理,因为枚举是真正的类,而不是它在 C/C++ 上的工作方式。

【讨论】:

  • 虽然我很欣赏其他提供枚举优缺点的答案,但这个答案应该是公认的答案,因为它特别为 Android 提供了最佳解决方案。支持注释是要走的路。作为 Android 开发人员,我们应该考虑“除非我有充分的理由使用枚举,否则我将使用带注释的常量”,而不是其他答案的思维方式“除非我以后开始担心内存/性能,我可以使用枚举”。现在阻止自己以后遇到性能问题!
  • @w3bshark 如果您遇到性能问题,枚举不太可能是您应该首先考虑解决的问题。注释有其自身的权衡:它们没有编译器支持,它们不能有自己的字段和方法,它们编写起来很乏味,你很容易忘记注释一个 int,等等。总体而言,它们并不是一个更好的解决方案,它们只是在您需要时节省堆空间。
  • @androiddeveloper 传递枚举声明中未定义的常量不会出错。这段代码永远不会编译。如果您使用 IntDef 注释传递错误的常量,它会。至于手表,我想也很清楚。哪种设备具有更多 CPU 功率、RAM 和电池:手机还是手表?因此,哪些软件需要针对性能进行更多优化?
  • @androiddeveloper 好的,如果你坚持严格的术语,我所说的错误是指不是编译时错误(运行时或程序逻辑错误)的错误。您是在捉弄我还是真的不明白它们之间的区别以及编译时错误的好处?这是一个很好讨论的话题,请在网上查找。关于手表,Android 确实包含更多设备,但最受限制的设备将是最小的公分母。
  • @androiddeveloper 我已经回复了这两个声明,再次重复它们不会使我说的话无效。
【解决方案3】:

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

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

类/拆箱/枚举

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

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

还有here is Jake Wharton 的一篇关于使用枚举的好帖子,看看吧。

作为库开发人员,我认识到应该进行这些小的优化,因为我们希望尽可能减少对消耗应用程序的大小、内存和性能的影响。但重要的是要意识到 [...] 在你的公共 API 中放置一个枚举而不是在适当的地方放置一个整数值是非常好的。了解差异以做出明智的决定很重要

【讨论】:

    【解决方案4】:

    对于 Android P,谷歌在使用枚举方面没有限制/反对

    文档已更改,之前建议谨慎,但现在没有提及。 https://developer.android.com/reference/java/lang/Enum

    【讨论】:

    【解决方案5】:

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

    没有。 "Strictly" 表示它们太糟糕了,根本不应该使用它们。在极端情况下可能会出现性能问题,例如 many many many(数千或数百万)枚举操作(在 ui 线程上连续)。更常见的是应该严格在后台线程中发生的网络 I/O 操作。 枚举最常见的用法可能是某种类型检查 - 一个对象是 this 还是 that,速度如此之快,以至于你无法注意到它们之间的区别枚举的单一比较和整数的比较。

    有人可以澄清哪一个是在 Android 中表示相同的最佳方式吗?

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

    【讨论】:

    • 为什么要在以后进行优化,因为现在可以很容易地使用带有支持注释的常量并进行优化?在此处查看@android_developer 的答案。
    【解决方案6】:

    两个事实。

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

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

    所以我的答案是否定的。我将在 Android 中使用 Enum。

    【讨论】:

    • 如果 Android 有很多内存,这并不意味着您的应用应该使用所有内存,而不会为其他应用留下任何内存。您应该遵循最佳实践,以免您的应用被 Android 操作系统杀死。我并不是说不要使用枚举,而是仅在别无选择时使用。
    • 我同意你的观点,我们应该遵循最佳实践,但是,最佳实践并不总是意味着使用最少的内存。保持代码干净、易于理解远比节省几 k 内存重要。你需要找到一个平衡点。
    • 我同意你的观点,我们需要找到一个平衡点,但使用 TypeDef 不会让我们的代码看起来很糟糕或难以维护。我使用它已经一年多了,从来没有觉得我的代码不容易理解,而且我的同行也没有人抱怨过 TypeDef 比枚举更难理解。我的建议是仅在别无选择时才使用枚举,但如果可以,请避免使用。
    【解决方案7】:

    我想补充一点,当您声明一个 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 ***
    

    所以当你需要将它打包成一个列表/映射时,使用枚举,因为它们可以添加,但@annotated int/string 组不能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-16
      • 1970-01-01
      • 2018-06-16
      • 2010-10-15
      • 2012-10-14
      • 2011-01-17
      • 2011-03-26
      • 2010-09-23
      相关资源
      最近更新 更多