Tiempo de compilación frente a dependencia del tiempo de ejecución - Java

90

¿Cuál es la diferencia entre el tiempo de compilación y las dependencias del tiempo de ejecución en Java? Está relacionado con la ruta de clases, pero ¿en qué se diferencian?

Kunal
fuente

Respuestas:

78
  • Dependencia en tiempo de compilación : necesita la dependencia en su CLASSPATHpara compilar su artefacto. Se producen porque tiene algún tipo de "referencia" a la dependencia codificada en su código, como llamar newa alguna clase, extender o implementar algo (ya sea directa o indirectamente), o una llamada a un método usando la reference.method()notación directa .

  • Dependencia en tiempo de ejecución : necesita la dependencia en su CLASSPATHpara ejecutar su artefacto. Se producen porque ejecuta código que accede a la dependencia (ya sea de forma codificada o mediante reflexión o lo que sea).

Aunque la dependencia en tiempo de compilación generalmente implica una dependencia en tiempo de ejecución, puede tener una dependencia solo en tiempo de compilación. Esto se basa en el hecho de que Java solo vincula las dependencias de clase en el primer acceso a esa clase, por lo que si nunca accede a una clase en particular en tiempo de ejecución porque nunca se atraviesa una ruta de código, Java ignorará tanto la clase como sus dependencias.

Ejemplo de esto

En C.java (genera C.class):

package dependencies;
public class C { }

En A.java (genera A.class):

package dependencies;
public class A {
    public static class B {
        public String toString() {
            C c = new C();
            return c.toString();
        }
    }
    public static void main(String[] args) {
        if (args.length > 0) {
            B b = new B();
            System.out.println(b.toString());
        }
    }
}

En este caso, Atiene una dependencia en tiempo de compilación de a Ctravés B, pero solo tendrá una dependencia en tiempo de ejecución en C si pasa algunos parámetros al ejecutar java dependencies.A, ya que la JVM solo intentará resolver Bla dependencia de Ccuando llegue a ejecutarse B b = new B(). Esta característica le permite proporcionar en tiempo de ejecución solo las dependencias de las clases que usa en sus rutas de código e ignorar las dependencias del resto de las clases en el artefacto.

gpeche
fuente
1
Sé que esta es ahora una respuesta muy antigua, pero ¿cómo puede la JVM no tener C como una dependencia del tiempo de ejecución desde el principio? Si es capaz de reconocer "aquí hay una referencia a C, es hora de agregarla como una dependencia", ¿entonces C ya no es esencialmente una dependencia ya que la JVM la reconoce y sabe dónde está?
wearebob
@wearebob Supongo que podría haberse especificado de esa manera, pero decidieron que la vinculación diferida era mejor, y personalmente estoy de acuerdo por la razón mencionada anteriormente: te permite usar algún código si es necesario, pero no te obliga a incluirlo en su implementación si no la necesita. Eso es bastante útil cuando se trata de código de terceros.
gpeche
Sin embargo, si tengo un jar implementado en algún lugar, ya tendrá que contener todas sus dependencias. No sabe si se ejecutará con argumentos o no (por lo que no sabe si se usará C o no), por lo que tendría que tener C disponible de cualquier manera. Simplemente no veo cómo se ahorra memoria / tiempo al no tener C en la ruta de clases desde el principio.
wearebob
1
@wearebob un JAR no necesita incluir todas sus dependencias. Es por eso que casi todas las aplicaciones no triviales tienen un directorio / lib o similar que contiene varios archivos JAR.
gpeche
33

Un ejemplo sencillo es mirar una api como la api del servlet. Para hacer que sus servlets se compilen, necesita servlet-api.jar, pero en tiempo de ejecución, el contenedor de servlets proporciona una implementación de api de servlet, por lo que no necesita agregar servlet-api.jar a su ruta de clases en tiempo de ejecución.

Martín Algesten
fuente
Para aclarar (esto me confunde), si está usando maven y construyendo una guerra, "servlet-api" suele ser una dependencia "proporcionada" en lugar de una dependencia de "tiempo de ejecución", lo que haría que se incluyera en la guerra, si Estoy en lo correcto.
xdhmoore
2
'proporcionado' significa incluir en tiempo de compilación, pero no agruparlo en el WAR u otra colección de dependencias. 'runtime' hace lo contrario (no disponible en la compilación, pero empaquetado con WAR).
KC Baltz
29

El compilador necesita el classpath correcto para compilar llamadas a una biblioteca (compilar dependencias de tiempo)

La JVM necesita la ruta de clases correcta para cargar las clases en la biblioteca que está llamando (dependencias de tiempo de ejecución).

Pueden ser diferentes en un par de formas:

1) si su clase C1 llama a la clase de biblioteca L1 y L1 llama a la clase de biblioteca L2, entonces C1 tiene una dependencia del tiempo de ejecución en L1 y L2, pero solo una dependencia del tiempo de compilación en L1.

2) si su clase C1 crea una instancia dinámica de una interfaz I1 usando Class.forName () o algún otro mecanismo, y la clase de implementación para la interfaz I1 es la clase L1, entonces C1 tiene una dependencia del tiempo de ejecución en I1 y L1, pero solo una dependencia del tiempo de compilación en I1.

Otras dependencias "indirectas" que son las mismas para el tiempo de compilación y el tiempo de ejecución:

3) su clase C1 extiende la clase de biblioteca L1 y L1 implementa la interfaz I1 y extiende la clase de biblioteca L2: C1 tiene una dependencia de tiempo de compilación en L1, L2 e I1.

4) su clase C1 tiene un método foo(I1 i1)y un método bar(L1 l1)donde I1 es una interfaz y L1 es una clase que toma un parámetro que es la interfaz I1: C1 tiene una dependencia en tiempo de compilación de I1 y L1.

Básicamente, para hacer algo interesante, su clase necesita interactuar con otras clases e interfaces en el classpath. El gráfico de clase / interfaz formado por ese conjunto de interfaces de biblioteca produce la cadena de dependencia en tiempo de compilación. Las implementaciones de la biblioteca producen la cadena de dependencia en tiempo de ejecución. Tenga en cuenta que la cadena de dependencia en tiempo de ejecución depende del tiempo de ejecución o es lenta: si la implementación de L1 a veces depende de la instanciación de un objeto de clase L2, y esa clase solo se instancia en un escenario en particular, entonces no hay dependencia excepto ese escenario.

Jason S
fuente
1
¿No debería ser L1 la dependencia en tiempo de compilación en el ejemplo 1?
BalusC
Gracias, pero ¿cómo funciona la carga de clases en tiempo de ejecución? En el momento de la compilación, es fácil de entender. Pero en tiempo de ejecución, ¿cómo actúa, en un caso en el que tengo dos jarras de diferentes versiones? ¿Cuál elegirá?
Kunal
1
Estoy bastante seguro de que el cargador de clases predeterminado toma la ruta de clases y la recorre en orden, por lo que si tiene dos jarras en la ruta de clases que contienen la misma clase (por ejemplo, com.example.fooutils.Foo), usará el que es el primero en el classpath. O eso o obtendrá un error que indica la ambigüedad. Pero si desea obtener más información específica para los cargadores de clases, debe hacer una pregunta aparte.
Jason S
Creo que en el primer caso, las dependencias de tiempo de compilación también deberían estar allí en L2, es decir, la oración debería ser: 1) si su clase C1 llama a la clase de biblioteca L1 y L1 llama a la clase de biblioteca L2, entonces C1 tiene una dependencia de tiempo de ejecución en L1 y L2, pero solo una dependencia del tiempo de compilación en L1 y L2. Esto es así, como en el momento de la compilación también cuando el compilador java verifica L1, luego también verifica todas las otras clases referenciadas por L1 (excluyendo las dependencias dinámicas como Class.forName ("myclassname)) ... de lo contrario, ¿cómo verifica eso la compilación está funcionando bien. Explique si piensa lo contrario
Rajesh Goel
1
No. Necesita leer sobre cómo funciona la compilación y el enlace en Java. Lo único que le importa al compilador, cuando se refiere a una clase externa, es cómo usar esa clase, por ejemplo, cuáles son sus métodos y campos. No le importa lo que realmente sucede en los métodos de esa clase externa. Si L1 llama a L2, eso es un detalle de implementación de L1, y L1 ya se ha compilado en otro lugar.
Jason S
12

Java en realidad no vincula nada en tiempo de compilación. Solo verifica la sintaxis utilizando las clases coincidentes que encuentra en CLASSPATH. No es hasta el tiempo de ejecución que todo se ensambla y ejecuta según el CLASSPATH en ese momento.

JOTN
fuente
No es hasta el tiempo de carga ... el tiempo de ejecución es diferente del tiempo de carga.
sobrecambio
10

Las dependencias en tiempo de compilación son solo las dependencias (otras clases) que usa directamente en la clase que está compilando. Las dependencias en tiempo de ejecución cubren las dependencias directas e indirectas de la clase que está ejecutando. Por lo tanto, las dependencias de tiempo de ejecución incluyen dependencias de dependencias y cualquier dependencia de reflexión como nombres de clase que tiene en a String, pero que se usan en Class#forName().

BalusC
fuente
Gracias, pero ¿cómo funciona la carga de clases en tiempo de ejecución? En el momento de la compilación, es fácil de entender. Pero en tiempo de ejecución, ¿cómo actúa, en un caso en el que tengo dos jarras de diferentes versiones? ¿Qué clase recogería Class.forName () en caso de múltiples clases de diferentes clases en una ruta de clases?
Kunal
El que coincide con el nombre, por supuesto. Si realmente te refieres a "múltiples versiones de la misma clase", entonces depende del cargador de clases. Se cargará el "más cercano".
BalusC
Bueno, creo que si tienes A.jar con A, B.jar con B extends Ay C.jar con, C extends Bentonces C.jar depende del tiempo de compilación en A.jar, aunque la dependencia de C en A es indirecta.
gpeche
1
El problema en todas las dependencias en tiempo de compilación es la dependencia de la interfaz (ya sea que la interfaz sea a través de los métodos de una clase, o mediante los métodos de una interfaz, o mediante un método que contenga un argumento que sea una clase o interfaz)
Jason S
2

Para Java, la dependencia del tiempo de compilación es la dependencia de su código fuente. Por ejemplo, si la clase A llama a un método de la clase B, entonces A es dependiente de B en el momento de la compilación, ya que A tiene que conocer B (tipo de B) para ser compilado. El truco aquí debería ser el siguiente: el código compilado aún no es un código completo y ejecutable. Incluye direcciones reemplazables (símbolos, metadatos) para las fuentes que aún no están compiladas o que existen en frascos externos. Durante la vinculación, esas direcciones deben reemplazarse por direcciones reales en la memoria. Para hacerlo correctamente, deben crearse los símbolos / direcciones correctos. Y esto se puede hacer con el tipo de clase (B). Creo que esa es la principal dependencia en el momento de la compilación.

La dependencia del tiempo de ejecución está más relacionada con el flujo de control real. Invoca direcciones de memoria reales. Es una dependencia que tiene cuando se ejecuta su programa. Necesita detalles de clase B aquí como implementaciones, no solo la información de tipo. Si la clase no existe, obtendrá RuntimeException y JVM saldrá.

Ambas dependencias, generalmente y no deberían, fluyen en la misma dirección. Sin embargo, esto es una cuestión de diseño OO.

En C ++, la compilación es un poco diferente (no solo a tiempo) pero también tiene un enlazador. Entonces, el proceso podría considerarse similar a Java, supongo.

stdout
fuente