Diffuser en toute sécurité du long vers l'int en Java

489

Quelle est la manière la plus idiomatique en Java pour vérifier qu'une distribution de longà intne perd aucune information?

Voici ma mise en œuvre actuelle:

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
la source
34
Deux chemins de code. L'un est l'héritage et a besoin d'ts. Ces données héritées DEVRAIENT toutes tenir dans un int, mais je veux lever une exception si cette hypothèse est violée. L'autre chemin de code utilisera des longs et n'aura pas besoin de cast.
Brigham
197
J'adore la façon dont les gens se demandent toujours pourquoi vous voulez faire ce que vous voulez faire. Si chacun expliquait son cas d'utilisation complet dans ces questions, personne ne pourrait les lire, encore moins y répondre.
BT
24
BT - Je déteste vraiment poser des questions en ligne pour cette raison. Si vous voulez aider, c'est bien, mais ne jouez pas 20 questions et forcez-les à se justifier.
Mason240
59
Pas d'accord avec BT et Mason240 ici: il est souvent utile de montrer à l'interrogateur une autre solution à laquelle il n'a pas pensé. Marquer les odeurs de code est un service utile. C'est loin de "Je suis curieux de savoir pourquoi ..." de "les forcer à se justifier".
Tommy Herbert
13
Il y a beaucoup de choses que vous ne pouvez pas faire avec les longs, par exemple indexer un tableau.
skot

Réponses:

580

Pour cela, une nouvelle méthode a été ajoutée avec Java 8 .

import static java.lang.Math.toIntExact;

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

Va jeter un ArithmeticExceptionen cas de débordement.

Voir: Math.toIntExact(long)

Plusieurs autres méthodes de sécurité anti-débordement ont été ajoutées à Java 8. Elles se terminent par exact .

Exemples:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)
Pierre-Antoine
la source
5
Nous avons aussi addExactet multiplyExact. Il convient de noter que la division ( MIN_VALUE/-1) et la valeur absolue ( abs(MIN_VALUE)) n'ont pas de méthodes de commodité sûres.
Aleksandr Dubinsky
Mais quelle différence y a-t-il à utiliser Math.toIntExact()au lieu de la distribution habituelle int? La mise en œuvre de Math.toIntExact()juste transtypes longà int.
Yamashiro Rion
@YamashiroRion En fait, l'implémentation de toIntExact vérifie d'abord si le cast entraînerait un débordement, auquel cas il lève une ArithmeticException. Ce n'est que si le cast est sûr qu'il effectue alors un cast de long à int auquel il revient. En d'autres termes, si vous essayez de lancer un nombre long qui ne peut pas être représenté comme entier (par exemple, tout nombre strictement supérieur à 2 147 483 647), il lèvera une ArithmeticException. Si vous faites de même avec une distribution simple, la valeur int résultante sera incorrecte.
Pierre-Antoine
306

Je pense que je le ferais aussi simplement que:

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;
}

Je pense que cela exprime l'intention plus clairement que le casting répété ... mais c'est quelque peu subjectif.

Note d'intérêt potentiel - en C # ce serait juste:

return checked ((int) l);
Jon Skeet
la source
7
Je ferais toujours la vérification de la gamme comme (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). J'ai du mal à trouver d'autres façons de le faire. Dommage que Java n'en ait pas unless.
Tom Hawtin - tackline
5
+1. Cela relève exactement de la règle "les exceptions doivent être utilisées pour des conditions exceptionnelles ".
Adam Rosenfield,
4
(Dans un langage moderne d'usage général, ce serait: "Eh? Mais les pouces ont une taille arbitraire?")
Tom Hawtin - tackline
7
@Tom: Préférence personnelle, je suppose - je préfère avoir le moins de négatifs possible. Si je regarde un «si» avec un corps qui lève une exception, j'aimerais voir des conditions qui le rendent exceptionnel - comme la valeur étant «hors du bas» de int.
Jon Skeet
6
@Tom: Dans ce cas, j'enlèverais le négatif, mettrais le cast / return à l'intérieur du corps "if", puis lèverais une exception après, si vous voyez ce que je veux dire.
Jon Skeet
132

Avec la classe Ints de Google Guava , votre méthode peut être modifiée en:

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

À partir des documents liés:

vérifiéCast

public static int checkedCast(long value)

Renvoie la valeur int qui est égale à value, si possible.

Paramètres: value - toute valeur dans la plage du inttype

Renvoie: la intvaleur égale àvalue

Lance: IllegalArgumentException - si valueest supérieur Integer.MAX_VALUEou inférieur àInteger.MIN_VALUE

Soit safeLongToIntdit en passant , vous n'avez pas besoin de l' encapsuleur, sauf si vous voulez le laisser en place pour modifier la fonctionnalité sans refactorisation approfondie bien sûr.

prasopes
la source
3
La goyave Ints.checkedCastfait exactement ce que fait OP, d'ailleurs
Partiellement nuageux
14
+1 pour la solution de goyave, bien qu'il ne soit pas vraiment nécessaire de l'envelopper dans une autre méthode, il suffit d'appeler Ints.checkedCast(l)directement.
dimo414
8
La goyave a également Ints.saturatedCastqui renverra la valeur la plus proche au lieu de lever une exception.
Jake Walsh
Oui, il est sûr d'utiliser l'api existante comme mon cas, la bibliothèque déjà dans le projet: pour lever l'exception si invalide: Ints.checkedCast (long) et Ints.saturedCast (long) pour obtenir le plus proche pour convertir long en int.
Osify
29

Avec BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds
Jaime Saiz
la source
J'aime celui-ci, quelqu'un a quelque chose contre cette solution?
Rui Marques
12
Eh bien, il s'agit d'allouer et de jeter un BigDecimal juste pour obtenir ce qui devrait être une méthode utilitaire, alors oui, ce n'est pas le meilleur processus.
Riking
@Riking à cet égard, il est préférable d'utiliser BigDecimal.valueOf(aLong), au lieu de new BigDecimal(aLong), pour indiquer qu'une nouvelle instance n'est pas requise. La mise en cache de l'environnement d'exécution sur cette méthode est spécifique à l'implémentation, tout comme la présence possible d'Escape Analysis. Dans la plupart des cas réels, cela n'a aucun impact sur les performances.
Holger
17

voici une solution, au cas où vous ne vous souciez pas de la valeur au cas où elle serait plus grande que nécessaire;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}
Vitaliy Kulikov
la source
semble, vous avez tort ... ça fonctionnera bien puis négatif. aussi, qu'est-ce que cela signifie too low? veuillez fournir un cas d'utilisation.
Vitaliy Kulikov
cette solution est la plus rapide et la plus sûre, alors nous parlons de lancer Long en Int pour obéir au résultat.
Vitaliy Kulikov
11

DONT: Ce n'est pas une solution!

Ma première approche a été:

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

Mais cela ne fait que convertir le long en un entier, créant potentiellement de nouvelles Longinstances ou les récupérant du pool Long.


Les inconvénients

  1. Long.valueOfcrée une nouvelle Longinstance si le nombre ne se trouve pas dans Longla plage du pool de [-128, 127].

  2. L' intValueimplémentation ne fait rien de plus que:

    return (int)value;

Cela peut donc être considéré comme pire que de simplement lancer le longto int.

Andreas
la source
4
Votre tentative d'aide est appréciée, mais donner un exemple de quelque chose qui ne fonctionne pas n'est pas tout à fait la même chose que de fournir une solution qui fonctionne. Si vous souhaitez modifier pour ajouter une manière correcte, cela pourrait être assez bon; sinon, il n'est pas vraiment approprié d'être affiché comme réponse.
Pops
4
D'accord, pourquoi ne pas avoir à la fois des DO et des DONT? Tbh, parfois j'aimerais avoir une liste de comment ne pas faire les choses (DONT) pour vérifier si j'ai utilisé un tel modèle / code. Quoi qu'il en soit, je peux supprimer cette "réponse".
Andreas
1
Bon anti-motif. Quoi qu'il en soit, cela aurait été génial si vous expliquiez ce qui se passe si la valeur longue est hors plage pour int? Je suppose qu'il y aura une exception ClassCastException ou quelque chose comme ça?
Peter Wippermann
2
@PeterWippermann: J'ai ajouté quelques informations supplémentaires. Les considérez-vous comme compréhensibles resp. assez explicatif?
Andreas
7

Je prétends que la façon évidente de voir si la conversion d'une valeur a changé la valeur serait de convertir et de vérifier le résultat. Je voudrais cependant supprimer le casting inutile lors de la comparaison. Je ne suis pas non plus trop intéressé par les noms de variables à une lettre (exception xet y, mais pas quand ils signifient ligne et colonne (parfois respectivement)).

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;
}

Cependant, je voudrais vraiment éviter cette conversion si possible. Évidemment, ce n'est parfois pas possible, mais dans ces cas, IllegalArgumentExceptionc'est presque certainement la mauvaise exception à lancer en ce qui concerne le code client.

Tom Hawtin - sellerie
la source
1
C'est ce que font les versions récentes de Google Guava Ints :: checkedCast.
lexicalscope
2

Les types d'entiers Java sont représentés comme signés. Avec une entrée entre 2 31 et 2 32 (ou -2 31 et -2 32 ), le lancer réussirait mais votre test échouerait.

Ce qu'il faut vérifier est de savoir si tous les bits hauts du longsont tous les mêmes:

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("...");
    }
}
foule
la source
3
Je ne vois pas ce que la signature a à voir avec ça. Pourriez - vous donner un exemple qui ne l' information mais lose ne pas le test? 2 ^ 31 serait converti en entier.MIN_VALUE (c'est-à-dire -2 ^ 31), donc des informations ont été perdues.
Jon Skeet
@ Jon Skeet: Peut-être que moi et l'OP parlons l'un derrière l'autre. (int) 0xFFFFFFFFet (long) 0xFFFFFFFFLont des valeurs différentes, mais elles contiennent toutes les deux les mêmes "informations", et il est presque trivial d'extraire la valeur longue d'origine de l'int.
mob
Comment pouvez-vous extraire la valeur longue d'origine de l'int, alors que le long aurait pu être -1 pour commencer, au lieu de 0xFFFFFFFF?
Jon Skeet
Désolé si je ne suis pas clair. Je dis que si le long et l'int contiennent les mêmes 32 bits d'information, et si le 32e bit est défini, alors la valeur int est différente de la valeur longue, mais qu'elle est facile d'obtenir la valeur longue.
mob
@mob à quoi cela fait-il référence? Le code OP indique correctement que les valeurs longues> 2 ^ {31} ne peuvent pas être converties en ints
Partiellement nuageux
0
(int) (longType + 0)

mais Long ne peut pas dépasser le maximum :)

Maury
la source
1
Le + 0n'ajoute rien à cette conversion, cela pourrait fonctionner si Java traitait la concaténation de type numérique de la même manière que les chaînes, mais comme il ne vous permet pas d'effectuer une opération d'ajout sans raison.
sixones
-7

Une autre solution peut être:

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

J'ai essayé ceci pour les cas où le client fait un POST et la base de données du serveur ne comprend que les entiers alors que le client a un Long.

Rajat Anantharam
la source
Vous obtiendrez une exception NumberFormatException sur les valeurs "très longues": cela Integer.valueOf(Long.MAX_VALUE.toString()); entraîne à java.lang.NumberFormatException: For input string: "9223372036854775807" peu près l'obscurcissement de l'exception hors plage car elle est désormais traitée de la même manière qu'une chaîne contenant des lettres est traitée.
Andreas
2
Il ne compile pas non plus car vous ne retournez pas toujours une valeur.
Patrick M