Передача enum або об'єкта через наміри (найкраще рішення)


221

У мене є діяльність, яка при запуску потребує доступу до двох різних списків ArrayLists. Обидва списки - це різні об'єкти, які я створив сам.

В основному мені потрібен спосіб передати ці об'єкти діяльності з наміру. Я можу використовувати addExtras (), але для цього потрібний сумісний з Parceable клас. Я міг би зробити свої заняття серійними, але, як я розумію, це уповільнює програму.

Які мої варіанти?

Чи можу я пройти Enum?

Як відміна: чи є спосіб передати параметри конструктору діяльності з наміру?


Можливо, мені чогось не вистачає, але як перерахунок пов'язаний з ArrayList?
Мартін Конечний

Відповіді:


558

Це давнє запитання, але всі не зазначають, що "Енумс" насправді є, Serializableі тому він цілком може бути доданий до наміру як додаткового. Подобається це:

public enum AwesomeEnum {
  SOMETHING, OTHER;
}

intent.putExtra("AwesomeEnum", AwesomeEnum.SOMETHING);

AwesomeEnum result = (AwesomeEnum) intent.getSerializableExtra("AwesomeEnum");

Пропозиція використовувати статичні чи загальноприйняті змінні - це дійсно погана ідея. Це дійсно поєднує вашу діяльність із системою управління державою, і її важко підтримувати, налагоджувати та вирішувати проблеми.


АЛЬТЕРНАТИВИ:

Добрий момент відмітив tedzyc щодо того, що рішення, яке надає Одерік, дає вам помилку. Однак запропонована альтернатива є трохи громіздкою у використанні (навіть із використанням дженериків).

Якщо ви справді переживаєте про ефективність додавання перерахунку до наміру, я пропоную замість цього альтернативи:

ВАРІАНТ 1:

public enum AwesomeEnum {
  SOMETHING, OTHER;
  private static final String name = AwesomeEnum.class.getName();
  public void attachTo(Intent intent) {
    intent.putExtra(name, ordinal());
  }
  public static AwesomeEnum detachFrom(Intent intent) {
    if(!intent.hasExtra(name)) throw new IllegalStateException();
    return values()[intent.getIntExtra(name, -1)];
  }
}

Використання:

// Sender usage
AwesomeEnum.SOMETHING.attachTo(intent);
// Receiver usage
AwesomeEnum result = AwesomeEnum.detachFrom(intent);

ВАРІАНТ 2: (загальний, багаторазовий та відокремлений від перерахунку)

public final class EnumUtil {
    public static class Serializer<T extends Enum<T>> extends Deserializer<T> {
        private T victim;
        @SuppressWarnings("unchecked") 
        public Serializer(T victim) {
            super((Class<T>) victim.getClass());
            this.victim = victim;
        }
        public void to(Intent intent) {
            intent.putExtra(name, victim.ordinal());
        }
    }
    public static class Deserializer<T extends Enum<T>> {
        protected Class<T> victimType;
        protected String name;
        public Deserializer(Class<T> victimType) {
            this.victimType = victimType;
            this.name = victimType.getName();
        }
        public T from(Intent intent) {
            if (!intent.hasExtra(name)) throw new IllegalStateException();
            return victimType.getEnumConstants()[intent.getIntExtra(name, -1)];
        }
    }
    public static <T extends Enum<T>> Deserializer<T> deserialize(Class<T> victim) {
        return new Deserializer<T>(victim);
    }
    public static <T extends Enum<T>> Serializer<T> serialize(T victim) {
        return new Serializer<T>(victim);
    }
}

Використання:

// Sender usage
EnumUtil.serialize(AwesomeEnum.Something).to(intent);
// Receiver usage
AwesomeEnum result = 
EnumUtil.deserialize(AwesomeEnum.class).from(intent);

ВАРІАНТ 3 (з Котліном):

Минув деякий час, але оскільки у нас є Котлін, я думав, що додам ще один варіант нової парадигми. Тут ми можемо скористатися функціями розширення та зміненими типами (що зберігає тип при компілюванні).

inline fun <reified T : Enum<T>> Intent.putExtra(victim: T): Intent =
    putExtra(T::class.java.name, victim.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(): T? =
    getIntExtra(T::class.java.name, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

Є кілька переваг зробити це таким чином.

  • Нам не потрібно «накладні витрати» посередницького об’єкта, щоб зробити серіалізацію, оскільки це все зроблено на місці, завдяки inlineякому виклики замінять код кодом всередині функції.
  • Функції більш звичні, оскільки вони схожі на SDK.
  • IDE автоматично виконає ці функції, а це означає, що немає необхідності мати попередні знання про клас утиліти.

Одним із недоліків є те, що якщо ми змінимо порядок емусів, то будь-яка стара посилання не спрацює. Це може бути проблемою з такими речами, як "Інтенти" всередині відкладених намірів, оскільки вони можуть пережити оновлення. Однак решту часу це повинно бути нормально.

Важливо зазначити, що інші рішення, як-от використання імені замість позиції, також не вдасться, якщо ми перейменуємо будь-яке значення. Хоча в цих випадках ми отримуємо виняток замість неправильного значення Enum.

Використання:

// Sender usage
intent.putExtra(AwesomeEnum.SOMETHING)
// Receiver usage
val result = intent.getEnumExtra<AwesomeEnum>()

14
+1 за вказівку на те, що зробити їх додатком широким "справжньою поганою ідеєю".
bugfixr

3
Насправді я працював над проектом, де я просто не хотів мати справу з серіалізацією або з’єднанням об'єктів (безліч об'єктів з великою кількістю змінних в них), а використання статичних глобальних змінних було добре ... до тих пір, поки товариш по команді не потрапив на проект. Вартість спроби координувати використання цих глобальних зразків змусила мене "закрутити це. Я пишу генератор коду, щоб зробити мені парцеляни". Кількість помилок значно скоротилася
Джо Плант

2
@Coeffect Так, це зрозуміла пропозиція, але в більшості випадків це можна кваліфікувати як передчасну оптимізацію, якщо ви не розбираєте тисячі перерахунків (яких за своєю природою їх має бути лише декілька, враховуючи, що вони використовуються для обробки стану) На Nexus 4 ви отримуєте поліпшення на 1 мс ( developerphil.com/parcelable-vs-serializable ) не впевнений, що варто додатково працювати на ногах, але знову ж таки у вас є інші альтернативи, які я запропонував;)
pablisco

1
@rgv Під кришкою Котлін хрест компілює enum classтипи на звичайну Java enum. Я думаю, що простіше вирішити проблему: зробити enum classінструмент Serializable: enum class AwesomeEnum : Serializable { A, B, C }Не ідеально, але він повинен працювати.
pablisco

1
@Pierre, як і у будь-якої хорошої відповіді, є "це залежить". Немає необхідності розширювати серіалізацію. Початкова відповідь справедлива. Ці додаткові варіанти є в тому випадку, якщо у вас є випадок, коли може бути горлечко для пляшок, наприклад, якщо ви десеріалізуєте мільйони записів (сподіваємось, що ні). Використовуйте те, що вважаєте за потрібне ...
pablisco

114

Ви можете змусити вашій переконання реалізувати Parcelable, що досить легко для переліків:

public enum MyEnum implements Parcelable {
    VALUE;


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(final Parcel dest, final int flags) {
        dest.writeInt(ordinal());
    }

    public static final Creator<MyEnum> CREATOR = new Creator<MyEnum>() {
        @Override
        public MyEnum createFromParcel(final Parcel source) {
            return MyEnum.values()[source.readInt()];
        }

        @Override
        public MyEnum[] newArray(final int size) {
            return new MyEnum[size];
        }
    };
}

Потім можна використовувати Intent.putExtra (String, Parcelable).

ОНОВЛЕННЯ: Зверніть увагу на коментар шкідника, який enum.values()виділяє новий масив під час кожного дзвінка.

ОНОВЛЕННЯ: Android Studio має живий шаблон, ParcelableEnumякий реалізує це рішення. (У Windows використовуйте Ctrl+ J)


3
Також можливі методи enS toString () та valueOf () замість порядкових знаків.
Натікс

2
Використання порядкового () може зламатись, коли розробник вставляє новий член enum. Звичайно, перейменування члена enum також порушить ім'я (). Але розробники мають більше шансів вставити нового члена замість перейменування, оскільки перейменування вимагає від нього перефактурування всього проекту.
Чак Ян Чен

2
Я не погоджуюся, що додаткові (або упорядковані) значення перерахунків швидше, ніж перейменовані. Використання такого складного IDE, як IntelliJ IDEA, рефакторинг - це не велика справа. Але ваш погляд все-таки хороший: ви повинні переконатися, що серіалізація є послідовною протягом усієї спільної реалізації. Це справедливо для будь-якої серіалізації. Я припускаю, що в більшості випадків парцеляни передаються навколо тієї програми, де існує лише одна реалізація, тому це не повинно бути проблемою.
Одерік

2
values ​​() створює новий масив під час кожного виклику, тому найкраще кешувати його, наприклад. приватний статичний масив
wreckgar23

2
Те, що в загальних випадках (наприклад, зберігання db), ordinal () не є безпечним, не стосується посилок Android. Посилки не призначені для довготривалого (стійкого) зберігання. Вони вмирають із додатком. Тож коли ви додасте / перейменуєте перерахунок, ви отримаєте нові посилки.
noamtm

24

Ви можете пропустити перерахунок у вигляді рядка.

public enum CountType {
    ONE,
    TWO,
    THREE
}

private CountType count;
count = ONE;

String countString = count.name();

CountType countToo = CountType.valueOf(countString);

Дані рядки підтримуються, ви повинні мати можливість передати значення переліку без проблем.


3
Найпростіша реалізація їх усіх.
Avi Cohen

22

Для передачі enum за наміром, ви можете перетворити enum в ціле число.

Наприклад:

public enum Num{A ,B}

Відправлення (перерахунок до цілого числа):

Num send = Num.A;
intent.putExtra("TEST", send.ordinal());

Отримання (ціле число для перерахування):

Num rev;
int temp = intent.getIntExtra("TEST", -1);
if(temp >= 0 && temp < Num.values().length)
    rev = Num.values()[temp];

З найкращими побажаннями. :)


8
або ви можете надіслати його як рядок (щоб він читався) за допомогою Num.A.name (), а потім поверніть його за допомогою Num.ValueOf (intent.getStringExtra ("TEST"))
Бенуа Джадінон

1
Я думаю, що спосіб Бенуа є більш безпечним, оскільки temp.ordinal () не є переважним на практиці, оскільки порядкове () значення може змінюватися. Дивіться цей пост: stackoverflow.com/questions/2836256 / ...
Shnkc

15

Якщо вам справді потрібно, ви можете серіалізувати перерахунок як String, використовуючи name()та valueOf(String), як показано нижче:

 class Example implements Parcelable { 
   public enum Foo { BAR, BAZ }

   public Foo fooValue;

   public void writeToParcel(Parcel dest, int flags) {
      parcel.writeString(fooValue == null ? null : fooValue.name());
   }

   public static final Creator<Example> CREATOR = new Creator<Example>() {
     public Example createFromParcel(Parcel source) {        
       Example e = new Example();
       String s = source.readString(); 
       if (s != null) e.fooValue = Foo.valueOf(s);
       return e;
     }
   }
 }

Це, очевидно, не спрацьовує, якщо у ваших перерахунків є стан, що змінюється (чого насправді вони не повинні).


4

Можливо, зробити так, щоб ваш Enum реалізував Serializable, тоді ви можете передати його через Намір, оскільки існує метод передачі його як серіалізаційного. Поради щодо використання int замість enum є хибними. Енуми використовуються для полегшення читання вашого коду та їх легкого обслуговування. Це було б великим кроком назад у темні століття, щоб не мати можливості використовувати Enums.


2
Будь-який тип Enum за замовчуванням розширює суперклас Enum, який вже реалізує Serializable.
Аркао

2

про пост Одеріка:

Ви можете змусити вашій переконання реалізувати Parcelable, що досить легко для переліків:

public enum MyEnum реалізує Parcelable {...} Ви можете використовувати Intent.putExtra (String, Parcelable).

Якщо ви визначите змінну MyEnum myEnum, то зробіть intent.putExtra ("Parcelable1", myEnum), ви отримаєте "Метод putExtra (String, Parcelable) неоднозначний для типу помилок типу". тому що існує також метод Intent.putExtra (String, Parcelable) і оригінальний тип 'Enum' сам реалізує інтерфейс Serializable, тому компілятор не знає, який саме метод (intent.putExtra (String, Parcelable / або Serializable)).

Запропонуйте видалити інтерфейс Parcelable з MyEnum та перемістити основний код у wrap-клас 'Реалізація parcelable, як це (Father2 є Parcelable і містить поле enum):

public class Father2 implements Parcelable {

AnotherEnum mAnotherEnum;
int mField;

public Father2(AnotherEnum myEnum, int field) {
    mAnotherEnum = myEnum;
    mField = field;
}

private Father2(Parcel in) {
    mField = in.readInt();
    mAnotherEnum = AnotherEnum.values()[in.readInt()];
}

public static final Parcelable.Creator<Father2> CREATOR = new Parcelable.Creator<Father2>() {

    public Father2 createFromParcel(Parcel in) {
        return new Father2(in);
    }

    @Override
    public Father2[] newArray(int size) {
        return new Father2[size];
    }

};

@Override
public int describeContents() {
    return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(mField);
    dest.writeInt(mAnotherEnum.ordinal());
}

}

тоді ми можемо зробити:

AnotherEnum anotherEnum = AnotherEnum.Z;
intent.putExtra("Serializable2", AnotherEnum.X);   
intent.putExtra("Parcelable2", new Father2(AnotherEnum.X, 7));

5
Ви можете чітко вибрати правильний підпис, "відкинувши" аргумент, наприкладintent.putExtra("myEnum", (Parcelable) enumValue);
Oderik

Використання порядкових відмінно!
слот

Це дійсно складний спосіб висловлювання bundle.putExtra("key", AnotherEnum.X.ordinal()).
TWiStErRob

2

ви можете використовувати enum конструктор для enum, щоб мати примітивний тип даних ..

public enum DaysOfWeek {
    MONDAY(1),
    TUESDAY(2),
    WEDNESDAY(3),
    THURSDAY(4),
    FRIDAY(5),
    SATURDAY(6),
    SUNDAY(7);

    private int value;
    private DaysOfWeek(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }

    private static final SparseArray<DaysOfWeek> map = new SparseArray<DaysOfWeek>();

    static
    {
         for (DaysOfWeek daysOfWeek : DaysOfWeek.values())
              map.put(daysOfWeek.value, daysOfWeek);
    }

    public static DaysOfWeek from(int value) {
        return map.get(value);
    }
}

ви можете використовувати для передачі int як допоміжних даних, а потім витягніть його з перерахунку, використовуючи його значення.


2

Більшість відповідей, які тут використовують концепцію Parcelable, містяться в коді Java. Простіше зробити це в Котліні.

Просто коментуйте свій клас enum за допомогою @Parcelize та реалізуйте інтерфейс Parcelable.

@Parcelize
enum class ViewTypes : Parcelable {
TITLE, PRICES, COLORS, SIZES
}

1

Мені подобається просте.

  • Діяльність Фреда має два режими - HAPPYі SAD.
  • Створіть статику, IntentFactoryяка створює вашу Intentдля вас. Передай це, що Modeхочеш.
  • IntentFactoryВикористовує ім'я Modeкласу як ім'я додаткового.
  • У IntentFactoryперетворює Modeдо Stringвикористовуючиname()
  • Після вступу в onCreateексплуатацію цю інформацію для перетворення назад до Mode.
  • Ви можете використовувати ordinal()і Mode.values()також. Мені подобаються струни, тому що я бачу їх у налагоджувачі.

    public class Fred extends Activity {
    
        public static enum Mode {
            HAPPY,
            SAD,
            ;
        }
    
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.betting);
            Intent intent = getIntent();
            Mode mode = Mode.valueOf(getIntent().getStringExtra(Mode.class.getName()));
            Toast.makeText(this, "mode="+mode.toString(), Toast.LENGTH_LONG).show();
        }
    
        public static Intent IntentFactory(Context context, Mode mode){
            Intent intent = new Intent();
            intent.setClass(context,Fred.class);
            intent.putExtra(Mode.class.getName(),mode.name());
    
            return intent;
        }
    }

цікаво .. хто називає IntentFactory? чи можете ви детальніше розповісти про те, як інша діяльність називатиме Фреда та як Фред може гарантувати, що режим перейшов?
erik

0

Я думаю, що найкращим буде стати перетворення цих списків у щось, що може бути розбірним, наприклад, рядок (чи карта?), Щоб отримати його до Діяльності. Тоді Діяльність доведеться перетворити його назад у масив.

Реалізація спеціальних посилок - це біль у шиї ІМХО, тому я би уникнув цього, якщо це можливо.


0

Розглянемо наступний перелік:

public static  enum MyEnum {
    ValueA,
    ValueB
}

Для проходження ::

 Intent mainIntent = new Intent(this,MyActivity.class);
 mainIntent.putExtra("ENUM_CONST", MyEnum.ValueA);
 this.startActivity(mainIntent);

Щоб повернути назад з наміру / групи / аргументів ::

 MyEnum myEnum = (MyEnum) intent.getSerializableExtra("ENUM_CONST");

0

Якщо ви просто хочете надіслати перерахунок, ви можете зробити щось на кшталт:

Спочатку оголосити перерахунок, що містить деяке значення (яке можна передати через наміри):

 public enum MyEnum {
    ENUM_ZERO(0),
    ENUM_ONE(1),
    ENUM_TWO(2),
    ENUM_THREE(3);
    private int intValue;

    MyEnum(int intValue) {
        this.intValue = intValue;
    }

    public int getIntValue() {
        return intValue;
    }

    public static MyEnum getEnumByValue(int intValue) {
        switch (intValue) {
            case 0:
                return ENUM_ZERO;
            case 1:
                return ENUM_ONE;
            case 2:
                return ENUM_TWO;
            case 3:
                return ENUM_THREE;
            default:
                return null;
        }
    }
}

Тоді:

  intent.putExtra("EnumValue", MyEnum.ENUM_THREE.getIntValue());

І коли ви хочете отримати це:

  NotificationController.MyEnum myEnum = NotificationController.MyEnum.getEnumByValue(intent.getIntExtra("EnumValue",-1);

Шматок торту!


0

Використовуйте функції розширення Kotlin

inline fun <reified T : Enum<T>> Intent.putExtra(enumVal: T, key: String? = T::class.qualifiedName): Intent =
    putExtra(key, enumVal.ordinal)

inline fun <reified T: Enum<T>> Intent.getEnumExtra(key: String? = T::class.qualifiedName): T? =
    getIntExtra(key, -1)
        .takeUnless { it == -1 }
        ?.let { T::class.java.enumConstants[it] }

Це дає вам можливість передавати кратні одного типу перерахування або за замовчуванням використовувати ім'я класу.

// Add to gradle
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

// Import the extension functions
import path.to.my.kotlin.script.putExtra
import path.to.my.kotlin.script.getEnumExtra

// To Send
intent.putExtra(MyEnumClass.VALUE)

// To Receive
val result = intent.getEnumExtra<MyEnumClass>()

-2

Не використовуйте переписки. Причина № 78 не використовувати переписки. :) Використовуйте цілі числа, які легко можна видалити за допомогою пакетів і парцеляції.


8
@hackbod - які ще 77 причин? ;) Серйозно, однак - напевно, існує багато переваг для перерахування, і їх зовсім не складно «віддалити» - будь-який шанс, що ти можеш розширити щодо своїх причин проти них?
ostergaard

3
@hackbod Будь ласка, докладно. Якщо переліків не слід використовувати, видаліть їх з API.
dcow

2
Переліки є частиною специфікації мови Java, тому вони трохи важко видалити , і до сих пір сумісні реалізації Java :)
трохи

1
про що mEnum.ordinal()? це повернення позиції стихії
Сайф Хамед

3
Значення Enum.ordinal()фіксується під час компіляції. Єдиний раз, коли цей коментар застосовується, - це передавання даних між додатками з різними версіями переліку, або через оновлення додатків, які змінюють порядок елементів у переліку. Така річ небезпечна, коли ви використовуєте що-небудь, що не є примітивом. Для передачі намірів між видами діяльності в рамках однієї програми Enum.ordinal()слід бути повністю безпечним.
Іван МакЛайд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.