Devido à implementação de genéricos Java, você não pode ter um código como este:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Como posso implementar isso mantendo a segurança do tipo?
Eu vi uma solução nos fóruns Java que é assim:
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
Mas eu realmente não entendo o que está acontecendo.
java
arrays
generics
reflection
instantiation
tatsuhirosatou
fonte
fonte
Respostas:
Eu tenho que fazer uma pergunta em troca: você está
GenSet
"marcado" ou "desmarcado"? O que isso significa?Verificado : digitação forte .
GenSet
sabe explicitamente que tipo de objetos que ele contém (ou seja, seu construtor foi explicitamente chamado com umClass<E>
argumento, e métodos irá lançar uma exceção quando eles são passados argumentos que não são do tipoE
. VejaCollections.checkedCollection
.-> nesse caso, você deve escrever:
Desmarcado : digitação fraca . Nenhuma verificação de tipo é realmente feita em nenhum dos objetos passados como argumento.
-> nesse caso, você deve escrever
Observe que o tipo de componente da matriz deve ser o apagamento do parâmetro type:
Tudo isso resulta de uma fraqueza conhecida e deliberada de genéricos em Java: foi implementada usando apagamento, para que as classes "genéricas" não saibam com qual argumento de tipo elas foram criadas no tempo de execução e, portanto, não podem fornecer tipos. segurança, a menos que algum mecanismo explícito (verificação de tipo) seja implementado.
fonte
Object[] EMPTY_ELEMENTDATA = {}
para armazenamento. Posso usar esse mecanismo para redimensionar sem conhecer o tipo usando genéricos?public void <T> T[] newArray(Class<T> type, int length) { ... }
Você consegue fazer isso:
Essa é uma das maneiras sugeridas de implementar uma coleção genérica em Java eficaz; Item 26 . Sem erros de tipo, sem necessidade de converter a matriz repetidamente. No entanto, isso dispara um aviso porque é potencialmente perigoso e deve ser usado com cautela. Conforme detalhado nos comentários, isso
Object[]
agora está disfarçado como nossoE[]
tipo e pode causar erros ou erros inesperados,ClassCastException
se usados de maneira insegura.Como regra geral, esse comportamento é seguro desde que a matriz de conversão seja usada internamente (por exemplo, para fazer backup de uma estrutura de dados) e não seja retornada ou exposta ao código do cliente. Se você precisar retornar uma matriz de um tipo genérico para outro código, a
Array
classe de reflexão mencionada é o caminho certo a seguir.Vale ressaltar que, sempre que possível, você terá mais prazer em trabalhar com
List
s do que com matrizes se estiver usando genéricos. Certamente, às vezes você não tem escolha, mas o uso da estrutura de coleções é muito mais robusto.fonte
String[] s=b;
notest()
método acima . Isso porque a matriz de E não é realmente, é Objeto []. Isso importa se você quiser, por exemplo, umList<String>[]
- você não pode usar umObject[]
para isso, você deve ter umList[]
especificamente. É por isso que você precisa usar a criação de matriz de classe <?> Refletida.public E[] toArray() { return (E[])internalArray.clone(); }
quandointernalArray
é digitado comoE[]
e, portanto, é realmente umObject[]
. Isso falha no tempo de execução com uma exceção de conversão de tipo porqueObject[]
não é possível atribuir uma matriz de qualquer tipoE
que seja.E[] b = (E[])new Object[1];
você pode ver claramente que a única referência para a matriz criada éb
e que o tipo deb
éE[]
. Portanto, não há perigo de você acessar acidentalmente a mesma matriz por meio de uma variável diferente de um tipo diferente. Se sim, vocêObject[] a = new Object[1]; E[]b = (E[])a;
precisaria ser paranóico sobre como usara
.Veja como usar os genéricos para obter uma matriz precisamente do tipo que você procura, preservando a segurança do tipo (ao contrário das outras respostas, que lhe devolvem uma
Object
matriz ou resultam em avisos em tempo de compilação):Isso é compilado sem avisos e, como você pode ver
main
, para qualquer tipo de declaração de instânciaGenSet
, você pode atribuira
a uma matriz desse tipo e atribuir um elemento dea
a uma variável desse tipo, o que significa que a matriz e os valores na matriz são do tipo correto.Ele funciona usando literais de classe como tokens de tipo de tempo de execução, conforme discutido no Tutoriais Java . Literais de classe são tratados pelo compilador como instâncias de
java.lang.Class
. Para usar um, basta seguir o nome de uma classe com.class
. Então,String.class
atua como umClass
objeto representando a classeString
. Isso também funciona para interfaces, enumerações, matrizes unidimensionais (por exemploString[].class
), primitivas (por exemploint.class
) e a palavra-chavevoid
( por exemplo ,void.class
).Class
em si é genérico (declarado comoClass<T>
, ondeT
representa o tipo que oClass
objeto está representando), o que significa que o tipo deString.class
éClass<String>
.Portanto, sempre que você chama o construtor
GenSet
, transmite uma classe literal para o primeiro argumento que representa uma matriz doGenSet
tipo declarado da instância (por exemplo,String[].class
paraGenSet<String>
). Observe que você não poderá obter uma matriz de primitivas, pois as primitivas não podem ser usadas para variáveis de tipo.Dentro do construtor, chamar o método
cast
retorna oObject
argumento transmitido para a classe representada peloClass
objeto no qual o método foi chamado. Chamar o método estáticonewInstance
emjava.lang.reflect.Array
retorna como umaObject
matriz do tipo representado peloClass
objeto passado como o primeiro argumento e do comprimento especificado pelo métodoint
passado como o segundo argumento. Chamar o métodogetComponentType
devolve umClass
objecto que representa o tipo de componente da matriz representada peloClass
objecto em que o método foi chamado (por exemplo,String.class
paraString[].class
,null
se oClass
objecto não representa uma matriz).Essa última frase não é totalmente precisa. Chamando
String[].class.getComponentType()
retorna umClass
objeto que representa a classeString
, mas seu tipoClass<?>
não é ,Class<String>
e é por isso que você não pode fazer algo como o seguinte.O mesmo vale para todos os métodos
Class
que retornam umClass
objeto.Sobre o comentário de Joachim Sauer em esta resposta (eu não tenho reputação suficiente para comentar sobre ela), o exemplo do uso do elenco
T[]
resultará em um aviso, pois o compilador não pode garantir a segurança do tipo nesse caso.Edite sobre os comentários de Ingo:
fonte
Esta é a única resposta que é do tipo seguro
fonte
Arrays#copyOf()
é independente do comprimento da matriz fornecida como o primeiro argumento. Isso é inteligente, apesar de pagar o custo das ligações paraMath#min()
eSystem#arrayCopy()
, nenhuma das quais é estritamente necessária para realizar esse trabalho. docs.oracle.com/javase/7/docs/api/java/util/…E
for uma variável de tipo. O varargs cria uma matriz de apagamento deE
quandoE
é uma variável de tipo, tornando-a não muito diferente de(E[])new Object[n]
. Por favor, consulte http://ideone.com/T8xF91 . Não é, de maneira alguma, mais seguro do que qualquer outra resposta.new E[]
. O problema que você mostrou no seu exemplo é um problema geral de apagamento, não exclusivo desta pergunta e desta resposta.Para estender a mais dimensões, basta adicionar
[]
's e parâmetros de dimensão anewInstance()
(T
é um parâmetro de tipo,cls
é aClass<T>
,d1
através ded5
números inteiros):Veja
Array.newInstance()
para detalhes.fonte
No Java 8, podemos criar um tipo de criação genérica de array usando uma referência lambda ou método. Isso é semelhante à abordagem reflexiva (que passa por a
Class
), mas aqui não estamos usando reflexão.Por exemplo, isso é usado por
<A> A[] Stream.toArray(IntFunction<A[]>)
.Isso também pode ser feito antes do Java 8 usando classes anônimas, mas é mais complicado.
fonte
ArraySupplier
essa, pode declarar o construtor comoGenSet(Supplier<E[]> supplier) { ...
e chamá-lo com a mesma linha que possui.IntFunction<E[]>
, mas sim, isso é verdade.Isso é abordado no Capítulo 5 (Genéricos) do Java efetivo, 2ª edição , item 25 ... Prefere listas a matrizes
Seu código funcionará, embora gere um aviso desmarcado (que você pode suprimir com a seguinte anotação:
No entanto, provavelmente seria melhor usar uma lista em vez de uma matriz.
Há uma discussão interessante sobre esse bug / recurso no site do projeto OpenJDK .
fonte
Você não precisa passar o argumento Class para o construtor. Tente isso.
e
resultado:
fonte
Os genéricos Java funcionam verificando tipos em tempo de compilação e inserindo conversões apropriadas, mas apagando os tipos nos arquivos compilados. Isso torna as bibliotecas genéricas utilizáveis por código que não entende os genéricos (que foi uma decisão deliberada de design), mas que significa que você normalmente não pode descobrir qual é o tipo em tempo de execução.
O
Stack(Class<T> clazz,int capacity)
construtor público exige que você passe um objeto Class em tempo de execução, o que significa que as informações da classe estão disponíveis no tempo de execução para o código que precisa delas. E aClass<T>
forma significa que o compilador verificará se o objeto Class que você passa é precisamente o objeto Class para o tipo T. Não é uma subclasse de T, nem uma superclasse de T, mas precisamente T.Isso significa que você pode criar um objeto de matriz do tipo apropriado em seu construtor, o que significa que o tipo de objetos que você armazena em sua coleção terá seus tipos verificados no ponto em que foram adicionados à coleção.
fonte
Olá, embora o tópico esteja inoperante, gostaria de chamar sua atenção para isso:
Os genéricos são usados para verificação de tipo durante o tempo de compilação:
Não se preocupe com avisos de conversão de tipos ao escrever classe genérica. Se preocupe quando você estiver usando.
fonte
E esta solução?
Funciona e parece simples demais para ser verdade. Existe alguma desvantagem?
fonte
T[]
, então você não pode criar programaticamente aT[] elems
para passar para a função. E se você pudesse, não precisaria da função.Veja também este código:
Ele converte uma lista de qualquer tipo de objeto em uma matriz do mesmo tipo.
fonte
List
tiver mais de um tipo de objeto, por exemplo,toArray(Arrays.asList("abc", new Object()))
será lançadoArrayStoreException
.for
loop e outros que usei,Arrays.fill(res, obj);
pois queria o mesmo valor para cada índice.Eu encontrei uma maneira rápida e fácil que funciona para mim. Observe que eu usei isso apenas no Java JDK 8. Não sei se ele funcionará com versões anteriores.
Embora não possamos instanciar uma matriz genérica de um parâmetro de tipo específico, podemos passar uma matriz já criada para um construtor de classe genérico.
Agora, no principal, podemos criar a matriz da seguinte maneira:
Para mais flexibilidade com suas matrizes, você pode usar uma lista vinculada, por exemplo. o ArrayList e outros métodos encontrados na classe Java.util.ArrayList.
fonte
O exemplo está usando a reflexão Java para criar uma matriz. Isso geralmente não é recomendado, pois não é seguro. Em vez disso, o que você deve fazer é usar uma lista interna e evitar a matriz.
fonte
Passando uma lista de valores ...
fonte
Criei esse trecho de código para instanciar reflexivamente uma classe que é passada para um simples utilitário de teste automatizado.
Observe este segmento:
para o início da matriz em que Array.newInstance (classe da matriz, tamanho da matriz) . A classe pode ser primitiva (int.class) e objeto (Integer.class).
BeanUtils faz parte da primavera.
fonte
Na verdade, uma maneira mais fácil de fazer isso é criar uma matriz de objetos e convertê-la no tipo desejado, como no exemplo a seguir:
onde
SIZE
é uma constante eT
é um identificador de tipofonte
O elenco forçado sugerido por outras pessoas não funcionou para mim, lançando uma exceção do elenco ilegal.
No entanto, esse elenco implícito funcionou bem:
onde Item é uma classe que eu defini contendo o membro:
Dessa forma, você obtém uma matriz do tipo K (se o item tiver apenas o valor) ou qualquer tipo genérico que você deseja definir na classe Item.
fonte
Ninguém mais respondeu à pergunta do que está acontecendo no exemplo que você postou.
Como outros já disseram, os genéricos são "apagados" durante a compilação. Portanto, em tempo de execução, uma instância de um genérico não sabe qual é o seu tipo de componente. A razão para isso é histórica, a Sun queria adicionar genéricos sem interromper a interface existente (de origem e binária).
As matrizes, por outro lado , conhecem seu tipo de componente em tempo de execução.
Este exemplo soluciona o problema fazendo com que o código que chama o construtor (que conhece o tipo) passe um parâmetro informando à classe o tipo necessário.
Portanto, o aplicativo construiria a classe com algo como
e o construtor agora sabe (em tempo de execução) qual é o tipo de componente e pode usar essas informações para construir a matriz por meio da API de reflexão.
Finalmente, temos uma conversão de tipo porque o compilador não tem como saber que a matriz retornada por
Array#newInstance()
é do tipo correto (mesmo que saibamos).Esse estilo é um pouco feio, mas às vezes pode ser a solução menos ruim para a criação de tipos genéricos que precisam conhecer o tipo de componente em tempo de execução por qualquer motivo (criação de matrizes ou instâncias do tipo de componente etc.).
fonte
Eu encontrei uma espécie de solução para esse problema.
A linha abaixo gera erro de criação de matriz genérica
No entanto, se eu encapsular
List<Person>
em uma classe separada, ele funcionará.Você pode expor pessoas da classe PersonList através de um getter. A linha abaixo fornecerá uma matriz que possui um
List<Person>
em cada elemento. Em outras palavras, matriz deList<Person>
.Eu precisava de algo assim em algum código no qual estava trabalhando e foi isso que fiz para fazê-lo funcionar. Até agora sem problemas.
fonte
Você pode criar uma matriz de objetos e convertê-la em E em qualquer lugar. Sim, não é uma maneira muito limpa de fazê-lo, mas deve pelo menos funcionar.
fonte
tente isso.
fonte
Element
classe?Uma solução fácil, embora desarrumada, seria aninhar uma segunda classe "portadora" dentro da sua classe principal e usá-la para armazenar seus dados.
fonte
new Holder<Thing>[10]
é uma criação genérica de array.Talvez não esteja relacionado a esta pergunta, mas enquanto eu estava recebendo o
generic array creation
erro " " ao usarDescobri os seguintes trabalhos (e trabalhei para mim) com
@SuppressWarnings({"unchecked"})
:fonte
Gostaria de saber se esse código criaria uma matriz genérica eficaz?
Edit: Talvez uma maneira alternativa de criar uma matriz desse tipo, se o tamanho exigido fosse conhecido e pequeno, fosse simplesmente alimentar o número necessário de "null" s no comando zeroArray?
Embora obviamente isso não seja tão versátil quanto usar o código createArray.
fonte
T
quandoT
é uma variável de tipo, ou seja,zeroArray
retorna anObject[]
. Consulte http://ideone.com/T8xF91 .Você pode usar um elenco:
fonte
a
a fora da classe!Na verdade, encontrei uma solução bastante exclusiva para ignorar a incapacidade de iniciar uma matriz genérica. O que você precisa fazer é criar uma classe que aceite a variável genérica T da seguinte forma:
e, em seguida, na sua classe array, comece com o seguinte:
iniciar um
new Generic Invoker[]
causará um problema com desmarcado, mas não deve haver nenhum problema.Para obter da matriz, você deve chamar a matriz [i] .variable da seguinte forma:
O restante, como redimensionar a matriz, pode ser feito com Arrays.copyOf () da seguinte forma:
E a função add pode ser adicionada da seguinte forma:
fonte
T
, não uma matriz de algum tipo parametrizado.De acordo com vnportnoy, a sintaxe
cria uma matriz de referências nulas, a serem preenchidas como
qual é o tipo seguro.
fonte
fonte
A criação genérica de array não é permitida em java, mas você pode fazê-lo como
fonte