Безпечне лиття довгих до 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
Два кодові шляхи. Один - це спадщина і потребує вкладів. Ці застарілі дані ДОЛЖНІ всі вписуватися в int, але я хочу винести виняток, якщо це припущення порушено. Інший шлях коду використовуватиме longs і не потребуватиме кастингу.
Брігхем

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

24
BT - Я дуже ненавиджу задавати будь-які запитання в Інтернеті з цієї причини. Якщо ви хочете допомогти, це чудово, але не грайте на 20 запитань і змушуйте їх виправдовуватися.
Мейсон240

59
Тут не погоджуються з BT та Mason240: часто корисно показати питаючому інше рішення, про яке вони не думали. Позначення запахів коду - корисна послуга. Це довгий шлях від "мені цікаво, чому ...", щоб "змусити їх виправдовуватися".
Томмі Герберт

13
Є багато речей, які ви не можете зробити з longs, наприклад, індексувати масив.
skot

Відповіді:


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. Слід зазначити, що поділ ( 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)). Мені важко обернутись головою навколо інших способів це зробити. Шкода Java не має unless.
Том Хоутін - тайклін

5
+1. Це підпадає саме під правило "виключення повинні використовуватися для виняткових умов".
Адам Розенфілд


7
@Tom: Особисті переваги, я думаю - я вважаю за краще мати якомога менше негативів. Якщо я дивлюсь на "якщо" з тілом, яке кидає виняток, я хотів би побачити умови, які роблять його винятком - як, наприклад, значення "знизу" int.
Джон Скіт

6
@Tom: У такому випадку я б видалив мінус, поклав касти / повернення всередину тіла "якщо", а потім кину виняток, якщо ви бачите, що я маю на увазі.
Джон Скіт

132

З класом Ints Google Guava ваш метод можна змінити на:

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

Із пов’язаних документів:

зареєстрованоКаст

public static int checkedCast(long value)

Повертає значення int, яке дорівнює value , якщо це можливо.

Параметри: value - будь-яке значення в діапазоніint типу

Повертає:int значення, відповіднеvalue

Кидки: IllegalArgumentException - якщо valueбільше Integer.MAX_VALUEабо меншеInteger.MIN_VALUE

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


3
Guava's Ints.checkedCastробить саме те, що робить ОП, до речі
мінлива хмарність

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

8
Guava також має, Ints.saturatedCastщо поверне найближче значення замість того, щоб кидати виняток.
Джейк Уолш

Так, безпечно використовувати існуючі api як мій випадок, бібліотеку, яка вже є у проекті: кинути виняток, якщо недійсний: Ints.ckedCast (довгий) та Ints.sat satCast (long), щоб отримати найближче для перетворення long у int.
Осифік

29

З BigDecimal:

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

Мені це подобається, хтось має щось проти цього рішення?
Rui Marques

12
Ну, це виділення та викидання BigDecimal просто для того, щоб досягти того, що має бути корисним методом, так що так, це не найкращий процес.
Рікінг

@ Звертаючись до цього, краще використовувати BigDecimal.valueOf(aLong), замість того new BigDecimal(aLong), щоб позначити, що новий екземпляр не потрібен. Незалежно від того, чи виконує середовище виконання кешування для цього методу, конкретна реалізація, як і можлива наявність аналізу Escape. У більшості випадків реального життя це не впливає на продуктивність.
Холгер

17

ось рішення, якщо ви не піклуєтесь про значення, якщо воно більше, ніж потрібно;)

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

Здається, ви помиляєтесь ... це буде добре, тоді негативно. також, що означає too low? будь ласка, вкажіть випадки використання.
Віталій Куліков

це рішення є швидким та безпечним, тоді ми говоримо про те, щоб кинути Довгий на Інт, щоб підкоритися результату.
Віталій Куліков

11

ДОНТ: Це не рішення!

Мій перший підхід:

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

Але це просто відкидає довгий час до int, потенційно створюючи нові Longекземпляри або витягаючи їх з пулу Long.


Недоліки

  1. Long.valueOfстворює новий Longекземпляр, якщо число не знаходиться в межах Longдіапазону пулів [-128, 127].

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

    return (int)value;

Тож це можна вважати навіть гірше, ніж просто кастинг longдо int.


4
Ваша спроба допомогти оцінена, але надати приклад того, що не працює, не зовсім те саме, що запропонувати рішення, яке працює. Якщо ви редагували, щоб додати правильний спосіб, це може бути досить непогано; в іншому випадку це не дуже придатно розміщувати як відповідь.
Попс

4
Гаразд, чому б не мати і DO, і DONT? Tbh, іноді мені хочеться, щоб у мене був список, як не робити речей (DONTs), щоб перевірити, чи використовував я такий шаблон / код. У будь-якому випадку я можу видалити цю "відповідь".
Андреас

1
Хороший анти-візерунок. У будь-якому випадку було б чудово, якби ви пояснили, що трапиться, якщо довге значення буде поза діапазоном для 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.
лексикаскоп

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), тому інформація втрачена.
Джон Скіт

@Jon Skeet: Можливо, я та ОП розмовляємо один з одним. (int) 0xFFFFFFFFі (long) 0xFFFFFFFFLмають різні значення, але вони обидва містять однакову "інформацію", і вилучити початкове довге значення з int майже тривіально.
маф

Як ви можете отримати вихідне довге значення з int, коли довге могло б почати з -1 замість 0xFFFFFFFF?
Джон Скіт

Вибачте, якщо я не зрозуміла. Я кажу, що якщо і long, і int містять однакові 32 біти інформації, а якщо встановлено 32-й біт, то значення int відрізняється від великого значення, але це легко отримати довгу цінність
мафія

@mob, на що це посилання? Код ОП правильно повідомляє, що довгі значення> 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" що це значно пригнічує виняток поза межами діапазону, тому що тепер він обробляється так само, як обробляється рядок, що містить літери.
Андреас

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