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

Should I strictly avoid using enums on Android?

我曾经在如下界面中一起定义一组相关常量,如 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? it's clear that Google has removed "Avoid enums" from its performance tips, but from it's official training docs Be aware of memory overhead 部分清楚地说: "Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android." 这仍然有效吗? (比如 1.6 之后的 Java 个版本)

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

有人可以阐明在 Android 中表示相同内容的最佳方式是哪一个吗?我应该严格避免在 Android 上使用 enums 吗?

Should I strictly avoid using enums on Android?

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

Can someone please clarify which one is the best way to represent the same in Android?

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

当您需要它的功能时使用enum不要回避严格.

Java枚举更强大,但如果你不需要它的特性,使用常量,它们占用更少space并且它们本身可以是原始的。

何时使用枚举:

  • 类型检查 - 你可以接受 列出的值,并且它们不是连续的(请参阅下面我称之为 continuous 的内容)
  • 方法重载 - 每个枚举常量都有自己的方法实现

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

枚举占用较多space

  • 对枚举常量的单个引用占用 4 个字节
  • 每个枚举常量占用 space,即 其字段大小的总和 对齐到 8 个字节 + 对象的开销
  • 枚举class本身占用了一些space

常量占用较少space

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

如果枚举只有值,您应该尝试使用 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;

并在将其作为 parameter/returned 值的函数中,使用:

@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++ 上的工作方式。

我想补充一点,当您声明一个 List<> 或 Map<> 时,您不能使用 @Annotations,其中键或值属于您的注释接口之一。 您收到错误 "Annotations are not allowed here".

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 ***

所以当你需要把它打包成一个list/map时,使用枚举,因为它们可以添加,但是@annotated int/string组不能。

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

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

class/unboxing/enum

Simplifies enum types to integer constants, whenever possible.

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

还有 here is 关于使用 Jake Wharton 的枚举的好post,请看一看。

As a library developer, I recognize these small optimizations that should be done as we want to have as little impact on the consuming app's size, memory, and performance as possible. But it's important to realize that [...] putting an enum in your public API vs. integer values where appropriate is perfectly fine. Knowing the difference to make informed decisions is what's important

两个事实。

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

2, Android phone 通常有很多内存。

所以我的回答是否定的。我将在 Android.

中使用枚举

有了Android P,google没有restriction/objection使用枚举

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