Selección de método sobrecargado basado en el tipo real del parámetro

Estoy experimentando con este código:

interface Callee { public void foo(Object o); public void foo(String s); public void foo(Integer i); } class CalleeImpl implements Callee public void foo(Object o) { logger.debug("foo(Object o)"); } public void foo(String s) { logger.debug("foo(\"" + s + "\")"); } public void foo(Integer i) { logger.debug("foo(" + i + ")"); } } Callee callee = new CalleeImpl(); Object i = new Integer(12); Object s = "foobar"; Object o = new Object(); callee.foo(i); callee.foo(s); callee.foo(o); 

Esto imprime foo(Object o) tres veces. Espero que la selección del método tome en consideración el tipo de parámetro real (no el declarado). ¿Me estoy perdiendo de algo? ¿Hay alguna manera de modificar este código para que imprima foo(12) , foo("foobar") y foo(Object o) ?

Espero que la selección del método tome en consideración el tipo de parámetro real (no el declarado). ¿Me estoy perdiendo de algo?

Sí. Tu expectativa es incorrecta. En Java, el envío de métodos dynamics ocurre solo para el objeto al que se llama el método, no para los tipos de parámetros de los métodos sobrecargados.

Citando la Especificación del lenguaje Java :

Cuando se invoca un método (§15.12), el número de argumentos reales (y cualquier argumento de tipo explícito) y los tipos de tiempo de comstackción de los argumentos se usan, en tiempo de comstackción, para determinar la firma del método que se invocará ( §15.12.2). Si el método que se va a invocar es un método de instancia, el método real que se invocará se determinará en tiempo de ejecución, utilizando la búsqueda dinámica de métodos (§15.12.4).

Como se mencionó anteriormente, la resolución de sobrecarga se realiza en tiempo de comstackción.

Java Puzzlers tiene un buen ejemplo para eso:

Puzzle 46: El caso del constructor confuso

Este rompecabezas te presenta dos constructores confusos. El método principal invoca un constructor, pero ¿cuál? La salida del progtwig depende de la respuesta. ¿Qué imprime el progtwig o incluso es legal?

 public class Confusing { private Confusing(Object o) { System.out.println("Object"); } private Confusing(double[] dArray) { System.out.println("double array"); } public static void main(String[] args) { new Confusing(null); } } 

Solución 46: caso del constructor confuso

… El proceso de resolución de sobrecarga de Java opera en dos fases. La primera fase selecciona todos los métodos o constructores que son accesibles y aplicables. La segunda fase selecciona el más específico de los métodos o constructores seleccionados en la primera fase. Un método o constructor es menos específico que otro si puede aceptar cualquier parámetro pasado al otro [JLS 15.12.2.5].

En nuestro progtwig, ambos constructores son accesibles y aplicables. El constructor Confusing (Object) acepta cualquier parámetro pasado a Confusing (double []) , por lo que Confusing (Object) es menos específico. (Cada matriz doble es un Objeto , pero no todos los Objetos son una matriz doble .) El constructor más específico es, por lo tanto, Confuso (doble []) , lo que explica la salida del progtwig.

Este comportamiento tiene sentido si pasa un valor de tipo double [] ; es contra intuitivo si pasas nulo . La clave para entender este enigma es que la prueba para qué método o constructor es más específico no usa los parámetros reales : los parámetros que aparecen en la invocación. Se usan solo para determinar qué sobrecargas son aplicables. Una vez que el comstackdor determina qué sobrecargas son aplicables y accesibles, selecciona la sobrecarga más específica, utilizando solo los parámetros formales: los parámetros que aparecen en la statement.

Para invocar el constructor Confusing (Object) con un parámetro null , escriba confusing (Object) null nuevo) . Esto asegura que solo se aplica Confusing (Object) . De manera más general, para obligar al comstackdor a seleccionar una sobrecarga específica, emite los parámetros reales a los tipos declarados de los parámetros formales.

La capacidad de enviar una llamada a un método basado en tipos de argumentos se denomina despacho múltiple . En Java, esto se hace con el patrón Visitor .

Sin embargo, dado que se trata de Integer y String , no puede incorporar fácilmente este patrón (simplemente no puede modificar estas clases). Por lo tanto, un switch gigante en el tiempo de ejecución de los objetos será tu arma de elección.

En Java, el método para llamar (como qué firma de método usar) se determina en tiempo de comstackción, por lo que va con el tipo de tiempo de comstackción.

El patrón típico para evitar esto es verificar el tipo de objeto en el método con la firma del Objeto y delegar al método con un molde.

  public void foo(Object o) { if (o instanceof String) foo((String) o); if (o instanceof Integer) foo((Integer) o); logger.debug("foo(Object o)"); } 

Si tiene muchos tipos y esto es inmanejable, entonces la sobrecarga de métodos probablemente no sea el enfoque correcto, más bien el método público solo debería tomar Object e implementar algún tipo de patrón de estrategia para delegar el manejo apropiado por tipo de objeto.

Tuve un problema similar al llamar al constructor correcto de una clase llamada “Parámetro” que podría tomar varios tipos básicos de Java, como String, Integer, Boolean, Long, etc. Dada una matriz de Objetos, quiero convertirlos en una matriz de mis objetos Parameter llamando al constructor más específico para cada objeto en la matriz de entrada. También quería definir el parámetro de constructor (Objeto o) que lanzaría una IllegalArgumentException. Por supuesto, encontré este método invocado para cada objeto en mi matriz.

La solución que utilicé fue buscar al constructor a través de la reflexión …

 public Parameter[] convertObjectsToParameters(Object[] objArray) { Parameter[] paramArray = new Parameter[objArray.length]; int i = 0; for (Object obj : objArray) { try { Constructor cons = Parameter.class.getConstructor(obj.getClass()); paramArray[i++] = cons.newInstance(obj); } catch (Exception e) { throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e); } } return paramArray; } 

¡No se requiere instancia desagradable, declaraciones de cambio o patrón de visitante! 🙂

Java mira el tipo de referencia cuando intenta determinar qué método llamar. Si desea forzar su código, elija el método “correcto”, puede declarar sus campos como instancias del tipo específico:

 Integeri = new Integer(12); String s = "foobar"; Object o = new Object(); 

También puedes convertir tus params como tipo de param:

 callee.foo(i); callee.foo((String)s); callee.foo(((Integer)o); 

Si hay una coincidencia exacta entre el número y los tipos de argumentos especificados en la llamada al método y la firma del método de un método sobrecargado, ese es el método que se invocará. Está utilizando referencias de objetos, por lo que java decide en tiempo de comstackción que para Object param, hay un método que acepta directamente Object. Entonces llamó a ese método 3 veces.