Безопасное приведение к int в Java


489

Какой самый идиоматичный способ в Java проверить, что приведение от longк intне теряет никакой информации?

Это моя текущая реализация:

public static int safeLongToInt(long l) {
    int i = (int)l;
    if ((long)i != l) {
        throw new IllegalArgumentException(l + " cannot be cast to int without changing its value.");
    }
    return i;
}

34
Два пути кода. Одним из них является наследие и нуждается в ints. Эти устаревшие данные ДОЛЖНЫ вписываться в int, но я хочу выдать исключение, если это предположение нарушается. Другой путь кода будет использовать long и не будет нуждаться в приведении.
— Бригам

197
Мне нравится, что люди всегда задаются вопросом, почему ты хочешь делать то, что хочешь. Если бы каждый объяснил свои варианты использования в этих вопросах, никто не смог бы их прочитать, а тем более ответить на них.
— BT

24
БТ - Я действительно ненавижу задавать вопросы онлайн по этой причине. Если вы хотите помочь, это здорово, но не разыгрывайте 20 вопросов и заставляйте их оправдываться.
— Mason240

59
Здесь не согласны с BT и Mason240: часто полезно показать спрашивающему другое решение, о котором они не думали. Регистрация запахов кода является полезным сервисом. От «мне любопытно, почему ...» далеко до «заставлять их оправдываться».
— Томми Герберт

13
Есть много вещей, которые вы не можете сделать с длинными, например, индексировать массив.
— Скот

Ответы:


580

Новый метод был добавлен с Java 8, чтобы сделать это.

import static java.lang.Math.toIntExact;

long foo = 10L;
int bar = toIntExact(foo);

Выкинет ArithmeticExceptionв случае переполнения.

Видеть: Math.toIntExact(long)

Несколько других безопасных методов переполнения были добавлены в Java 8. Они заканчиваются точным .

Примеры:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)

5
У нас также есть addExactи multiplyExact. Следует отметить, что Division ( MIN_VALUE/-1) и абсолютное значение ( abs(MIN_VALUE)) не имеют безопасных методов удобства.
— Александр Дубинский

Но в чем разница использования Math.toIntExact()вместо обычного приведения int? Реализация Math.toIntExact()просто бросает longв int.
— Рион

@YamashiroRion На самом деле реализация toIntExact сначала проверяет, приведет ли приведение к переполнению, и в этом случае генерируется исключение ArithmeticException. Только если приведение безопасно, оно выполняет приведение от long к int, которое возвращает. Другими словами, если вы попытаетесь привести длинное число, которое не может быть представлено как int (например, любое число строго выше 2 147 483 647), оно выдаст исключение ArithmeticException. Если вы сделаете то же самое с простым приведением, полученное значение int будет неправильным.
— Пьер-Антуан

306

Я думаю, я бы сделал это так же просто, как:

public static int safeLongToInt(long l) {
    if (l < Integer.MIN_VALUE || l > Integer.MAX_VALUE) {
        throw new IllegalArgumentException
            (l + " cannot be cast to int without changing its value.");
    }
    return (int) l;
}

Я думаю, что это выражает намерение более четко, чем повторное приведение ... но это несколько субъективно.

Примечание потенциального интереса - в C # это будет просто:

return checked ((int) l);

7
Я всегда делаю проверку дальности как (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Мне трудно обдумать другие способы сделать это. Жалкой Явы нету unless.
— Том Хотин -

5
+1. Это подпадает точно под правило «исключения должны использоваться для исключительных условий».
— Адам Розенфилд

4
(На современном языке общего назначения это было бы: «А? Но у целых есть произвольный размер?»)
— Том Хотин - tackline

7
@ Том: Личные предпочтения, я думаю - я предпочитаю иметь как можно меньше негативов. Если я смотрю на «если» с телом, которое выдает исключение, я хотел бы видеть условия, которые заставляют его выглядеть исключительным - например, значение находится «в нижней части» int.
— Джон Скит

6
@Tom: В этом случае я бы удалил негатив, поместил приведение / возвращение внутрь тела «если», а затем бросил бы исключение, если вы понимаете, что я имею в виду.
— Джон Скит

132

С помощью класса Ints Google Guava ваш метод может быть изменен на:

public static int safeLongToInt(long l) {
    return Ints.checkedCast(l);
}

Из связанных документов:

checkedCast

public static int checkedCast(long value)

Возвращает значение int, равное value, если возможно.

Параметры: value - любое значение в диапазоне intтипа

Возвращает:int значение, соответствующееvalue

Броски: IllegalArgumentException - если valueбольше Integer.MAX_VALUEили меньше чемInteger.MIN_VALUE

Между прочим, вам не нужна safeLongToIntобертка, если вы не хотите оставить ее на месте для изменения функциональности без большого рефакторинга, конечно.


3
Кстати, Гуава Ints.checkedCastделает именно то, что делает OP
— переменная облачность

14
+1 для решения Guava, хотя в действительности нет необходимости заключать его в другой метод, просто вызовите его Ints.checkedCast(l)напрямую.
— dimo414

8
Гуава также имеет, Ints.saturatedCastкоторый будет возвращать ближайшее значение, а не выбрасывать исключение.
— Джейк Уолш

Да, в моем случае безопасно использовать существующий API, библиотека уже в проекте: вывести исключение, если оно недопустимо: Ints.checkedCast (long) и Ints.saturationCast (long), чтобы получить ближайший для преобразования long в int.
— Osify

29

С BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds

Мне нравится этот, кто-нибудь имеет что-то против этого решения?
— Руи Маркиз

12
Ну, это выделение и выбрасывание BigDecimal просто для того, чтобы получить то, что должно быть служебным методом, так что да, это не лучший процесс.
— Riking

@ В этом отношении лучше использовать BigDecimal.valueOf(aLong)вместо new BigDecimal(aLong)обозначения, что новый экземпляр не требуется. То, выполняет ли среда выполнения кэширование в этом методе, зависит от конкретной реализации, как и возможное присутствие Escape Analysis. В большинстве случаев это не влияет на производительность.
— Хольгер

17

вот решение, в случае, если вы не заботитесь о стоимости, если она больше, чем нужно;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}

кажется, вы не правы ... это будет работать хорошо, а затем отрицательно. Кроме того, что это значит too low? Пожалуйста, предоставьте пример использования.
— Виталий Куликов

Это быстрое и безопасное решение, тогда мы говорим, чтобы привести Long к Int, чтобы получить результат.
— Виталий Куликов

11

НЕ: Это не решение!

Мой первый подход был:

public int longToInt(long theLongOne) {
  return Long.valueOf(theLongOne).intValue();
}

Но это просто сводит long к int, потенциально создавая новые Longэкземпляры или извлекая их из пула Long.


Недостатки

  1. Long.valueOfсоздает новый Longэкземпляр, если номер не входит в Longдиапазон пула [-128, 127].

  2. intValueРеализация ничего не делает больше , чем:

    return (int)value;

Так что это может считаться даже хуже, чем просто приведение longк int.


4
Ваша попытка помочь приветствуется, но привести пример чего-то, что не работает, совсем не то же самое, что предоставить решение, которое работает. Если вы хотите отредактировать, чтобы добавить правильный путь, это может быть довольно хорошо; в противном случае, это не очень подходит для публикации в качестве ответа.
— появляется

4
Хорошо, почему бы не иметь как DOS, так и DONTs? Tbh, иногда я хотел бы иметь список того, как не делать вещи (DONTs), чтобы проверить, использовал ли я такой шаблон / код. В любом случае, я могу удалить этот «ответ».
— Андреас

1
Хороший анти-паттерн. В любом случае было бы здорово, если бы вы объяснили, что происходит, если значение long выходит за пределы диапазона для int? Я предполагаю, что будет ClassCastException или что-то вроде этого?
— Питер Випперманн

2
@PeterWippermann: я добавил еще немного информации. Считаете ли вы их понятными соотв. достаточно объяснительно?
— Андреас

7

Я утверждаю, что очевидным способом увидеть, изменилось ли приведение значения, было бы приведение и проверка результата. Я бы, однако, убрал лишний бросок при сравнении. Я также не слишком увлечен однобуквенными именами переменных (исключение xи y, но не когда они означают строку и столбец (иногда соответственно)).

public static int intValue(long value) {
    int valueInt = (int)value;
    if (valueInt != value) {
        throw new IllegalArgumentException(
            "The long value "+value+" is not within range of the int type"
        );
    }
    return valueInt;
}

Однако, действительно, я бы хотел избежать этого преобразования, если это вообще возможно. Очевидно, что иногда это невозможно, но в этих случаях IllegalArgumentExceptionпочти наверняка это неправильное исключение, которое нужно выбросить в отношении клиентского кода.


1
Это то, что делают последние версии Google Guava Ints :: checkedCast.
— lexicalscope

2

Целочисленные типы Java представлены как подписанные. С входом от 2 31 до 2 32 (или от -2 31 до -2 32 ) приведение будет успешным, но ваш тест не пройден.

На что нужно обратить внимание - все ли старшие биты longодинаковы:

public static final long LONG_HIGH_BITS = 0xFFFFFFFF80000000L;
public static int safeLongToInt(long l) {
    if ((l & LONG_HIGH_BITS) == 0 || (l & LONG_HIGH_BITS) == LONG_HIGH_BITS) {
        return (int) l;
    } else {
        throw new IllegalArgumentException("...");
    }
}

3
Я не понимаю, что с этим связано. Не могли бы вы привести пример, который не теряет информацию, но не проходит тест? 2 ^ 31 будет приведено к Integer.MIN_VALUE (т.е. -2 ^ 31), поэтому информация будет потеряна.
— Джон Скит

@ Джон Скит: Может быть, я и ОП говорим друг за другом. (int) 0xFFFFFFFFи (long) 0xFFFFFFFFLимеют разные значения, но они оба содержат одну и ту же «информацию», и почти тривиально извлечь исходное длинное значение из int.
— моб

Как вы можете извлечь исходное длинное значение из int, если длиннее могло бы быть -1, вместо 0xFFFFFFFF?
— Джон Скит

Извините, если мне непонятно. Я говорю, что если long и int оба содержат одинаковые 32 бита информации, и если установлен 32-й бит, значение int отличается от long значения, но оно легко получить длинное значение
— моб

@mob что это за ссылка? Код OP правильно сообщает, что длинные значения> 2 ^ {31} нельзя привести к целым числам
— облачность

0
(int) (longType + 0)

но долго не может превышать максимум :)


1
Добавление + 0ничего не добавляет к этому преобразованию, оно может работать, если Java обрабатывает конкатенацию числовых типов аналогично строкам, но поскольку вы не выполняете операцию добавления без причины.
— шесть

-7

Еще одно решение может быть:

public int longToInt(Long longVariable)
{
    try { 
            return Integer.valueOf(longVariable.toString()); 
        } catch(IllegalArgumentException e) { 
               Log.e(e.printstackstrace()); 
        }
}

Я пробовал это для случаев, когда клиент делает POST и серверная БД понимает только целые числа, в то время как у клиента есть Long.


Вы получите NumberFormatException для «очень длинных» значений: в Integer.valueOf(Long.MAX_VALUE.toString()); результате это в java.lang.NumberFormatException: For input string: "9223372036854775807" значительной степени запутывает исключение Out-Of-Range-Exception, поскольку теперь оно обрабатывается так же, как обрабатывается строка, содержащая буквы.
— Андреас

2
Он также не компилируется, потому что вы не всегда возвращаете значение.
— Патрик М
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.