2012-05-23 10 views
8

Véase el siguiente ejemplo:¿Por qué no se puede implementar la sobrecarga en el momento de la ejecución?

interface I {} 

class A implements I {} 

class B implements I {} 

class Foo{ 
    void f(A a) {} 
    void f(B b) {} 
    static public void main(String[]args) { 
     I[] elements = new I[] {new A(), new B(), new B(), new A()}; 
     Foo o = new Foo(); 
     for (I element:elements) 
      o.f(element);//won't compile 
    } 
} 

métodos Por qué no sobrecargar apoyan upcasting?

Si se implementó la sobrecarga en el tiempo de ejecución, proporcionaría mucha más flexibilidad. Por ejemplo, el patrón de visitante sería más simple. ¿Hay alguna razón técnica que impida que Java haga esto?

+0

Estoy tratando de imaginar cómo funcionaría la semántica de este enfoque, y mi cerebro simplemente dice "ew". Espero poder cambiar la implementación que estoy usando de una interfaz, y hacer que el resto del código funcione exactamente igual. –

+0

Porque tratar estáticamente de determinar el tipo de tiempo de ejecución de un objeto sería similar a simplemente ejecutar el programa? – Perception

+1

@Perception Pregunta por qué el despacho de sobrecarga no se pudo aplazar al tiempo de ejecución en lugar de resolverse en tiempo de compilación. Parece que esto agregaría una enorme complejidad a la JVM y la lógica de carga de clases. –

Respuesta

1

Su método dice que aceptará A o B clases que se derivan de I, pueden contener más detalles a continuación I

void f(A a) {} 

Cuando intenta enviar súper clase de A en su interfaz caso I, compilador quiere una confirmación de que está realmente enviando A ya que los detalles disponibles en A pueden no estar disponibles en I, también solo durante el tiempo de ejecución I se referirá a una instancia de A que no incluye dicha información en com acumule tiempo, por lo que tendrá que decirle explícitamente al compilador que I es en realidad A o B y usted hace un yeso para decirlo.

+0

No puedo tener mucho sentido de esto, pero si la intención es decir que lo que el OP desea no se puede hacer, puede hacerlo. – EJP

6

La resolución de sobrecarga implica algunas reglas no triviales para determinar qué sobrecarga es la más adecuada, y sería difícil hacer esto de manera eficiente en el tiempo de ejecución. Por el contrario, la resolución de anulación es más fácil: en el caso difícil, solo tiene que buscar la función foo para la clase del objeto, y en el caso fácil (por ejemplo, cuando solo hay una implementación o solo una en esta ruta de código), puede convertir el método virtual en una llamada compilada estáticamente, no virtual, sin despacho dinámico (si lo hace en función de la ruta del código, debe hacer una comprobación rápida para verificar que el objeto sea en realidad el uno que esperas).

Resulta que es bueno que Java 1.4 y versiones anteriores no tengan una resolución de anulación de tiempo de ejecución, porque eso haría que los genéricos fueran mucho más difíciles de actualizar. Los genéricos desempeñan un papel en la anulación de la resolución, pero esta información no estaría disponible en tiempo de ejecución debido a la eliminación.

2

No es la respuesta a Java. existe esta funcionalidad en C# 4 sin embargo:

using System; 

public class MainClass {  
    public static void Main() { 
     IAsset[] xx = { 
      new Asset(), new House(), new Asset(), new House(), new Car() 
     };  
     foreach(IAsset x in xx) { 
      Foo((dynamic)x); 
     } 
    } 


    public static void Foo(Asset a) { 
     Console.WriteLine("Asset"); 
    } 

    public static void Foo(House h) { 
     Console.WriteLine("House"); 
    } 

    public static void Foo(Car c) { 
     Console.WriteLine("Car"); 
    }  
} 

public interface IAsset { }  

public class Asset : IAsset { } 

public class House : Asset { } 

public class Car : Asset { } 

Salida:

Asset 
House 
Asset 
House 
Car 

Si está usando C# 3 y a continuación, usted tiene que utilizar la reflexión, hice un post sobre ello en mi blog múltiple Despacho en C#: http://www.ienablemuch.com/2012/04/multiple-dispatch-in-c.html

Si desea hacer despachos múltiples en Java, puede ir por la ruta de reflexión.

Aquí hay otra solución para Java: http://blog.efftinge.de/2010/03/multiple-dispatch-and-poor-mens-patter.html

3

No creo que nadie más que los diseñadores de la lengua podría posible respuesta a esta pregunta. No soy casi un experto en el tema, pero proporcionaré solo mi opinión.

Al leer el JLS 15.12 sobre Método de expresiones de invocación, es bastante claro que elegir el método correcto para ejecutar es un proceso de tiempo de compilación ya complicado; sobre todo después de la introducción de los genéricos.

Ahora imagine mover todo esto al tiempo de ejecución solo para admitir la característica única de mutimethods. Para mí, suena como una pequeña característica que agrega demasiada complejidad al lenguaje, y probablemente sea una característica con cierta cantidad de implicaciones de rendimiento ahora que todas estas decisiones deberían tomarse, una y otra vez, en tiempo de ejecución, y no solo una vez, como lo es hoy, en tiempo de compilación.

A todo esto podríamos agregar el hecho de que debido a type erasure sería imposible determinar el tipo real de ciertos tipos genéricos. Me parece que abandonar la seguridad de la comprobación de tipos estáticos no es lo mejor para Java.

En cualquier caso, existen alternativas válidas para hacer frente al problema del envío múltiple, y quizás estas alternativas justifiquen por qué no se han implementado en el lenguaje. Por lo tanto, puede usar el clásico visitor pattern o puede usar cierta cantidad de reflexión.

Hay un anticuado MultiJava Project que implementó mutiple apoyo de despacho en Java y hay un par de otros proyectos por ahí que utilizan la reflexión para apoyar métodos múltiples en Java: Java Multimethods, Java Multimethods Framework. Quizás haya aún más.

También podría considerar un lenguaje alternativo basado en Java que admita multimétodos, como Clojure o Groovy.

Además, dado que C# es un lenguaje bastante similar a Java en su philopofia general, podría ser interesante investigar más sobre cómo es supports multimethods y meditar sobre cuáles serían las implicaciones de ofrecer una característica similar en Java. Si crees que es una característica que vale la pena tener en Java, incluso puedes enviar un JEP y se puede tener en cuenta para futuras versiones del lenguaje Java.

4

No hay teórico por lo que no se puede hacer. El sistema de objetos Common Lisp admite este tipo de construcción, llamado despacho múltiple, aunque lo hace en un paradigma algo diferente (los métodos, en lugar de adjuntarlos a los objetos, son casos de genéricos (o funciones genéricas), que pueden hacer despacho virtual en tiempo de ejecución en los valores de múltiples parámetros). Creo que también ha habido extensiones de Java para habilitarlo (me viene a la mente Multi-Java, aunque puede haber sido herencia múltiple en lugar de envío múltiple).

Puede haber, sin embargo, razones del lenguaje Java por las que no se puede hacer, además de que los diseñadores de idiomas simplemente piensan que no se debe hacer, que dejaré a otros que razonen. Sin embargo, introduce complicaciones para la herencia. Tenga en cuenta:

interface A {} 
interface B {} 
class C implements A {} 

class Foo { 
    public void invoke(A a) {} 
    public void invoke(B b) {} 
} 

class Bar extends Foo { 
    public void invoke(C c) {} 
} 

class Baz extends Bar { 
    public void invoke(A a) {} 
} 

Baz obj = new Baz(); 
obj.invoke(new C); 

¿Cuál invoke se invoca? Baz? Bar? ¿Cuál es super.invoke? Es posible encontrar una semántica determinista, pero probablemente implicará confusión y sorpresa al menos en algunos casos. Dado que Java apunta a ser un lenguaje simple, no creo que las características que introducen tal confusión puedan verse de acuerdo con sus objetivos.

2

Supongo que simplemente tiene que conformarse con la reflexión:

import java.lang.reflect.*; 

interface I {}  
class A implements I {}  
class B implements I {} 

public class Foo { 

    public void f(A a) { System.out.println("from A"); } 
    public void f(B b) { System.out.println("from B"); } 

    static public void main(String[]args) throws InvocationTargetException 
     , NoSuchMethodException, IllegalAccessException 
    { 
     I[] elements = new I[] {new A(), new B(), new B(), new A()}; 
     Foo o = new Foo(); 
     for (I element : elements) { 
      o.multiDispatch(element);  
     } 
    } 

    void multiDispatch(I x) throws NoSuchMethodException 
     , InvocationTargetException, IllegalAccessException 
    {  
     Class cls = this.getClass(); 

     Class[] parameterTypes = { x.getClass() }; 
     Object[] arguments = { x }; 

     Method fMethod = cls.getMethod("f", parameterTypes); 
     fMethod.invoke(this,arguments); 
    }  
} 

Salida:

from A 
from B 
from B 
from A 
3

¿Hay alguna razón técnica que impida Java de hacer esto?

Código corrección: el ejemplo actual proporciona dos implementaciones de I y dos correspondientes métodos f. Sin embargo, nada impide la existencia de otras clases que implementen I: mover la resolución al tiempo de ejecución también reemplazaría los errores de compilación por errores de tiempo de ejecución posiblemente ocultos.

Rendimiento: como otros han mencionado la sobrecarga de métodos implica reglas bastante complejas, hacerlo una vez en tiempo de compilación es ciertamente más rápido que hacerlo para cada invocación de método en tiempo de ejecución.

Compatibilidad hacia atrás: métodos sobrecargados actualmente se resuelven utilizando el tipo de tiempo de compilación de argumentos pasados ​​en lugar de su tipo en tiempo de ejecución, cambiar el comportamiento de utilizar la información en tiempo de ejecución se rompería una gran cantidad de aplicaciones existentes.

Cómo evitar que

utilizar el patrón de visitante, no entiendo cómo alguien podría pensar que es difícil.

interface I{ 
     void accept(IVisitor v); 
} 
interface IVisitor{ 
     void f(A a); 
     void f(B b); 
} 

class A implements I{ 
    void accept(IVisitor v){v.f(this);} 
} 
class B implements I{ 
    void accept(IVisitor v){v.f(this);} 
} 
class Foo implements IVisitor{ 
void f(A a) {} 
void f(B b) {} 
static public void main(String[]args) { 
    I[] elements = new I[] {new A(), new B(), new B(), new A()}; 
    Foo o = new Foo(); 
    for (I element:elements) 
     element.accept(o); 
} 


} 
Cuestiones relacionadas