Java: Instância de e genéricos

135

Antes de procurar na minha estrutura de dados genérica o índice de um valor, gostaria de ver se até mesmo uma instância do tipo thisfoi parametrizada.

Mas o Eclipse reclama quando faço isso:

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

Esta é a mensagem de erro:

Não é possível executar instanceof check no parâmetro de tipo E. Use seu apagamento Object, pois as informações genéricas do tipo serão apagadas no tempo de execução

Qual é a melhor maneira de fazer isso?

Nick Heiner
fonte

Respostas:

79

A mensagem de erro diz tudo. Em tempo de execução, o tipo se foi, não há como verificar.

Você pode pegá-lo criando uma fábrica para seu objeto assim:

 public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
 }

E então, no construtor do objeto, armazene esse tipo, de forma variável, para que seu método possa se parecer com isso:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
fonte
3
Eu não acho que você queira Class.isAssignableFrom.
Tom Hawtin - defina
@ Tom, eu escrevi isso ontem à noite de memória e o corrigi para realmente passar na aula (duh!), Mas, caso contrário, não entendo por que você não iria querer (talvez eu precise de mais café hoje de manhã, estou apenas no meu primeiro copo).
Yishai
Eu estou com o Tom. Você poderia esclarecer isso? Usar isAssignableFrom () teria sido minha escolha para o trabalho. Talvez esteja faltando alguma coisa?
Luis.espinal
6
@ luis, o comentário de Tom provavelmente significava que eu deveria usar isInstance () e passar o parâmetro arg0 real. Tem a vantagem de evitar a verificação nula.
Yishai
1
Eu tenho uma situação semelhante, mas não consigo entender completamente a resposta. Você pode esclarecer melhor para manequins como eu?
Rubens Mariuzzo
40

Duas opções para verificação do tipo de tempo de execução com genéricos:

Opção 1 - Corrompa seu construtor

Vamos supor que você esteja substituindo indexOf (...) e deseje verificar o tipo apenas para desempenho, para economizar a iteração de toda a coleção.

Faça um construtor imundo como este:

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

Em seguida, você pode usar isAssignableFrom para verificar o tipo.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Cada vez que você instancia seu objeto, você precisa se repetir:

new MyCollection<Apples>(Apples.class);

Você pode decidir que não vale a pena. Na implementação de ArrayList.indexOf (...) , eles não verificam se o tipo corresponde.

Opção 2 - Deixe falhar

Se você precisar usar um método abstrato que exija seu tipo desconhecido, tudo o que você realmente deseja é que o compilador pare de chorar sobre instanceof . Se você tem um método como este:

protected abstract void abstractMethod(T element);

Você pode usá-lo assim:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Você está convertendo o objeto para T (seu tipo genérico), apenas para enganar o compilador. Seu elenco não faz nada em tempo de execução , mas você ainda receberá uma ClassCastException ao tentar passar o tipo errado de objeto para o seu método abstrato.

OBSERVAÇÃO 1: Se você estiver fazendo conversões adicionais não verificadas no seu método abstrato, suas ClassCastExceptions serão capturadas aqui. Isso pode ser bom ou ruim, então pense bem.

NOTA 2: Você recebe uma verificação nula gratuita ao usar instanceof . Como você não pode usá-lo, pode ser necessário verificar nulo com as próprias mãos.

SharkAlley
fonte
18

Postagem antiga, mas uma maneira simples de fazer a verificação de instanceOf genérica.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
fonte
21
Não está claro o que você está realmente contribuindo aqui.
Andrew
12

Desde que sua classe estenda uma classe com um parâmetro genérico, você também pode obtê-lo em tempo de execução via reflexão e usá-lo para comparação, ou seja,

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

No caso acima, em tempo de execução, você obterá String.class de getParameterizedClass (), e ele fará cache, para que você não receba nenhuma sobrecarga de reflexão após várias verificações. Observe que você pode obter os outros tipos parametrizados por índice no método ParameterizedType.getActualTypeArguments ().

terryscotttaylor
fonte
7

Eu tive o mesmo problema e aqui está a minha solução (muito humilde, @george: desta vez compilando E trabalhando ...).

Meu probem estava dentro de uma classe abstrata que implementa o Observer. O Observable aciona a atualização do método (...) com a classe Object que pode ser qualquer tipo de Object.

Eu só quero manipular objetos do tipo T

A solução é passar a classe para o construtor para poder comparar tipos em tempo de execução.

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

Para a implementação, basta passar a classe para o construtor

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}
hsaturn
fonte
5

Ou você pode pegar uma tentativa fracassada de lançar em E eg.

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
fonte
1
+1 Uma solução pragmática e não projetada em excesso para uma verificação simples! Eu gostaria de poder votar mais sobre isso #
higuaro 30/10/12
24
Isso não funcionaria. E é apagado no tempo de execução, para que o elenco não falhe, você receberá apenas um aviso do compilador.
Yishai 9/01/13
1

Tecnicamente, você não precisa, esse é o ponto dos genéricos, para que você possa fazer a verificação do tipo de compilação:

public int indexOf(E arg0) {
   ...
}

mas o @Override pode ser um problema se você tiver uma hierarquia de classes. Caso contrário, veja a resposta de Yishai.

Jason S
fonte
Sim, a interface List exige que a função use um parâmetro de objeto.
22610 Nick Heiner
você está implementando a lista? Por que você não está implementando a Lista <E>?
21710 Jason S
(veja, por exemplo, a declaração de ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: o método indexOf () da interface List não exige que o argumento fornecido seja do mesmo tipo que o objeto que você está procurando. ele só precisa ser .equals () e objetos de tipos diferentes podem ser .equals () entre si. Esse é o mesmo problema do método remove (), consulte: stackoverflow.com/questions/104799/…
newacct 15/10/2009
1
[[[timidamente]]] não importa, eu pensei que indexOf () exigia E como parâmetro em vez de Object. (por que eles fazem isso ???!)
Jason S
1

O tipo de tempo de execução do objeto é uma condição relativamente arbitrária para filtrar. Sugiro manter essa imundície longe de sua coleção. Isso é simplesmente conseguido ao delegar sua coleção a um filtro passado em uma construção.

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );
Tom Hawtin - linha de orientação
fonte
(Embora você pode querer fazer uma verificação de tipo de instância se você estiver usando Comparatorou similar.)
Tom Hawtin - tackline