¿Por qué no es legal tener los siguientes dos métodos en la misma clase?
class Test{
void add(Set<Integer> ii){}
void add(Set<String> ss){}
}
Consigo el compilation error
El método add (Set) tiene el mismo add de borrado (Set) que otro método del tipo Test.
Si bien puedo solucionarlo, me preguntaba por qué a javac no le gusta esto.
Puedo ver que en muchos casos, la lógica de esos dos métodos sería muy similar y podría ser reemplazada por una sola
public void add(Set<?> set){}
método, pero este no es siempre el caso.
Esto es muy molesto si quieres tener dos constructors
que tomen esos argumentos porque entonces no puedes simplemente cambiar el nombre de uno de los constructors
.
List
y no sé cómo manejarlo.Respuestas:
Esta regla está destinada a evitar conflictos en el código heredado que todavía usa tipos sin formato.
Aquí hay una ilustración de por qué esto no estaba permitido, extraído del JLS. Supongamos que antes de que se introdujeran genéricos en Java, escribí un código como este:
Extiendes mi clase, así:
Después de la introducción de los genéricos, decidí actualizar mi biblioteca.
No estás listo para hacer ninguna actualización, así que dejas tu
Overrider
clase sola. Para anular correctamente eltoList()
método, los diseñadores del lenguaje decidieron que un tipo sin formato era "equivalente de anulación" a cualquier tipo genérico. Esto significa que aunque la firma de su método ya no es formalmente igual a la firma de mi superclase, su método todavía se anula.Ahora, pasa el tiempo y decides que estás listo para actualizar tu clase. Pero te equivocas un poco y, en lugar de editar el
toList()
método en bruto existente, agregas un método nuevo como este:Debido a la equivalencia de anulación de tipos sin formato, ambos métodos tienen una forma válida para anular el
toList(Collection<T>)
método. Pero, por supuesto, el compilador necesita resolver un único método. Para eliminar esta ambigüedad, no se permite que las clases tengan múltiples métodos que sean equivalentes a la anulación, es decir, múltiples métodos con los mismos tipos de parámetros después del borrado.La clave es que esta es una regla de idioma diseñada para mantener la compatibilidad con el código antiguo utilizando tipos sin formato. No es una limitación requerida por el borrado de los parámetros de tipo; debido a que la resolución del método se produce en tiempo de compilación, habría sido suficiente agregar tipos genéricos al identificador del método.
fuente
javac
.List<?>
y algunosenum
que defina para indicar el tipo. La lógica específica del tipo en realidad podría estar en un método en la enumeración. Alternativamente, es posible que desee crear dos clases diferentes, en lugar de una clase con dos constructores diferentes. La lógica común estaría en una superclase o un objeto auxiliar al que delegan ambos tipos.Los genéricos de Java usan borrado de tipo. El bit en los corchetes angulares (
<Integer>
y<String>
) se elimina, por lo que terminaría con dos métodos que tienen una firma idéntica (laadd(Set)
que ve en el error). Eso no está permitido porque el tiempo de ejecución no sabría cuál usar para cada caso.Si Java alguna vez obtiene genéricos reificados, entonces podría hacer esto, pero probablemente eso sea poco probable ahora.
fuente
Esto se debe a que los genéricos de Java se implementan con Type Erasure .
Sus métodos se traducirían, en tiempo de compilación, a algo como:La resolución del método ocurre en tiempo de compilación y no considera los parámetros de tipo. ( ver la respuesta de erickson )
Ambos métodos tienen la misma firma sin los parámetros de tipo, de ahí el error.
fuente
El problema es eso
Set<Integer>
y enSet<String>
realidad son tratados como unSet
JVM. Seleccionar un tipo para el Set (String o Integer en su caso) es solo azúcar sintáctico utilizado por el compilador. La JVM no puede distinguir entreSet<String>
ySet<Integer>
.fuente
Set
, pero dado que la resolución del método ocurre en tiempo de compilación, cuando la información necesaria está disponible, esto no es relevante. El problema es que permitir estas sobrecargas entraría en conflicto con la asignación de tipos sin formato, por lo que se hicieron ilegales en la sintaxis de Java.(Ljava/util/Collection;)Ljava/util/List;
que no funciona. Podrías usarlo(Ljava/util/Collection<String>;)Ljava/util/List<String>;
, pero ese es un cambio incompatible y te encontrarás con problemas irresolubles en lugares donde todo lo que tienes es un tipo borrado. Probablemente tendrías que borrar por completo, pero eso es bastante complicado.Definir un solo método sin tipo como
void add(Set ii){}
Puede mencionar el tipo al llamar al método según su elección. Funcionará para cualquier tipo de conjunto.
fuente
Podría ser posible que el compilador traduzca Set (Integer) a Set (Object) en código de bytes java. Si este es el caso, Set (Integer) se usaría solo en la fase de compilación para la verificación de sintaxis.
fuente
Set
. Los genéricos no existen en el código de byte, son azúcar sintáctica para la fundición y brindan seguridad en tiempo de compilación.Me topé con esto cuando intenté escribir algo como:
Continuable<T> callAsync(Callable<T> code) {....}
y seContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
convirtieron para el compilador en las 2 definiciones deContinuable<> callAsync(Callable<> veryAsyncCode) {...}
La eliminación de tipo literalmente significa borrar la información de los argumentos de tipo de los genéricos. Esto es MUY molesto, pero es una limitación que estará con Java por un tiempo. Para el caso de los constructores, no se puede hacer mucho, por ejemplo, 2 nuevas subclases especializadas con diferentes parámetros en el constructor. O use métodos de inicialización en su lugar ... (¿constructores virtuales?) Con diferentes nombres ...
para métodos de operación similares, el cambio de nombre ayudaría, como
O con algunos nombres más descriptivos, autodocumentados para casos de oyu, like
addNames
yaddIndexes
or such.fuente