Безопасное приведение к 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;
}
Brigham
источник
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)
Pierre-Antoine
источник
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обертка, если вы не хотите оставить ее на месте для изменения функциональности без большого рефакторинга, конечно.

prasopes
источник
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.

Andreas
источник
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почти наверняка это неправильное исключение, которое нужно выбросить в отношении клиентского кода.

Том Хотин - Tackline
источник
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
Он также не компилируется, потому что вы не всегда возвращаете значение.
Патрик М