Anotação Java SafeVarargs, existe uma prática recomendada ou padrão?

175

Recentemente, deparei com a @SafeVarargsanotação java . Pesquisar no Google o que torna uma função variável em Java insegura me deixou um pouco confuso (envenenamento por heap? Tipos apagados?), Então gostaria de saber algumas coisas:

  1. O que torna uma função Java variável insegura no @SafeVarargssentido (de preferência explicada na forma de um exemplo detalhado)?

  2. Por que essa anotação é deixada a critério do programador? Não é algo que o compilador possa verificar?

  3. Existe algum padrão a que se deve seguir para garantir que sua função seja de fato segura? Caso contrário, quais são as melhores práticas para garantir isso?

Oren
fonte
1
Você viu o exemplo (e explicação) no JavaDoc ?
jlordo
2
Para a sua terceira questão, uma prática é sempre ter um primeiro elemento e outros: retType myMethod(Arg first, Arg... others). Se você não especificar first, uma matriz vazia será permitida e você poderá muito bem ter um método com o mesmo nome com o mesmo tipo de retorno sem argumentos, o que significa que a JVM terá dificuldade em determinar qual método deve ser chamado.
fge
4
@ jlordo eu fiz, mas não entendo por que é dado no contexto varargs, pois é possível substituir facilmente essa situação fora de uma função varargs (verificado isso, compila com os avisos de segurança de tipo esperado e erros no tempo de execução) ..
Oren

Respostas:

244

1) Existem muitos exemplos na Internet e no StackOverflow sobre um problema específico com genéricos e varargs. Basicamente, é quando você tem um número variável de argumentos de um tipo de parâmetro de tipo:

<T> void foo(T... args);

Em Java, varargs são um açúcar sintático que passa por uma "reescrita" simples em tempo de compilação: um parâmetro do tipo varargs X...é convertido em um parâmetro do tipo X[]; e toda vez que uma chamada é feita para esse método varargs, o compilador coleta todos os "argumentos da variável" que vão no parâmetro varargs e cria uma matriz da mesma forma new X[] { ...(arguments go here)... }.

Isso funciona bem quando o tipo varargs é concreto String.... Quando é uma variável de tipo como T..., também funciona quando Té conhecido como um tipo concreto para essa chamada. por exemplo, se o método acima fizesse parte de uma classe Foo<T>e você tivesse uma Foo<String>referência, então chamar fooisso seria bom, porque sabemos que Té Stringnesse ponto do código.

No entanto, ele não funciona quando o "valor" de Té outro parâmetro de tipo. Em Java, é impossível criar uma matriz de um tipo de componente de parâmetro de tipo ( new T[] { ... }). Então, o Java usa new Object[] { ... }(aqui Objectestá o limite superior de T; se o limite superior fosse algo diferente, seria isso em vez deObject ) e, em seguida, fornece um aviso do compilador.

Então, o que há de errado em criar, em new Object[]vez de new T[]ou o que for? Bem, matrizes em Java conhecem seu tipo de componente em tempo de execução. Portanto, o objeto de matriz passado terá o tipo de componente errado no tempo de execução.

Provavelmente, para o uso mais comum de varargs, simplesmente para iterar sobre os elementos, isso não é problema (você não se importa com o tipo de tempo de execução da matriz), portanto, isso é seguro:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

No entanto, para qualquer coisa que dependa do tipo de componente de tempo de execução da matriz passada, não será seguro. Aqui está um exemplo simples de algo que não é seguro e trava:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

O problema aqui é que dependemos do tipo de argsser T[]para retornar como T[]. Mas, na verdade, o tipo de argumento em tempo de execução não é uma instância de T[].

3) Se o seu método tiver um argumento do tipo T...(onde T é qualquer parâmetro do tipo), então:

  • Seguro: se o seu método depender apenas do fato de que os elementos da matriz são instâncias de T
  • Inseguro: se depender do fato de que a matriz é uma instância de T[]

As coisas que dependem do tipo de tempo de execução da matriz incluem: retorná-lo como tipo T[], passando-o como um argumento para um parâmetro do tipo T[], obtendo o tipo de matriz .getClass(), passando-o para métodos que dependem do tipo de tempo de execução da matriz, como List.toArray()e Arrays.copyOf(), etc.

2) A distinção que mencionei acima é muito complicada para ser facilmente distinguida automaticamente.

newacct
fonte
7
Bem, agora tudo faz sentido. obrigado. então, só para ver que eu o entendo completamente, digamos que temos uma classe, FOO<T extends Something>seria seguro retornar o T [] de um método varargs como uma matriz de Somthings?
Oren
30
Talvez valha a pena notar que um caso em que (presumivelmente) sempre é correto usar @SafeVarargsé quando a única coisa que você faz com a matriz é passá-la para outro método que já está tão anotado (por exemplo: eu frequentemente me pego escrevendo vários métodos que existe para converter seu argumento em uma lista usando Arrays.asList(...)e passá-lo para outro método; esse caso sempre pode ser anotado com @SafeVarargsporque Arrays.asListpossui a anotação).
Jules
3
@newacct Não sei se esse foi o caso no Java 7, mas o Java 8 não permite @SafeVarargs, a menos que o método seja staticou final, porque, caso contrário, poderia ser substituído em uma classe derivada por algo inseguro.
28417 Andy
3
e no Java 9 também será permitido privatemétodos que também não podem ser substituídos.
Dave L.
4

Para melhores práticas, considere isso.

Se você tem isso:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Altere para este:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

Descobri que normalmente apenas adiciono varargs para torná-lo mais conveniente para meus chamadores. Quase sempre seria mais conveniente para minha implementação interna usar a List<>. Então, para pegar caronaArrays.asList() e garantir que não há como introduzir o Heap Pollution, é isso que faço.

Eu sei que isso apenas responde ao seu número 3. newacct deu uma ótima resposta para os itens 1 e 2 acima, e eu não tenho reputação suficiente para deixar isso como um comentário. : P

Geladeira
fonte