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

我过去常常在一个接口中定义一组相关的常量,比如 Bundle键,如下所示:

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

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

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

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

经过一番研究,我对这个问题感到困惑 从 Android 的性能提示中删除“避免只需要 Ints 的枚举”?很明显,Google已经把 “避免枚举”从它的表现技巧中删除了,但是从它的官方培训文档 注意内存开销部分它清楚地说: 枚举通常需要比静态常量多两倍的内存。您应该严格避免在 Android 上使用枚举仍然保持良好吗?(例如在1.6以后的 Java版本)

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

有人可以澄清哪一个是最好的方式来表示在 Android相同?我是否应该严格避免在 Android上使用 enums

45888 次浏览

Should I strictly avoid using enums on Android?

No. "Strictly" means they are so bad, they should not be used at all. Possibly a performance issues might arise in an extreme situation like many many many (thousands or millions of) operations with enums (consecutive on the ui thread). Far more common are the network I/O operations that should strictly happen in a background thread. The most common usage of enums is probably some kind of type check - whether an object is this or that which is so fast you won't be able to notice a difference between a single comparison of enums and a comparison of integers.

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

There is no general rule of thumb for this. Use whatever works for you and helps you get your app ready. Optimize later - after you notice there's a bottleneck that slows some aspect of your app.

Use enum when you need its features. Don't avoid it strictly.

Java enum is more powerful, but if you don't need its features, use constants, they occupy less space and they can be primitive itself.

When to use enum:

  • type checking - you can accept only listed values, and they are not continuous (see below what I call continuous here)
  • method overloading - every enum constant has its own implementation of a method

    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);
    }
    
  • more data - your one constant contains more than one information that cannot be put in one variable

  • complicated data - your constant need methods to operate on the data

When not to use enum:

  • you can accept all values of one type, and your constants contain only these most used
  • you can accept continuous data

    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);
    }
    
    
    ...
    }
    }
    
  • for names (like in your example)
  • for everything else that really doesn't need an enum

Enums occupy more space

  • a single reference to an enum constant occupies 4 bytes
  • every enum constant occupies space that is a sum of its fields' sizes aligned to 8 bytes + overhead of the object
  • the enum class itself occupies some space

Constants occupy less space

  • a constant doesn't have a reference so it's a pure data (even if it's a reference, then enum instance would be a reference to another reference)
  • constants may be added to an existing class - it's not necessary to add another class
  • constants may be inlined; it brings extended compile-time features (such as null checking, finding dead code etc.)

If the enums simply have values, you should try to use IntDef/StringDef , as shown here:

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

Example: instead of :

enum NavigationMode {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}

you use:

@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;

and in the function that has it as a parameter/returned value , use:

@NavigationMode
public abstract int getNavigationMode();


public abstract void setNavigationMode(@NavigationMode int mode);

In case the enum is complex, use an enum. It's not that bad.

To compare enums vs constant values, you should read here:

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

Their example is of an enum with 2 values. It takes 1112 bytes in dex file compared to 128 bytes when constant integers are used . Makes sense, as enums are real classes, as opposed to how it works on C/C++ .

I like to add, that you can not use @Annotations when you declare a List<> or Map<> where either key or value is of one of your annotation interfaces. You get the error "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 ***

So when you need to pack it into a list/map, use enum, as they can be added, but @annotated int/string groups can not.

In addition to previous answers, I would add that if you are using Proguard (and you should definitely do it to reduce size and obfuscate your code), then your Enums will be automatically converted to @IntDef wherever it is possible:

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

class/unboxing/enum

Simplifies enum types to integer constants, whenever possible.

Therefore, if you have some discrete values and some method should allow to take only this values and not others of the same type, then I would use Enum, because Proguard will make this manual work of optimizing code for me.

And here is a good post about using enums from Jake Wharton, take a look at it.

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

Two facts.

1, Enum is one of the most powerful feature in JAVA.

2, Android phone usually has a LOT of memory.

So my answer is NO. I will use Enum in Android.

With Android P, google has no restriction/objection in using enums

The documentation has changed where before it was recommended to be cautious but it doesn't mention it now. https://developer.android.com/reference/java/lang/Enum