1. El JDK y las herramientas de build
Ejemplo completo: code/java-for-csharp/l01 — cada comando de abajo se ejecuta en CI en los tres sistemas operativos.
Las piezas
Sección titulada «Las piezas»| .NET | Java | Función |
|---|---|---|
| SDK de .NET | JDK (Java Development Kit) | compilador, runtime y herramientas en una sola instalación |
| CLR | JVM (HotSpot) | ejecuta el código compilado, lo compila con JIT, recolecta la basura |
IL en una .dll |
bytecode en archivos .class, comprimidos en un .jar |
el resultado de la compilación |
Roslyn (csc) |
javac |
el compilador |
dotnet run app.cs |
java Hello.java |
ejecutar un archivo fuente sin proyecto |
| REPL de C# | JShell | probar una expresión de forma interactiva |
CLI dotnet + .csproj |
Maven (pom.xml) o Gradle (build.gradle.kts) |
compilar, probar, empaquetar, resolver dependencias |
| NuGet | Maven Central | repositorio público de paquetes |
La gran diferencia: el JDK no tiene sistema de proyectos. dotnet build entiende el .csproj; javac solo entiende archivos fuente. Las dependencias, las pruebas y el empaquetado vienen de una herramienta de build aparte, y tanto Maven como Gradle son habituales.
Versiones y distribuciones
Sección titulada «Versiones y distribuciones»Cada seis meses sale una nueva versión de Java, y una de cada cuatro es una versión con soporte a largo plazo (LTS): Java 21 en 2023, Java 25 en septiembre de 2025. Es el mismo ritmo que las versiones anuales de .NET, con una LTS cada dos años. La versión del lenguaje sigue a la del JDK: no hay <LangVersion>, eliges un JDK y un destino con --release.
OpenJDK es de código abierto y varios proveedores lo compilan: Eclipse Temurin, el Microsoft Build of OpenJDK, Amazon Corretto, los builds de Oracle. Ejecutan el mismo código; se diferencian en sus condiciones de soporte y su calendario de actualizaciones. La CI de este curso usa Temurin; mi máquina ejecuta el build OpenJDK 25 de Oracle.
Instalar un JDK
Sección titulada «Instalar un JDK»winget install Microsoft.OpenJDK.25Abre una terminal nueva para que java esté en el PATH.
sudo apt install openjdk-25-jdk # Ubuntu 24.04 y versiones posterioresO, para cualquier distribución y para cambiar de una versión a otra, SDKMAN!:
curl -s "https://get.sdkman.io" | bashsdk install java 25.0.4-tembrew install openjdk@25sudo ln -sfn "$(brew --prefix)/opt/openjdk@25/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-25.jdkLa fórmula es keg-only: la segunda línea la registra para que /usr/bin/java la encuentre. SDKMAN! (consulta la pestaña Linux) también funciona en macOS.
$ java --versionopenjdk 25 2025-09-16OpenJDK Runtime Environment (build 25+36-3489)OpenJDK 64-Bit Server VM (build 25+36-3489, mixed mode, sharing)Ejecutar un solo archivo
Sección titulada «Ejecutar un solo archivo»Desde Java 25, un archivo fuente puede ser tan corto como un programa C# con instrucciones de nivel superior (JEP 512, archivos fuente compactos):
void main() { IO.println("Hello, world!");}$ java Hello.javaHello, world!java Hello.java compila el archivo en memoria y lo ejecuta; no se escribe nada en disco. Un archivo fuente compacto también importa automáticamente todo el módulo java.base, así que List, Map o Files no necesitan ningún import.
La forma clásica, que verás en cualquier base de código existente, declara una clase con un método public static void main(String[] args):
public class Greeter { public static void main(String[] args) { System.out.println("Hello, " + (args.length > 0 ? args[0] : "world") + "!"); }}Con las herramientas del JDK, compilar y ejecutar son dos pasos: javac escribe archivos .class, y java ejecuta una clase que encuentra en el class path (-cp), el equivalente en la JVM a la búsqueda de ensamblados.
javac -d out Greeter.java # escribe out\Greeter.classjava -cp out Greeter Adajavac -d out Greeter.java # escribe out/Greeter.classjava -cp out Greeter Adajavac -d out Greeter.java # escribe out/Greeter.classjava -cp out Greeter AdaHello, Ada!Al ejecutar un archivo fuente, un error de compilación lo detiene todo antes de que se ejecute nada:
void main() { int count = "three";}$ java Oops.javaOops.java:2: error: incompatible types: String cannot be converted to int int count = "three"; ^1 errorerror: compilation failedUn proyecto Maven
Sección titulada «Un proyecto Maven»Maven se basa en convenciones: pon el código donde lo espera y basta con un pom.xml muy corto.
hello-maven/├── pom.xml ← como el .csproj└── src/ ├── main/java/com/example/App.java └── test/java/ ← las pruebas van al lado, no en otro proyecto<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId> <artifactId>hello</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties></project>groupId:artifactId:version(las coordenadas) identifican el proyecto, como el ID y la versión de un paquete NuGet.groupIdsuele ser un nombre de dominio invertido.-SNAPSHOTmarca una versión todavía en desarrollo, más o menos un sufijo de versión preliminar.maven.compiler.releasees la versión de Java para la que se compila, como<TargetFramework>.- El nombre de paquete
com.exampledebe coincidir con la carpetacom/example. En C#, los espacios de nombres y las carpetas son independientes; en Java, el compilador y el cargador de clases dependen de esa coincidencia.
$ mvn package...[INFO] --- compiler:3.15.0:compile (default-compile) @ hello ---[INFO] Compiling 1 source file with javac [debug release 25] to target\classes...[INFO] --- surefire:3.5.4:test (default-test) @ hello ---[INFO] No tests to run.[INFO][INFO] --- jar:3.5.0:jar (default-jar) @ hello ---[INFO] Building jar: …\hello\target\hello-1.0-SNAPSHOT.jar[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESSmvn package ejecuta un ciclo de vida: cada fase incluye todas las anteriores, y son los plugins los que hacen el trabajo (compiler, surefire para las pruebas, jar). El primer build descarga esos plugins en ~/.m2/repository, el equivalente en Maven a la carpeta global de paquetes de NuGet.
| Tarea | dotnet | Maven | Gradle |
|---|---|---|---|
| Compilar | dotnet build |
mvn compile |
gradle build (o classes) |
| Ejecutar las pruebas | dotnet test |
mvn test |
gradle test |
| Producir el artefacto | dotnet publish |
mvn package |
gradle jar |
| Limpiar | dotnet clean |
mvn clean |
gradle clean |
| Instalar localmente para otros proyectos | feed local de NuGet | mvn install |
gradle publishToMavenLocal (con el plugin maven-publish) |
| Añadir una dependencia | dotnet add package |
editar pom.xml |
editar build.gradle.kts |
El JAR contiene las clases compiladas, pero no dice cuál arrancar:
$ java -cp target/classes com.example.AppHello from Maven!$ java -jar target/hello-1.0-SNAPSHOT.jarno main manifest attribute, in target\hello-1.0-SNAPSHOT.jarUn JAR no es un .exe: mientras su manifiesto no indique una Main-Class, java -jar no tiene nada que ejecutar. El ejercicio 2 lo corrige.
Un proyecto Gradle
Sección titulada «Un proyecto Gradle»gradle init genera un proyecto con un wrapper: scripts gradlew que descargan la versión exacta de Gradle que necesita el proyecto, para que los colaboradores no tengan que instalar Gradle ellos mismos (Maven tiene la misma idea con el Maven Wrapper).
hello-gradle/├── settings.gradle.kts ← como un .sln: enumera los proyectos├── gradle/libs.versions.toml ← catálogo de versiones: las versiones de las dependencias en un solo lugar├── gradlew, gradlew.bat ← el wrapper└── app/ ├── build.gradle.kts ← el script de build, en Kotlin └── src/main/java/com/example/App.java// app/build.gradle.kts (generado por Gradle 9.7.1, sin los comentarios)plugins { application}
repositories { mavenCentral()}
dependencies { testImplementation(libs.junit.jupiter) testRuntimeOnly("org.junit.platform:junit-platform-launcher") implementation(libs.guava)}
java { toolchain { languageVersion = JavaLanguageVersion.of(25) }}
application { mainClass = "com.example.App"}.\gradlew run./gradlew run./gradlew run> Task :app:runHello World!
BUILD SUCCESSFUL in 2sLos scripts de build de Gradle son código, lo que los hace potentes y más difíciles de leer; el XML de Maven es declarativo y verboso. Las guías de inicio de Spring ofrecen ambos. Este curso usa Maven: sus convenciones se aplican a casi cualquier proyecto Java que abras.
Puntos clave
Sección titulada «Puntos clave»- El JDK te da
java,javac,jaryjshell; el modelo de proyecto viene de Maven o de Gradle. java File.javaejecuta un solo archivo; desde Java 25 puede ser un archivo compacto convoid main().- Los paquetes deben coincidir con las carpetas, y
src/main/java/src/test/javaes la estructura que esperan todas las herramientas. - Un JAR necesita una entrada
Main-Classen el manifiesto para quejava -jarpueda ejecutarlo.
Ejercicios
Sección titulada «Ejercicios»- Sin compilarlo antes, ejecuta
Greeter.javacon tu nombre como argumento. ¿Qué hace el JDK para que esto sea posible?
Solución
$ java Greeter.java GraceHello, Grace!El modo de archivo fuente del lanzador (JEP 330, ampliado a varios archivos por la JEP 458) compila el archivo en memoria y luego ejecuta el método main de la primera clase de nivel superior con los argumentos restantes. No se escribe ningún archivo .class.
- Haz que funcione
java -jar target/hello-1.0-SNAPSHOT.jar.
Solución
Configura el plugin JAR para que escriba una entrada Main-Class en el manifiesto:
<build> <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> <version>3.5.0</version> <configuration> <archive> <manifest> <mainClass>com.example.App</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>$ mvn -q package$ java -jar target/hello-1.0-SNAPSHOT.jarHello from Maven!El META-INF/MANIFEST.MF generado contiene ahora Main-Class: com.example.App. Si la aplicación tuviera dependencias, seguirían faltando en el class path: eso es lo que resuelven los plugins de «fat JAR» (o el empaquetado propio de Spring Boot).
- En JShell, calcula
-7 % 3yMath.floorMod(-7, 3). ¿Cuál coincide con el%de C#?
Solución
jshell> -7 % 3$1 ==> -1
jshell> Math.floorMod(-7, 3)$2 ==> 2En los dos lenguajes, % es un resto que toma el signo del dividendo, así que -7 % 3 también vale -1 en C#. Math.floorMod es el módulo matemático, siempre en [0, 3) para un divisor positivo.