Отримання переліку, пов'язане зі значенням int


89

Раніше я перераховував LegNo просто як:

NO_LEG, LEG_ONE, LEG_TWO

і зателефонувавши return LegNo.values()[i];, я зміг отримати значення, пов'язане з кожним переліченням.

Але тепер я вирішив, що хочу, щоб LegNoenum NO_LEGбув int -1 замість 0, тому я вирішив використовувати приватний конструктор для ініціалізації та встановлення значення int

NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

private LegNo(final int leg) { legNo = leg; }

зараз єдине, що оскільки я роблю це таким чином, values()метод не буде працювати для NO_LEGпереліку. Як отримати перелік, пов'язаний з int? Чи існує якийсь ефективний спосіб зробити це, крім використання оператора case switch або if-elseif-elseif

Я бачу багато питань SO, пов’язаних з отриманням значення int з переліку, але я шукаю зворотного.

Відповіді:


148

EDIT серпень 2018

Сьогодні я б реалізував це наступним чином

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int value;

    LegNo(int value) {
        this.value = value;
    }

    public static Optional<LegNo> valueOf(int value) {
        return Arrays.stream(values())
            .filter(legNo -> legNo.value == value)
            .findFirst();
    }
}

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

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private static Map<Integer, LegNo> map = new HashMap<Integer, LegNo>();

    static {
        for (LegNo legEnum : LegNo.values()) {
            map.put(legEnum.legNo, legEnum);
        }
    }

    private LegNo(final int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}

Статичний блок буде викликаний лише один раз, тому тут практично немає проблем із продуктивністю.

РЕДАКТУВАТИ: перейменовано метод на, valueOfоскільки він більш вбудований в інші класи Java.


вибачте, я не впевнений, чи був я достатньо чітким. я хочу передати int і отримати перелік, пов'язаний з ним.
L-Samuels

@ L-Samuels Думаю, я неправильно прочитав ваше запитання. Дивіться моє оновлення.
adarshr

2
Я знаю , це здається очевидним, але використовувати це подобається так: LegNo foo = LegNo.valueOf(2);. Попередній код поверне a LegNo.LEG_TWO.
Перший,

1
Щоб зазначити, передача недійсного цілочисельного значення (не зіставленого) повернеться null, як очікувалося за допомогою HashMap.get : Повертає значення, на яке вказаний ключ зіставляється, або нуль, якщо ця карта не містить зіставлення для ключа.
Перший,

Хоча синтаксис потоку є акуратним, варто зазначити, що він має вищу часову складність, ніж статична карта (яка, правда, має більше споживання пам'яті). Не проблема для 3 значень, але, безумовно, проблема, якщо ви valueOf()перелічуєте 1000 членів всередині іншого циклу.
Патрік М

24

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

public enum LegNo {
   NO_LEG(-1),
   LEG_ONE(1),
   LEG_TWO(2);

   private int legIndex;

   private LegNo(int legIndex) { this.legIndex = legIndex; }

   public static LegNo getLeg(int legIndex) {
      for (LegNo l : LegNo.values()) {
          if (l.legIndex == legIndex) return l;
      }
      throw new IllegalArgumentException("Leg not found. Amputated?");
   }
}

Тепер, якщо ви хочете отримати значення Enum за цілим числом, ви просто використовуєте:

int myLegIndex = 1; //expected : LEG_ONE
LegNo myLeg = LegNo.getLeg(myLegIndex);

Я думаю, це було б більш елегантно, ніж використання оператора if else if. Але з огляду на те, що було більше переліку для пошуку, тоді стратегія карт, запропонована @adarshr, була б кращою. Хоча проголосуйте за гумор.
L-Samuels

1
Мені також дуже подобається картографічна стратегія. Особливо, коли перелік або має багато значень, або його дуже часто потрібно шукати за допомогою цього механізму. Однак, якщо пошук значень за пов'язаним int є відносно рідкісним явищем або у вас є багато різних перерахувань з однаковою вимогою до пошуку, я вважаю, що мій спосіб був би більш зручним для ресурсів, оскільки накладні витрати на карту зберігаються. Крім того, я вважаю, що це робить для менш захаращеного коду. У мене є кілька випадків використання, коли я точно перейду до типу Map.
Mike Adler

Ви ніколи не повинні отримувати пов'язане значення переліку за порядковим номером. Використання статичної карти є рекомендованою методологією архітекторів Java.
hfontanez

Поле legIndex збігається з порядковим в цьому прикладі, але може мати будь-яке значення int. Порядковий пошук не виконується. Також, будь-ласка, вкажіть або посилайте причину, чому ви вважаєте, що порядковий пошук є поганим.
Mike Adler

1
"Нога не знайдена. Ампутована?"
Gnagy,

17

Відповідь adarshr адаптована до Java 8:

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;

import java.util.Map;

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    private final static Map<Integer, LegNo> map =
            stream(LegNo.values()).collect(toMap(leg -> leg.legNo, leg -> leg));

    private LegNo(final int leg) {
        legNo = leg;
    }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}

11

Ви також можете отримати доступ до значення Enum, що відповідає даному цілочисельному значенню, просто викликавши метод values ​​() у переліченні LegNo. Повертає поле переписів LegNo: LegNo.values()[0]; //returns LEG_NO LegNo.values()[1]; //returns LEG_ONE LegNo.values()[2]; //returns LEG_TWO

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


6

8 способів Java зі значенням за замовчуванням:

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    LegNo(int legNo) {
        this.legNo = legNo;
    }

    public static LegNo find(int legNo, Supplier<? extends LegNo> byDef) {
        return Arrays.asList(LegNo.values()).stream()
                .filter(e -> e.legNo == legNo).findFirst().orElseGet(byDef);
    }
}

дзвонити:

LegNo res = LegNo.find(0, () -> LegNo.NO_LEG);

або за винятком:

LegNo res = LegNo.find(0, () -> {
    throw new RuntimeException("No found");
});

2
public enum LegNo {

  NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

  private final int code;

  LegNo(int code) {
    this.code = code;
    ReverseStorage.reverseMap.put(code, this);
  }

  public static Optional<LegNo> getByCode(int code) {
    return Optional.ofNullable(ReverseStorage.reverseMap.get(code));
  }

  private static final class ReverseStorage {
    private static final Map<Integer, LegNo> reverseMap = new LinkedHashMap<>();
  }
}

1

Оскільки ваш перелік містить лише 3 елементи, найшвидшим способом буде просто використовувати серію if else, як ви запропонували.

редагувати: відповідь, яку надав adarshr, більше підходить для загальних випадків, коли існує багато значень перерахування, але я думаю, що це надмірно для вашої проблеми.


Наявність Mapвашого коду, безумовно, не є надмірним. До того ж це робить метод набагато чистішим, ніж спагетті, якщо не так.
adarshr

Я погоджуюсь, що Карта краща, коли у вас багато значень переліку, але для 3 значень я б дотримувався конструкції if / else. Гадаю, це справа смаку.
DieterDP

Який би підхід ми не вибрали, підпис методу public LegNo valueOf(int value)змінювати не слід. Тоді if-else можна було записати в самому переліку. Якщо if-else виходить із переліку, то це, безумовно, стає не дуже чистим кодом.
adarshr

1
Я повністю з вами згоден :)
DieterDP

1
public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private LegNo(int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        for (LegNo leg : LegNo.values()) {
            if (leg.legNo == legNo) return leg;
        }   
    }
}

assert LegNo.valueOf(2) == LegNo.LEG_TWO
assert LegNo.valueOf(3) == null

4
Прийнятний для перерахувань із значеннями <10, але абсолютно неефективний для великої кількості значень перерахувань через складність пошуку O (n)
Alfishe,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.