Transmitindo com segurança por muito tempo para int em Java

489

Qual é a maneira mais idiomática em Java para verificar se uma conversão de longpara intnão perde nenhuma informação?

Esta é minha implementação atual:

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
fonte
34
Dois caminhos de código. Um é legado e precisa de ints. Todos os dados legados devem caber em um int, mas eu quero lançar uma exceção se essa suposição for violada. O outro caminho de código usará longs e não precisará da conversão.
Brigham
197
Eu amo como as pessoas sempre questionam por que você quer fazer o que quer fazer. Se todos explicassem seu caso de uso completo nessas perguntas, ninguém seria capaz de lê-los, muito menos respondê-los.
BT
24
BT - Eu realmente odeio fazer perguntas on-line por esse motivo. Se você quer ajudar, isso é ótimo, mas não faça 20 perguntas e force-as a se justificarem.
Mason240
59
Não concordo com o BT e o Mason240 aqui: muitas vezes é valioso mostrar ao questionador outra solução em que eles não pensaram. A sinalização de cheiros de código é um serviço útil. É um longo caminho desde 'Estou curioso sobre o porquê ...' para 'forçá-los a se justificar'.
Tommy Herbert
13
Há muitas coisas que você não pode fazer com longos, por exemplo, indexar uma matriz.
skot

Respostas:

580

Um novo método foi adicionado ao Java 8 para fazer exatamente isso.

import static java.lang.Math.toIntExact;

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

Jogará um ArithmeticExceptionem caso de estouro.

Vejo: Math.toIntExact(long)

Vários outros métodos seguros de estouro foram adicionados ao Java 8. Eles terminam com exatidão .

Exemplos:

  • Math.incrementExact(long)
  • Math.subtractExact(long, long)
  • Math.decrementExact(long)
  • Math.negateExact(long),
  • Math.subtractExact(int, int)
Pierre-Antoine
fonte
5
Nós também temos addExacte multiplyExact. É importante notar que a divisão ( MIN_VALUE/-1) e o valor absoluto ( abs(MIN_VALUE)) não possuem métodos de conveniência seguros.
Aleksandr Dubinsky
Mas qual é a diferença de usar ao Math.toIntExact()invés do elenco habitual int? A implementação de Math.toIntExact()apenas lança longpara int.
Yamashiro Rion
@YamashiroRion Na verdade, a implementação do toIntExact primeiro verifica se a conversão levaria a um estouro; nesse caso, ele lança uma ArithmeticException. Somente se o elenco estiver seguro, ele executará o cast de long a int, retornando. Em outras palavras, se você tentar converter um número longo que não pode ser representado como int (por exemplo, qualquer número estritamente acima de 2 147 483 647), será gerada uma ArithmeticException. Se você fizer o mesmo com uma conversão simples, seu valor int resultante estará errado.
Pierre-Antoine
306

Eu acho que faria isso simplesmente:

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

Eu acho que expressa a intenção mais claramente do que o elenco repetido ... mas é um pouco subjetivo.

Nota de interesse em potencial - em C # seria apenas:

return checked ((int) l);
Jon Skeet
fonte
7
Eu sempre verificaria o alcance como (!(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)). Acho difícil entender outras maneiras de fazê-lo. Pena que o Java não tem unless.
Tom Hawtin # tackline 19/10/09
5
+1. Isso se encaixa exatamente na regra "exceções devem ser usadas para condições excepcionais ".
23909 Adam Rosenfield
4
(Em uma linguagem moderna de uso geral, seria: "Eh? Mas as ints têm tamanho arbitrário?")
Tom Hawtin - defina as linhas
7
@ Tom: preferência pessoal, eu acho - eu prefiro ter o mínimo de negativos possível. Se eu estiver visualizando um "se" com um corpo que gera uma exceção, gostaria de ver condições que o tornam excepcional - como o valor "fora da extremidade inferior" de int.
21416 Jon Skeet
6
@ Tom: Nesse caso, eu removeria o negativo, colocaria o elenco / retornaria dentro do corpo "if" e, em seguida, lançaria uma exceção, se você entender o que quero dizer.
9139 Jon Skeet
132

Com a classe Ints do Google Guava , seu método pode ser alterado para:

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

Dos documentos vinculados:

CheckerCast

public static int checkedCast(long value)

Retorna o valor int igual a value , se possível.

Parâmetros: value - qualquer valor na faixa doint tipo

Retorna: o intvalor igual avalue

Lança: IllegalArgumentException - se valuefor maior Integer.MAX_VALUEou menor queInteger.MIN_VALUE

Aliás, você não precisa do safeLongToIntwrapper, a menos que queira deixá-lo no lugar para alterar a funcionalidade sem refatoração extensiva, é claro.

prasopes
fonte
3
Goiaba do Ints.checkedCastfaz exatamente o OP faz, aliás
Parcialmente Nublado
14
+1 para a solução Guava, embora não seja necessário envolvê-la em outro método, basta ligar Ints.checkedCast(l)diretamente.
precisa saber é o seguinte
8
A goiaba também possui o Ints.saturatedCastque retornará o valor mais próximo em vez de lançar uma exceção.
Jake Walsh
Sim, é seguro usar a API existente como o meu caso, a biblioteca já no projeto: para lançar uma exceção se inválida: Ints.checkedCast (long) e Ints.saturatedCast (long) para obter o valor mais próximo para converter long to int.
Osify
29

Com BigDecimal:

long aLong = ...;
int anInt = new BigDecimal(aLong).intValueExact(); // throws ArithmeticException
                                                   // if outside bounds
Jaime Saiz
fonte
Eu gosto deste, alguém tem algo contra esta solução?
Rui Marques
12
Bem, ele está alocando e descartando um BigDecimal apenas para obter o que deveria ser um método utilitário, então sim, esse não é o melhor processo.
Riking
@Riking a esse respeito, é melhor usar BigDecimal.valueOf(aLong), em vez de new BigDecimal(aLong), para denotar que uma nova instância não é necessária. Se o ambiente de execução faz cache nesse método, é específico da implementação, assim como a possível presença do Escape Analysis. Na maioria dos casos da vida real, isso não afeta o desempenho.
Holger
17

aqui está uma solução, caso você não se importe com valor, caso seja maior que o necessário;)

public static int safeLongToInt(long l) {
    return (int) Math.max(Math.min(Integer.MAX_VALUE, l), Integer.MIN_VALUE);
}
Vitaliy Kulikov
fonte
parece que você está errado ... estará funcionando bem e depois será negativo. Além disso, o que significa too low? por favor, forneça casos de uso.
Vitaliy Kulikov
Como essa solução é rápida e segura, estamos falando de converter Long to Int para obedecer ao resultado.
Vitaliy Kulikov
11

NÃO: Esta não é uma solução!

Minha primeira abordagem foi:

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

Mas isso apenas converte o long para um int, potencialmente criando novas Longinstâncias ou recuperando-as do pool Long.


As desvantagens

  1. Long.valueOfcria uma nova Longinstância se o número não estiver dentro Longdo intervalo do pool [-128, 127].

  2. A intValueimplementação nada mais faz do que:

    return (int)value;

Portanto, isso pode ser considerado ainda pior do que apenas lançar o longpara int.

Andreas
fonte
4
Sua tentativa de ajudar é apreciada, mas dar um exemplo de algo que não funciona não é o mesmo que fornecer uma solução que funcione. Se você editasse para adicionar uma maneira correta, isso poderia ser muito bom; caso contrário, não é realmente adequado para ser publicado como resposta.
Pops
4
Ok, por que não ter DOs e NÃO? Às vezes, gostaria de ter uma lista de como não fazer as coisas (NÃO) para verificar se usei esse padrão / código. De qualquer forma, eu posso excluir esta "resposta".
Andreas
1
Bom anti-padrão. De qualquer forma, teria sido ótimo se você explicasse o que acontece se o valor longo estiver fora do intervalo para int? Eu acho que haverá uma ClassCastException ou algo assim?
Peter Wippermann
2
@ PeterWippermann: Adicionei mais algumas informações. Você os considera compreensíveis? explicativo o suficiente?
Andreas
7

Afirmo que a maneira óbvia de ver se a conversão de um valor alterou o valor seria lançar e verificar o resultado. No entanto, eu removeria o elenco desnecessário ao comparar. Também não gosto muito de nomes de variáveis ​​de uma letra (exceção xe y, mas não quando eles significam linha e coluna (às vezes respectivamente)).

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

No entanto, eu realmente gostaria de evitar essa conversão, se possível. Obviamente, às vezes não é possível, mas nesses casos IllegalArgumentExceptioné quase certamente a exceção errada a ser lançada no que diz respeito ao código do cliente.

Tom Hawtin - linha de orientação
fonte
1
É o que as versões recentes do Google Guava Ints :: CheckerCast fazem.
Lexicalscope
2

Os tipos inteiros Java são representados como assinados. Com uma entrada entre 2 31 e 2 32 (ou -2 31 e -2 32 ), o elenco terá êxito, mas seu teste falhará.

O que verificar é se todos os bits altos do longsão todos iguais:

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("...");
    }
}
multidão
fonte
3
Não vejo o que sinal tem a ver com isso. Poderia dar um exemplo que não faz informações perder, mas faz falhar no teste? 2 ^ 31 seria convertido em Inteiro.MIN_VALUE (ou seja, -2 ^ 31) para que as informações fossem perdidas.
21416 Jon Skeet
@ Jon Skeet: Talvez eu e o OP estejam conversando. (int) 0xFFFFFFFFe (long) 0xFFFFFFFFLtêm valores diferentes, mas ambos contêm a mesma "informação" e é quase trivial extrair o valor longo original do int.
mob
Como você pode extrair o valor longo original do int, quando o comprimento poderia ter sido -1 para começar, em vez de 0xFFFFFFFF?
21416 Jon Skeet
Desculpe se não estou claro. Estou dizendo que, se o longo e o int contiverem os mesmos 32 bits de informação e se o 32º bit estiver definido, o valor int será diferente do valor longo, mas que é fácil de obter o valor de comprimento.
mob
@mob, o que é isso em referência? O código do OP relata corretamente que valores longos> 2 ^ {31} não podem ser convertidos em ints
Parcialmente nublado
0
(int) (longType + 0)

mas Long não pode exceder o máximo :)

Maury
fonte
1
O + 0acrescenta nada a esta conversão, ele poderia funcionar se o Java tratados tipo de concatenação numérico de forma semelhante às cordas, mas uma vez que não o seu fazer uma operação de adição sem motivo.
sixones 30/09/16
-7

Uma outra solução pode ser:

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

Eu tentei isso nos casos em que o cliente está executando um POST e o banco de dados do servidor entende apenas números inteiros enquanto o cliente possui um Long.

Rajat Anantharam
fonte
Você obterá um NumberFormatException em valores "reais longos": Integer.valueOf(Long.MAX_VALUE.toString()); resulta em java.lang.NumberFormatException: For input string: "9223372036854775807" praticamente ofuscar a exceção fora do intervalo, porque agora é tratada da mesma maneira que uma string contendo letras é tratada.
Andreas
2
Também não é compilado porque você nem sempre retorna um valor.
Patrick M