| para bien o para mal | |
Para mucha gente, programar sigue pareciendo un poco brujería. Lo bueno es que se aprende estudiando, como decía la madre del cartel.
Evita los problemas de instalación
Nunca instales una versión nueva de Eclipse sobre una versión antigua. Renombra la instalación antigua para quitarla del camino y descomprime la versión nueva en un directorio limpio.
Recupera tu workspace dañado
Un workspace dañado es una causa común de problemas entre muchos desarrolladores. Si tu instalación de
eclipse comienza a tener errores o un comportamiento inestable, puede que sea tiempo de darle un nuevo comienzo. Ejecuta
eclipse con la opción -clean y todo los datos en cache y de ejecución serán eliminados. Esto a menudo ayuda a resolver algunos problemas con plug-ins y mejora la estabilidad en general.
Incrementa el espacio de memoria
Cuando se agregan nuevos plugins al nucleo de funcionalidad de
eclipse o cuando se necesita usar plug-ins de terceros, los requerimientos de memoria de eclipse se incrementan. La memoria default configurada para eclipse deja de ser suficiente para muchos proyectos de tipo empresarial y eso causa una respuesta lenta por parte del IDE. Si comienzas a tener errores de falta de memoria o respuesta lenta, quizá debas incrementar los valores por default establecidos en el archivo eclipse.ini en el directorio de instalación. En particular, si llegas a tener un error del tipo «PermGem» (permanent generation), debes incluir esta línea al final del archivo y reiniciar eclipse
XX:MaxPermSize=256m
Usa la menor cantidad de memoria menor que puedas pero que sea suficiente para ejecutar bien tus proyectos.
Edicion Side by side
Puedes editar un mismo archivo desde dos puntos diferentes usando el comando Window>New Editor. Es mas cómodo y mejora la productividad.
Deja que mejore tu código automáticamente
Configura Eclipse para que formatee el código automáticamente y organice los «imports» cada vez que salvas. Usa la opción en Window>Preferences>Java Editor>Save Actions para habilirtar estas opciones. Esta ventana de dialogo te permitirá configurar acciones como la de remover «casts» incesarios o agregar notaciones faltantes. Configura tu eclipse para optimizar la compilación, formateo y estilo de código.
Utiliza los teclas de aceso directo
Esto mas productivo y conveniente usar las teclas de acceso directo para ejecutar algunas tareas en eclipse en lugar de buscar las opciones que hacen lo mismo en los menús de navegación. Por ejemplo al buscar las referencias de una variable, método o una clase pueden ser mas fácilmente encontradas mediante el acceso directo Ctrl+Shif+g. Busca alguna ficha tácnica con todos los accesos directos de la IDE, son muy comunes y fáciles de encontrar.
via: javacodegeeks.com
Durante casi treinta años, todo programa de Java empezó por public static void main(String[] args). ¿Por qué cada una de esas palabras? ¿Qué pasa si lo declaras privado? Y una novedad: desde Java 25 ya no hace falta escribirlo así.
También te puede interesar
public class Privado {
private static void main(String[] args) {
System.out.println("hola");
}
}javac Privado.java java Privado
salidaError: Main method not found in class Privado, please define the main method as: public static void main(String[] args) or a JavaFX application class must extend javafx.application.Application [código 1]
Compila sin protestar, porque para el compilador es un método privado más. El que falla es el lanzador, el programa java, al buscar por dónde empezar: no encuentra un main que pueda llamar y termina con código 1. Es el mismo mensaje en Java 21 y en Java 25.
| Palabra | Por qué |
|---|---|
public | Quien llama al método es el lanzador, desde fuera de la clase y del paquete. Necesita poder verlo. |
static | Al arrancar no existe ningún objeto de la clase. Un método estático se puede llamar sin crear uno. |
void | El programa no devuelve su resultado con return. El código de salida que ve el sistema operativo se fija con System.exit(n). |
String[] args | Los argumentos de la línea de órdenes, ya separados. args[0] es el primero, no el nombre del programa. |
El void es el que más se explica mal. No tiene que ver con que Java sea multiplataforma: un int es igual en todas partes. Es una decisión de diseño. En Java el programa puede tener varios hilos, y terminar main no tiene por qué terminar el programa, así que no tendría sentido que su return fuera el código de salida. Para eso está System.exit:
public class Codigo {
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("falta el nombre del archivo");
System.exit(2);
}
System.out.println("procesando " + args[0]);
}
}java Codigo / java Codigo datos.csvfalta el nombre del archivo [código 2] procesando datos.csv [código 0]
Si main termina con normalidad, el código es 0. Si se escapa una excepción, el programa termina con código 1, sin necesidad de llamar a nada:
java Fallaantes del error Exception in thread "main" java.lang.NumberFormatException: For input string: "doce" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:565) at java.base/java.lang.Integer.parseInt(Integer.java:662) at Falla.main(Falla.java:4) [código 1]
Java 25 incorpora de forma definitiva los archivos fuente compactos y los main de instancia. Para un programa pequeño basta con esto:
void main() {
IO.println("hola desde un main de Java 25");
}java Moderno.javahola desde un main de Java 25
Sin clase, sin public, sin static y sin args. El compilador envuelve el archivo en una clase implícita, e IO.println es un atajo nuevo para escribir en la consola. Dentro de una clase normal, el main también puede ser de instancia:
public class Instancia {
private final String saludo = "hola desde un main de instancia";
void main() {
System.out.println(saludo);
}
}java Instanciahola desde un main de instancia
Si el main no es estático, el lanzador crea primero un objeto con el constructor sin parámetros y después llama al método. Por eso aquí se puede usar el campo saludo directamente.
En Java 21 nada de esto funciona sin activar las funciones en pruebas: el archivo compacto no compila, y la clase Instancia da el mismo «Main method not found» que la de antes.
javac Moderno.java — Java 21Moderno.java:1: error: unnamed classes are a preview feature and are disabled by default.
void main() {
^
(use --enable-preview to enable unnamed classes)| Forma | Java 21 | Java 25 |
|---|---|---|
public static void main(String[] args) | Sí | Sí |
static void main(), sin public ni parámetros | No | Sí |
void main() de instancia, en una clase | No | Sí |
void main() suelto, sin clase | No | Sí |
private static void main(String[] args) | No | No |
El main puede ser público, protegido o de paquete, pero nunca privado. Y si una clase tiene dos main válidos, gana el que recibe String[] args:
public class Ambos {
public static void main(String[] args) {
System.out.println("el de String[] args");
}
static void main() {
System.out.println("el que no tiene parámetros");
}
}java Ambosel de String[] args
La forma clásica sigue funcionando igual, y es la que encontrarás en casi todo el código existente. Las nuevas están pensadas para empezar y para programas cortos, como los de nuestro curso de Java.
Entrada publicada en 2012 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.
En Java hay tres clases para trabajar con texto: String, StringBuilder y StringBuffer. La diferencia cabe en una línea: String no se puede modificar, las otras dos sí, y de esas dos solo StringBuffer está sincronizada para usarse desde varios hilos. Lo interesante es lo que eso significa en la práctica, y aquí lo vemos con ejemplos probados en Java 25.
| String | StringBuilder | StringBuffer | |
|---|---|---|---|
| ¿Se puede modificar? | No (inmutable) | Sí | Sí |
| ¿Segura entre hilos? | Sí, porque no cambia | No | Sí, sus métodos están sincronizados |
| Velocidad al construir texto | Lenta si se concatena en un bucle | La más rápida | Algo más lenta que StringBuilder |
| Desde | Java 1.0 | Java 5 | Java 1.0 |
| Úsala para… | Casi todo: valores, claves, parámetros | Construir texto por partes | Código antiguo o un búfer compartido entre hilos |
Un objeto String no cambia nunca después de crearse. Cuando parece que lo modificas, por ejemplo con +, replace o toUpperCase, en realidad Java crea un String nuevo y deja el original como estaba. Con StringBuilder pasa lo contrario: append modifica el mismo objeto, y cualquier variable que apunte a él ve el cambio.
public class Inmutable {
public static void main(String[] args) {
String saludo = "Hola";
String otro = saludo;
saludo = saludo + " mundo"; // crea un String nuevo
System.out.println(saludo); // Hola mundo
System.out.println(otro); // Hola: el original no cambió
StringBuilder sb = new StringBuilder("Hola");
StringBuilder mismo = sb;
sb.append(" mundo"); // modifica el mismo objeto
System.out.println(sb); // Hola mundo
System.out.println(mismo); // Hola mundo: es el mismo objeto
}
}java InmutableHola mundo Hola Hola mundo Hola mundo
La inmutabilidad no es un capricho. Permite que Java comparta las cadenas literales en memoria, que un String sirva sin riesgo como clave de un HashMap y que se pueda pasar a cualquier método o hilo sabiendo que nadie lo va a alterar. Lo explicamos con más detalle en qué es un objeto inmutable en Java.
La inmutabilidad tiene un coste cuando se construye un texto poco a poco. Cada s += algo crea un String nuevo y copia en él todo lo que había antes. En un bucle de cien mil vueltas, eso son cien mil copias de un texto cada vez más largo. StringBuilder, en cambio, va añadiendo al final de un búfer interno que solo crece de vez en cuando.
public class Bucle {
static final int N = 100_000;
static String conMas() {
String s = "";
for (int i = 0; i < N; i++) {
s += i % 10; // copia todo el texto en cada vuelta
}
return s;
}
static String conBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++) {
sb.append(i % 10); // añade al final del mismo búfer
}
return sb.toString();
}
public static void main(String[] args) {
for (int ronda = 0; ronda < 3; ronda++) { // la primera ronda calienta la JVM
long t0 = System.nanoTime();
String a = conMas();
long t1 = System.nanoTime();
String b = conBuilder();
long t2 = System.nanoTime();
System.out.printf("ronda %d: con + %d ms | con StringBuilder %,.2f ms | iguales: %b%n",
ronda + 1, (t1 - t0) / 1_000_000, (t2 - t1) / 1e6, a.equals(b));
}
}
}java Bucleronda 1: con + 751 ms | con StringBuilder 3.91 ms | iguales: true ronda 2: con + 559 ms | con StringBuilder 0.59 ms | iguales: true ronda 3: con + 499 ms | con StringBuilder 0.46 ms | iguales: true
El resultado es el mismo texto, pero con + tarda unas doscientas veces más en la primera ronda y más de mil veces más en las siguientes. El tiempo exacto depende del equipo y cambia en cada ejecución, pero la diferencia de escala no: con + el trabajo crece con el cuadrado del número de vueltas, y con StringBuilder, de forma lineal. La primera ronda es más lenta en los dos casos porque la máquina virtual todavía no ha optimizado el código.
+ en una sola expresión, como "Hola, " + nombre + ".", está bien y es lo más legible. El texto original de esta entrada, de 2012, decía que Java lo convierte internamente en un StringBuffer o un StringBuilder. Eso era cierto hasta Java 8. Desde Java 9, el compilador genera una llamada a makeConcatWithConstants y la máquina virtual elige la mejor forma de unir las piezas al ejecutar el programa. Se puede ver con javap.public class Concat {
static String saludo(String nombre, int edad) {
return "Hola, " + nombre + ". Tienes " + edad + " años.";
}
public static void main(String[] args) {
System.out.println(saludo("Ana", 30));
}
}javap -c Concat (extracto) static java.lang.String saludo(java.lang.String, int);
Code:
0: aload_0
1: iload_1
2: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
7: areturnStringBuilder y StringBuffer tienen exactamente los mismos métodos. Los más usados son append para añadir al final, insert para meter texto en una posición, delete y replace para quitar o sustituir un tramo, setCharAt para cambiar un carácter, reverse para darle la vuelta y toString para obtener el String final.
public class Metodos {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("decodigo");
sb.append(".com"); // decodigo.com
sb.insert(0, "https://"); // https://decodigo.com
System.out.println(sb);
sb.replace(0, 8, ""); // quita "https://"
sb.setCharAt(0, 'D'); // Decodigo.com
System.out.println(sb);
sb.delete(sb.length() - 4, sb.length()); // quita ".com"
System.out.println(sb.reverse()); // ogidoceD
System.out.println("longitud " + sb.length() + ", capacidad " + sb.capacity());
}
}java Metodoshttps://decodigo.com Decodigo.com ogidoceD longitud 8, capacidad 24
La capacidad es el tamaño del búfer interno: al crear un StringBuilder a partir de un texto, Java reserva su longitud más 16 caracteres, por eso sale 24. Cuando el texto no cabe, el búfer se amplía solo. Si sabes de antemano que vas a construir un texto grande, puedes indicar la capacidad inicial con new StringBuilder(10_000) y te ahorras esas ampliaciones.
La única diferencia entre StringBuffer y StringBuilder es que los métodos de StringBuffer están sincronizados: si dos hilos llaman a append a la vez, uno espera a que termine el otro. En este ejemplo, dos hilos añaden cien mil caracteres cada uno al mismo objeto.
public class Hilos {
public static void main(String[] args) throws InterruptedException {
for (int prueba = 1; prueba <= 3; prueba++) {
StringBuilder builder = new StringBuilder();
StringBuffer buffer = new StringBuffer();
Runnable tarea = () -> {
for (int i = 0; i < 100_000; i++) {
builder.append('x');
buffer.append('x');
}
};
Thread h1 = new Thread(tarea), h2 = new Thread(tarea);
h1.start(); h2.start();
h1.join(); h2.join();
System.out.printf("prueba %d: StringBuilder %d | StringBuffer %d (esperado 200000)%n",
prueba, builder.length(), buffer.length());
}
}
}java Hilosprueba 1: StringBuilder 193695 | StringBuffer 200000 (esperado 200000) prueba 2: StringBuilder 196891 | StringBuffer 200000 (esperado 200000) prueba 3: StringBuilder 176761 | StringBuffer 200000 (esperado 200000)
Con StringBuilder se pierden caracteres, y en cada ejecución una cantidad distinta: los dos hilos escriben a la vez en la misma posición del búfer. A veces el programa incluso falla con una ArrayIndexOutOfBoundsException. Con StringBuffer siempre salen los 200000.
StringBuilder es lo correcto y no necesita sincronización. Compartir un búfer de texto entre hilos es raro, y la sincronización de StringBuffer solo protege cada llamada por separado: dos append seguidos de un hilo pueden quedar intercalados con los de otro. StringBuffer existe sobre todo por compatibilidad con el código anterior a Java 5, cuando no había otra opción.Un error clásico: StringBuilder no redefine equals, así que dos objetos con el mismo texto no son «iguales» para Java. Para comparar el contenido, hay que pasarlos a String, usar compareTo, disponible desde Java 11, o contentEquals.
public class Comparar {
public static void main(String[] args) {
StringBuilder a = new StringBuilder("java");
StringBuilder b = new StringBuilder("java");
System.out.println(a.equals(b)); // false: compara si son el mismo objeto
System.out.println(a.toString().equals(b.toString())); // true
System.out.println(a.compareTo(b) == 0); // true (desde Java 11)
System.out.println("java".contentEquals(a)); // true
}
}java Compararfalse true true true
Muchas veces ni siquiera hace falta un StringBuilder. Para unir una lista con un separador están String.join y Collectors.joining; para repetir un texto, repeat; para dar formato, formatted; y para textos de varias líneas, los bloques de texto con tres comillas. Por debajo usan StringBuilder o algo equivalente, y el código queda más claro.
import java.util.List;
import java.util.stream.Collectors;
public class Modernos {
public static void main(String[] args) {
List<String> lenguajes = List.of("Java", "Go", "Rust");
System.out.println(String.join(", ", lenguajes)); // Java, Go, Rust
System.out.println(lenguajes.stream().collect(Collectors.joining(" | ", "[", "]")));
System.out.println("-".repeat(20)); // Java 11
System.out.println("%s tiene %d letras".formatted("Java", 4)); // Java 15
String json = """
{"lenguaje": "Java", "version": 25}
"""; // bloque de texto, Java 15
System.out.print(json);
}
}java ModernosJava, Go, Rust
[Java | Go | Rust]
--------------------
Java tiene 4 letras
{"lenguaje": "Java", "version": 25}+ para concatenar en una sola expresión.Si estás empezando con Java, en el curso de Java tienes los tipos y métodos explicados desde cero, y en medir el rendimiento en Java, cómo comparar tiempos como los de este artículo con más rigor.
Todos los ejemplos se compilaron y ejecutaron con Java 25 (Eclipse Temurin 25.0.4). Los tiempos y los recuentos de la prueba con hilos cambian en cada ejecución. Referencias: documentación de StringBuilder y StringBuffer, y JEP 280: concatenación con invokedynamic.
Un objeto es inmutable cuando su estado no puede cambiar después de crearlo. String, Integer y LocalDate lo son. Parece una limitación, pero es lo que permite compartirlos entre hilos sin miedo o usarlos como claves de un mapa. Vemos qué significa en la práctica y cómo hacer los tuyos.
También te puede interesar
No, y por eso los métodos que parecen modificarlo en realidad devuelven otro objeto:
String s = "hola"; String t = s.toUpperCase(); IO.println(s + " / " + t);
salidahola / HOLA
s sigue siendo hola. toUpperCase no tocó la cadena: creó una nueva. Lo mismo hacen replace, trim o plusDays de LocalDate. Si llamas al método y no guardas lo que devuelve, no ha pasado nada.
final List<String> lista = new ArrayList<>();
lista.add("se puede");
IO.println("final pero mutable: " + lista);
List<String> fija = List.of("a", "b");
try {
fija.add("c");
} catch (UnsupportedOperationException e) {
IO.println("List.of: " + e.getClass().getSimpleName());
}
var original = new ArrayList<>(List.of("x"));
var vista = Collections.unmodifiableList(original);
var copia = List.copyOf(original);
original.add("y");
IO.println("vista: " + vista + " copia: " + copia);java Inmutable.javahola / HOLA final pero mutable: [se puede] List.of: UnsupportedOperationException vista: [x, y] copia: [x]
Tres cosas distintas que se confunden a menudo:
| ¿Qué impide? | ¿Inmutable? | |
|---|---|---|
final List | Que la variable apunte a otra lista. La lista en sí se puede cambiar. | No |
Collections.unmodifiableList(x) | Que la cambies a través de la vista. Si alguien cambia x, la vista lo refleja (sale [x, y]). | No |
List.of(…) / List.copyOf(x) | Cualquier cambio. copyOf hace una copia propia y no le afecta lo que pase después con x. | Sí |
Desde Java 16, con un record: todos sus campos son final, no tiene métodos que los cambien, y además trae equals, hashCode y toString. Para «modificarlo» se devuelve uno nuevo:
record Punto(int x, int y) {
Punto mover(int dx, int dy) { return new Punto(x + dx, y + dy); }
}
void main() {
var p = new Punto(1, 2);
var q = p.mover(3, 0);
IO.println(p + " -> " + q);
var claves = new java.util.HashMap<Punto, String>();
claves.put(new Punto(0, 0), "origen");
IO.println(claves.get(new Punto(0, 0)));
}java Punto.javaPunto[x=1, y=2] -> Punto[x=4, y=2] origen
Si el record tiene campos que a su vez son mutables, como una lista, hay que copiarlos en el constructor con List.copyOf. Si no, quien te la pasó puede seguir cambiándola. Lo vemos con un ejemplo en las buenas prácticas de Java, en el punto 3.
De las ventajas que se suelen enumerar, esta es la que más se nota. Un HashMap coloca cada clave según su hashCode en el momento de guardarla. Si la clave cambia después, queda en un sitio que ya no le corresponde:
var mapa = new HashMap<Punto, String>(); var p = new Punto(0, 0); mapa.put(p, "origen"); p.x = 5;
java MapaMutable.java — con un Punto mutableget(new Punto(0,0)): null get(new Punto(5,0)): null tamaño: 1, containsKey(p): false
La entrada sigue en el mapa (el tamaño es 1), pero no se puede encontrar ni con la clave vieja ni con la nueva, ni siquiera con el mismo objeto p. Con el record de arriba esto no puede pasar, y por eso new Punto(0, 0) encuentra «origen» sin problema.
| Ventaja | Por qué |
|---|---|
| Se comparten entre hilos sin sincronizar | Nadie puede cambiarlos, así que ningún hilo lee un estado a medias |
| No hace falta copiarlos | Se pueden devolver y pasar sin copias defensivas |
| Siempre están en un estado válido | Se comprueban una vez, en el constructor, y ya no cambian |
| Son fáciles de probar y de razonar | El mismo objeto da siempre los mismos resultados |
El coste es crear un objeto nuevo en cada «cambio». Para objetos pequeños es despreciable; si vas a hacer miles de cambios seguidos sobre algo grande, como un texto que se va construyendo, para eso existen las versiones mutables, como StringBuilder.
Entrada publicada en 2012 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.
| Primera lección para novatos de Java |
La primera lección para quien empieza con Java: no tiene nada que ver con JavaScript, salvo el nombre y parte de la sintaxis. Si quieres aprender Java desde cero, tienes nuestro curso de Java.

Algunas cosas que solemos decir en nuestra profesión:
Tomado del artículo original de javacodegeeks.com
Una lista de quince principios que un Ingeniero de Software quizá debería seguir.