Java: Instanceof y genéricos

135

Antes de mirar a través de mi estructura de datos genéricos para el índice de un valor, me gustaría ver si incluso thisse ha parametrizado una instancia del tipo .

Pero Eclipse se queja cuando hago esto:

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

Este es el mensaje de error:

No se puede realizar una instancia de comprobación con el parámetro de tipo E. Utilice en su lugar su objeto de borrado ya que la información de tipo genérico se borrará en tiempo de ejecución

¿Cuál es la mejor manera de hacerlo?

Nick Heiner
fuente

Respuestas:

79

El mensaje de error lo dice todo. En tiempo de ejecución, el tipo se ha ido, no hay forma de verificarlo.

Puede atraparlo haciendo una fábrica para su objeto de esta manera:

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

Y luego, en el constructor del objeto, almacene ese tipo, tan variable que su método podría verse así:

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }
Yishai
fuente
3
No creo que desee Class.isAssignableFrom.
Tom Hawtin - tackline
@Tom, escribí esto anoche de memoria, y lo arreglé para aprobar la clase (¡duh!) Pero de lo contrario, no entiendo por qué no lo querrías (tal vez necesito más café esta mañana, yo ' m solo en mi primera copa).
Yishai
Estoy con Tom ¿Podrías aclarar eso? Usar isAssignableFrom () habría sido mi elección para el trabajo. Tal vez me estoy perdiendo algo?
luis.espinal
66
@luis, el comentario de Tom probablemente significaba que debería usar isInstance () y pasar el parámetro arg0 real. Tiene la ventaja de evitar el cheque nulo.
Yishai
1
Tengo una situación similar pero no puedo entender completamente la respuesta. ¿Puedes aclararlo mejor para tontos como yo?
Rubens Mariuzzo
40

Dos opciones para la verificación del tipo de tiempo de ejecución con genéricos:

Opción 1: corrompe a su constructor

Supongamos que está anulando indexOf (...), y desea verificar el tipo solo para el rendimiento, para ahorrarse iterando toda la colección.

Haz un constructor sucio como este:

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

    this.t = t;
}

Luego puede usar isAssignableFrom para verificar el tipo.

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

Cada vez que crea una instancia de su objeto, tendría que repetirlo:

new MyCollection<Apples>(Apples.class);

Podrías decidir que no vale la pena. En la implementación de ArrayList.indexOf (...) , no verifican que el tipo coincida.

Opción 2: dejar que falle

Si necesita utilizar un método abstracto que requiera su tipo desconocido, entonces todo lo que realmente desea es que el compilador deje de llorar por la instancia de . Si tienes un método como este:

protected abstract void abstractMethod(T element);

Puedes usarlo así:

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

Estás lanzando el objeto a T (tu tipo genérico), solo para engañar al compilador. Su conversión no hace nada en tiempo de ejecución , pero aún obtendrá una ClassCastException cuando intente pasar el tipo de objeto incorrecto a su método abstracto.

NOTA 1: Si está realizando conversiones adicionales no verificadas en su método abstracto, sus ClassCastExceptions quedarán atrapadas aquí. Eso podría ser bueno o malo, así que piénselo bien.

NOTA 2: Obtiene una comprobación nula gratuita cuando utiliza instanceof . Como no puede usarlo, es posible que deba verificar si es nulo con sus propias manos.

TiburónAlley
fuente
18

Publicación anterior, pero una forma simple de hacer una instancia genérica de verificación.

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}
Jonas Pedersen
fuente
21
No está claro qué estás contribuyendo realmente aquí.
Andrew
12

Siempre que su clase extienda una clase con un parámetro genérico, también puede obtener esto en tiempo de ejecución mediante reflexión, y luego usarlo para comparar, es decir

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

En el caso anterior, en tiempo de ejecución obtendrá String.class de getParameterizedClass (), y se almacena en caché para que no obtenga ningún reflejo sobrecarga en múltiples comprobaciones. Tenga en cuenta que puede obtener los otros tipos parametrizados por índice desde el método ParameterizedType.getActualTypeArguments ().

terryscotttaylor
fuente
7

Tuve el mismo problema y aquí está mi solución (muy humilde, @george: esta vez compilando Y trabajando ...).

Mi probem estaba dentro de una clase abstracta que implementa Observer. La actualización del método de incendios Observable (...) con la clase Object que puede ser cualquier tipo de Object.

Solo quiero manejar objetos del tipo T

La solución es pasar la clase al constructor para poder comparar tipos en tiempo de ejecución.

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 la implementación solo tenemos que pasar la clase al constructor

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

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

O podría atrapar un intento fallido de lanzar en E, por ejemplo

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}
ben
fuente
1
+1 ¡Una solución pragmática sin ingeniería para un simple control! Ojalá pudiera votar más esto
higuaro
24
Esto no funcionaria. E se borra en tiempo de ejecución, por lo que el elenco no fallará, solo recibirá una advertencia del compilador al respecto.
Yishai
1

Técnicamente no debería tener que hacerlo, ese es el punto de los genéricos, por lo que puede hacer una verificación de tipo de compilación:

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

pero luego el @Override puede ser un problema si tiene una jerarquía de clases. De lo contrario, vea la respuesta de Yishai.

Jason S
fuente
Sí, la interfaz de Lista exige que la función tome un parámetro de objeto.
Nick Heiner el
estás implementando List? ¿Por qué no estás implementando la Lista <E>?
Jason S
(véase, por ejemplo, la declaración de ArrayList: java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html )
Jason S
@Rosarch: el método indexOf () de la interfaz de lista no requiere que el argumento dado sea del mismo tipo que el objeto que está buscando. solo tiene que ser .equals (), y los objetos de diferentes tipos pueden ser .equals () entre sí. Este es el mismo problema que para el método remove (), consulte: stackoverflow.com/questions/104799/…
newacct el
1
[[[tímidamente]]] no importa, pensé que indexOf () requería E como parámetro en lugar de Object. (¿Por qué hicieron eso?!?!)
Jason S
1

El tipo de tiempo de ejecución del objeto es una condición relativamente arbitraria para filtrar. Sugiero mantener esa suciedad lejos de su colección. Esto se logra simplemente haciendo que su colección delegue en un filtro pasado en una construcción.

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 - tackline
fuente
(Aunque es posible que desee hacer una comprobación tipo de instancia si está utilizando Comparatoro similar.)
Tom Hawtin - tackline