2009-10-15 11 views
91

estoy experimentando con este código:selección del método sobrecargado en función del tipo de bienes del parámetro

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)?

Respuesta

72

lo esperado El método de selección para tomar en cuenta la real (no la declarado) tipo de parámetro. ¿Me falta algo?

Sí. Tu expectativa es incorrecta. En Java, el envío de métodos dinámicos 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 Java Language Specification:

Cuando se invoca un método (§15.12), el número de argumentos reales (y cualquier argumentos de tipo explícito) y los tipos de tiempo de compilación de los argumentos se utilizan, en tiempo de compilació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 , se invocará el método real a al ejecutar el tiempo , usando la búsqueda de método dinámico (§15.12.4).

+3

¿Puede explicar las especificaciones que citó por favor? Las dos oraciones parecen contradecirse entre sí. El ejemplo anterior usa métodos de instancia, sin embargo, el método invocado claramente no se está determinando en tiempo de ejecución. –

+10

@Alex Worden: el tipo de tiempo de compilación ** de los parámetros del método ** se usa para determinar la firma del método a llamar, en este caso 'foo (Object)'. En tiempo de ejecución, la clase del ** objeto al que se llama el método ** determina qué implementación de ese método se llama, teniendo en cuenta que puede ser una instancia de una subclase del tipo declarado que anula el método. –

12

La capacidad de enviar una llamada a un método basado en tipos de argumentos se llama multiple dispatch. En Java esto se hace con Visitor pattern.

Sin embargo, como se trata de Integer sy String s, no puede incorporar fácilmente este patrón (simplemente no puede modificar estas clases). Por lo tanto, un arma switch en tiempo de ejecución de objetos será su arma de elección.

11

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

El patrón típico para solucionar este problema es comprobar el tipo de objeto en el método con la firma Object y delegar en el 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 difícil de manejar, a continuación, la sobrecarga de métodos probablemente no es el enfoque correcto, en lugar del método público sólo debe tomar objetos e implementar algún tipo de patrón de estrategia de delegar el manejo adecuado según el tipo de objeto .

2

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

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

También podría emitir su params como el tipo del parámetro:

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

Como se mencionó antes, la resolución de sobrecarga se realiza en tiempo de compilación.

Java Puzzlers tiene un buen ejemplo de ello:

Puzzle 46: El caso del Constructor confuso

Este puzzle se presenta con dos constructores confuso. El método principal invoca un constructor, pero ¿cuál? La salida del programa depende de la respuesta. ¿Qué imprime el programa, o es incluso 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

... proceso de resolución de sobrecarga de Java funciona 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 programa, ambos constructores son accesibles y aplicables. El constructor Confusing (Objeto) acepta cualquier parámetro pasado a Confusing (double []), de modo Confusing (Objeto) es menos específica. (Cada doble matriz es un Objeto, pero no todas Objeto es una doble matriz.) Por consiguiente, el constructor más específico es Confusing (double []), lo que explica la salida del programa.

Este comportamiento tiene sentido si pasa un valor de tipo double []; es contra-intuitivo si pasa nulo. La clave para entender este rompecabezas es que la prueba para la que el método o el 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 compilador 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 declaración.

para invocar el Confusing (Objeto) constructor con un parámetro nulo , escribir nuevo Confusing ((Object) null). Esto asegura que solo Confusing (Object) es aplicable.Más en general, para obligar al compilador a seleccionar una sobrecarga específica, eche parámetros reales a los tipos declarados de los parámetros formales.

+4

Espero que no sea demasiado tarde para decir: "una de las mejores explicaciones sobre SOF". Gracias :) – TheLostMind

+3

Nunca es demasiado tarde para decir buenas palabras :) –

+4

Creo que si también agregamos el constructor 'Confundir privado (int [] iArray)' no se compilaría, ¿no es así? Porque ahora hay dos constructores con la misma especificidad. – Risser

4

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. Dado una matriz de Objetos, quiero convertir 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 a buscar el 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<Parameter> 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; 
} 

Sin fea instanceof, cambiar los estados, o patrón visitante requiere! :)

0

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, entonces ese es el método que se invocará. Está utilizando referencias de objetos, por lo que java decide en tiempo de compilación que para Object param, hay un método que acepta directamente Object. Entonces llamó a ese método 3 veces.

Cuestiones relacionadas