Mostrando entradas con la etiqueta Java. Mostrar todas las entradas
Mostrando entradas con la etiqueta Java. Mostrar todas las entradas

jueves, 5 de marzo de 2026

■ ¿Por qué encapsular?

Para entender el encapsulamiento debe entenderse primero cómo funciona la Programación Orientada a Objetos (POO).

Cuando hablamos de Objetos en programación, debemos pensar en clases y propiedades, una clase es la representación del objeto del mundo real, por ejemplo, si queremos manejar empleados crearíamos una clase llamada "Empleado", y así como el empleado tiene características que lo identifican, la clase tiene propiedades, que serían las características del empleado, por ejemplo, el empleado tiene nombre, teléfono, correo, pertenece a un departamento y tiene un sueldo, todos estos datos los podemos representar como propiedades de la clase, a esto se le llama abstracción.

Luego de hacer la abstracción, la clase empleado podría quedar así:

class Empleado {
    String nombre;
    Strign telefono;
    String correo;
    double sueldo;
}


Para acceder a dichas propiedades desde la clase principal, basta con instanciar la clase Empleado, al crear un objeto de la misma dicho objeto automáticamente ya tiene las propiedades de la clase, por ejemplo:

public class Control {
    public static void main(String[] args) {
        Empleado emp = new Empleado(); // Instancia de Empleado

        //emp es el objeto Empleado
        //y puede disponer de sus propiedades así
        emp.nombre = "John Smith";
        emp.telefono = "123456789";
        emp.correo = "jsmith@domain.com";
        emp.sueldo = 10000.00;
    }
}


Hasta aquí todo parece correcto, creé un objeto Empleado y luego lo inicialicé accediendo directamente a sus propiedades, eso funciona perfectamente, si luego de asignar los datos del empleado los mostrara en pantalla así:

    System.out.println("Nombre: " + emp.nombre);
    System.out.println("Teléfono: " + emp.telefono);
    System.out.println("Correo: " + emp.correo);
    System.out.println("Sueldo: " + emp.sueldo);



Esto funcionaría perfectamente, pero hay un detalle: el acceso.

Como puede observarse en el ejemplo, bastó con crear el objeto "emp" de tipo "Empleado" (acción de instanciar), para que mediante este objeto se pudiera acceder a las propiedades de la clase, darle un nombre, un teléfono, un correo y un sueldo.

Hasta este punto podrían pensar "obvio, ese es el objetivo", pero aquí es donde entra un tema muy importante de la POO, el Encapsulamiento.

Encapsular es ocultar los atributos y métodos de la clase para restringir o controlar su acceso desde el exterior. ¿Por qué se hace esto?, se hace para evitar que se pueda acceder a información, que queremos mantener como privada, desde otras clases y que dichas clases solo puedan acceder a lo que nosotros permitamos o de la forma en la que se los permitamos.

Por ejemplo en la clase Empleado, ¿qué evitaría que el usuario en la clase principal (main) pusiera texto en el teléfono, o un correo mal estructurado o un sueldo negativo?, algo así:

Empleado emp = new Empleado();

// Inicializamos valores
emp.nombre = "John";
emp.telefono = "no tiene";
emp.correo = "jsmit@";
emp.sueldo = -5000;


Dado que las propiedades de Empleado son públicas, hacer lo anterior no representa un error sintáctico, es decir, es válido, se permite, pero mirándolo desde la lógica está mal, aquí es donde se aplica el encapsulamiento.


■ Encapsulamiento

Encapsular es volver privadas las propiedades de Empleado para que estas solo puedan ser accedidas dentro de la propia clase Empleado y no desde afuera. ¿Cómo hacemos esto?, simplemente les anteponemos "private":

class Empleado {
    private String nombre;
    private Strign telefono;
    private String correo;
    private double sueldo;
}

Cuando las propiedades no tienen la palabra reservada "private" se toman como públicas, para indicar que son públicas se les antepone "public", no es un error no ponérselo pero como buena práctica se sugiere colocar el "public" para cuando queramos que sean públicas.

Si colocamos las propiedades como "
private", forzamos a que el acceso a ellas solo se pueda hacer mediante los propios métodos de la clase, pero estos también deberán ser públicos, por lo que podemos tener tanto métodos públicos como privados.

A estos métodos para inicializar las variables se le llama "setters", serían de la siguiente forma:

public void setNombre(String _nombre) {
    this.nombre = _nombre;
}

public void setTelefono(String _telefono) {
    this.telefono = _telefono;
}

public void setCorreo(String _correo) {
    this.correo = _correo;
}

public void setSueldo(double _sueldo) {
    this.sueldo = _sueldo;
}


Donde:

  • "this" se refiere a que la variable es de la propia clase, no es forzoso que lleve el "this" pero se sugiere ponerlo.
  • Las variables pasadas por parámetro son los valores que se asignarán a las propiedades del objeto, mismos que pasamos al momento de llamar a los métodos.

Puestas como private las propiedades, ya no podemos hacer esto en la clase principal:

Empleado emp = new Empleado();
emp.nombre = "John";
// Esto es un error ya que la variable es privada

Ahora el acceso a las propiedades se debe hacer mediante los métodos públicos de la clase, así:

Empleado emp = new Empleado();
emp.setNombre("John");
// Asignamos el nombre del empleado
                       // mediante el método set

Ya que el método setNombre(String _nombre) es ahora el que se encarga de tomar un valor string y asignárselo a la propiedad nombre de la clase, es decir, la asignación ya no se hace directamente a la propiedad sino que se realiza a través de la clase, esto nos da mayor control sobre lo que el usuario intenta guardar y podemos decidir si lo permitimos o no.

Hasta este punto se encapsularon las propiedades pero no se ha solucionado el problema planteado anteriormente, ya que los métodos públicos que acceden a las propiedades, reciben valores y los asignan directamente a las variables. Es aquí donde hay que ponerse creativos.

Si creamos métodos públicos para inicializar las variables, entonces ¿podemos hacer más cosas dentro de los métodos?, es decir, meter más instrucciones, la respuesta es si, lo que significa que podemos analizar los valores recibidos y determinar si los asignamos o no a las  variables.

Por ejemplo:

Evitar que intenten guardar un texto como teléfono:

public void setTelefono(String _telefono) {
    if (_telefono.matches("\\d+")) {
        // Si _teléfono es numérico, lo asignamos
        this.telefono = _telefono;
    } else {
        // Si _teléfono no es numérico ponemos solo un guion
        this.telefono = "-";
    }
}


Verificar que el correo ingresado tenga estructura de correo:

public void setCorreo(String _correo) {
    if (_correo.matches(".+\\@.+")) {
        // Si _correo tiene texto + @ + texto, lo asignamos
        this.correo = _correo;
    } else {
        // Si no, solo asignamos un guión
        this.correo = "-";
    }
}


Evitar que el usuario ponga un sueldo negativo:

public void setSueldo(double _sueldo) {
    if (_sueldo > 0) {
        // Si sueldo es mayor que 0, se asigna
        this.sueldo = _sueldo;
    } else {
        // Si no, solo se pone 0.0
        this.sueldo = 0.0;
    }
}



Esto solo es un ejemplo de cómo funciona la encapsulación y el acceso por métodos públicos, la solución dependerá de la problemática y del cómo quiera manejarse.

viernes, 27 de febrero de 2026

■ StringBuilder la alternativa a String

El problema de String

En Java, las variables de tipo String son como una foto, una vez que la tomas no la puedes cambiar, por ejemplo si hacemos esto:

String frase = "Hola";
frase = frase + " mundo"; //o frase += "mundo";


Parece que modificamos "frase", pero en realidad Java hizo esto:
1. Creó el objeto "Hola".
2. Creó el objeto "mundo".
3. Creó un tercer objeto "Hola mundo".

¿Y los primeros dos?, quedan huérfanos en la memoria y eventualmente son eliminados por el garbage collector, es decir, existen pero ya no se usan, por lo tanto "frase" no cambia  solo es sustituida por un nuevo objeto.

Ahora imagina si hacemos esto en un ciclo que se repita muchas veces, lo único que conseguiríamos es saturar la memoria RAM. Entonces ¿cúal es la solución?, aquí es donde entra StringBuilder.


■ StringBuilder

StringBuilder es como escribir a lápiz, podemos anotar, añadir, borrar y volver a anotar, todo sobre el mismo medio, en este caso la variable si cambia y no se destruye cada vez.

¿Cómo funciona?, mantiene un espacio en memoria que puede cambiar, ya sea crecer o reducirse según se necesite, pero ¿cómo se logra esto?, usando los métodos propios de esta clase, estos son los siguientes.

Primero creamos el objeto, a diferencia de la variable String que se crea así:
String texto = "";

El objeto StringBuilder debe crearse como tal, es decir:
StringBuilder sb = new StringBuilder();

Donde "sb" es el nombre que queramos darle.

Una vez creado ya podemos trabajar con él y esto se hace de la siguiente forma:

■ Métodos

append(texto): Inserta un texto al final.

Este método nos sirve para inicializar nuestro objeto o bien para anexarle más texto al final del mismo.

Inicializar:
sb.append("Haz el bien");
System.out.println(sb); // Imprimirá "Haz el bien"


Insertar texto al final:
sb.append(" sin mirar a quién");
System.out.println(sb); // Imprimirá "Haz el bien sin mirar a quién"


Nótese que al anexar la segunda parte la iniciamos con un espacio en blanco, esto para que el texto no quede pegado, de lo contrario diría:
"Haz el biensin mirar a quién"

insert(pos, texto): Inserta texto en determinada posición.
Si queremos insertar al inicio, indicamos en la posición 0:
sb.insert(0, "Hey tú! ");
System.out.println(sb); // Imprime "Hey tú! Haz el bien sin mirar a quién
"

Si queremos insertar algo a media frase, calculamos la posición y la indicamos:
sb.insert(19, ","); // Insertamos una coma
System.out.println(sb); // Imprime: "Hey tú! Haz el bien, sin mirar a quién"

delete(inicio, fin): Elimina un rango del texto.
Recibe dos parámetros, el primero es la posición de la cadena donde inicia el texto a eliminar y el segundo es la posición donde termina, por ejemplo si queremos eliminar la primera parte de la frase:
sb.delete(0, 8);
System.out.println(sb); // Imprime: "Haz el bien, sin mirar a quién"

deleteCharAt(pos): Elimina el caracter de eterminada posición.
Indicamos la posición y elimina el caracter que ahí se encuentre:
sb.deleteCharAt(29);
System.out.println(sb); // Imprime: "Haz el bien, sin mirar a quié"


Es decir, se eliminó el último caracter, otra forma de hacerlo para no tener que contar cuántos caracteres son y saber cuál es el último, es pasándole length como posición menos 1, ya que devuelve la longitud de la cadena pero dado que los índices inician en 0 hay que restar 1:
sb.deleteCharAt(sb.length() - 1);
System.out.println(sb);
 // Imprime: "Haz el bien, sin mirar a quié"

replace(inicio, fin, texto): Remplaza un rango de texto con el proporcionado.

El primer parametro es la posición a partir de la cual queremos insertar el texto, el segundo parámetro es la longitud a partir de la posición inicial que elegimos que sustituirá el nuevo texto de la frase original.

Por ejemplo si queremos sustituir, de la frase anterior "quié" por "quiénes", lo haríamos así:
sb.replace(25, 29, "quiénes");
System.out.println(sb); // Imprime: "Haz el bien, sin mirar a quiénes"


Donde:
25 es la posición donde inicia la palabra "quié".
29 es la posición final de la frase.
Con esto decimos que se va sustituir el texto desde la posición 25 hasta la 29.

Por lo que si quisiéramos insertar un texto sin alterar la frase, le pasaríamos la misma posición inicial como final:
sb.replace(11, 11, " hoy");
System.out.println(sb); // Imprime "Haz el bien hoy, sin mirar a quiénes"
Al decirle que la posición inicial es la misma que la final, el texto nuevo se inserta ahí mismo y empuja al resto, por lo que la frase restante no se ve alterada.

reverse(): Invierte el texto.

Esta es muy simple, solo invierte los caracteres del texto, el primero será el último, el segundo el penúltimo, el tercero el antepenúltimo y así sucesivamente:
sb.reverse();
System.out.println(sb); // Imprime  "senéiuq a rarim nis ,yoh neib le zaH"


Este cambio no es solo visual sino permanente, por lo que si queremos devolver a la frase a su estado original haríamos nuevamente lo mismo:
sb.reverse();
System.out.println(sb); // Imprime "Haz el bien hoy, sin mirar a quiénes"

substring(inicio, fin): Devuelve el texto del rango indicado.

A diferencia de los métodos anteriores, este no modifica la frase, solo regresa parte de ella, por lo que si hacemos esto:
System.out.println(sb.substring(7, 11)); // Imprime "bien"
System.out.println(sb); // Imprime "Haz el bien hoy, sin mirar a quiénes"

La frase original sigue intacta.

charAt(pos): Obtiene el caracter de la posición indicada.

Igual que el caso anterior (substring), este método no cambia la frase, solo obtiene el caracter de la posición que le indiquemos:
System.out.println(sb.charAt(7)); // Imprime "b"
System.out.println(sb); // Imprime "Haz el bien hoy, sin mirar a quiénes"

La frase original sigue intacta.

length(): Devuelve la longitud del texto.
Simplemente devuelve un entero equivalente a la longitud de la cadena de texto que almacena el objeto:
System.out.println(sb); // Imprime 36

setLength(tamaño): Corta o expande el tamaño del texto.
Acorta el texto hasta el tamaño indicado, es decir, si le pasamos un 11, entonces esta será la nueva longitud del texto y se eliminará el resto:
sb.setLength(11);
System.out.println(sb); // Imprime: "Haz el bien"

toString(): Convierte el objeto a tipo String.
String texto = sb.toString(); // Asignar el objeto a una variable String
System.out.println(sb); // Imprime "Haz el bien"
System.out.println(texto); // Imprime "Haz el bien"

capacity(): Devuelve el espacio reservado.
System.out.println(sb.capacity()); // Imprime (en mi caso) 70


Sabiendo esto y dependiendo de lo que necesites hacer, ahora podrás elegir entre String o StringBuilder para trabajar con cadenas de texto, ninguno es mejor que el otro, simplemente se comportan diferente y uno se adapta mejor que el otro en diferentes escenarios.

jueves, 7 de noviembre de 2024

Java 032 ► Contar las vocales y consonantes de un String

Ejercicio 032: Contar cuántas vocales y consonantes tiene un String. (No se consideran vocales acentuadas ni caracteres que no sean letras ni la "ñ").

Entrada: "Una frase cualquiera"

Salida: Vocales: 10
           Consonantes: 8

import java.util.*;

public class Java_032 {
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        String frase = "";
        int vocal = 0, consonante = 0;
      
        System.out.println("Ingresa la frase");
        frase = sc.nextLine();
        
        frase = frase.toLowerCase();
        
        for (int i=0; i < frase.length(); i++) {
            if ((frase.charAt(i) == 'a') ||
                (frase.charAt(i) == 'e') ||
                (frase.charAt(i) == 'i') ||
                (frase.charAt(i) == 'o') ||
                (frase.charAt(i) == 'u')) {
                vocal++;
            } else if (frase.charAt(i) != ' ') {
                consonante++;
            }
        }
        
        System.out.println();
        System.out.println("Vocales:\t " + vocal);
        System.out.println("Consonantes: " + consonante);
    }
}


Explicación:

Dado que no estamos considerando caracteres especiales como vocales acentuadas o símbolos suponemos que el String de entrada solo contendrá letras, lo primero que hacemos es convertir todo el String a minúsculas (con el método toLowerCase) para que al revisarlo letra por letra no tengamos que evaluar si es minúscula o mayúscula.

Luego recorremos el String como si de un arreglo se tratara con un for y por cada uno de sus caracteres preguntamos si es igual a las letras "a", "e", "i", "o" o "u", en cualquiera de esos casos contabilizamos una vocal.

Si la posición actual del String no es ninguna de las anteriores y sabiendo que el String solo contiene vocales y consonantes, sabemos que si no es vocal y omitiendo los espacios significa que es consonante y por lo tanto en el else se contabilizan las consonantes. Al final solo se muestran los resultados.

lunes, 30 de septiembre de 2024

■ Java ► BorderLayout

BorderLayout

Es un mecanismo de Java para organizar los elementos que queremos mostrar en la pantalla, ya sea en una ventana o en un panel.

Un panel es un área determinada que puede contener elementos, dependiendo del layout utilizado el panel puede tener un tamaño en pixeles o ajustarse a la ventana, en el caso de borderLayout si los elementos que se agregan son paneles estos se ajustarán de la siguiente forma:
  • El panel que quede al norte o al sur, solo se le podrá dar un alto en pixeles y el ancho será el que tenga la ventana.
  • Los paneles que queden al oeste y al este, solo se les podrá dar un ancho en pixeles y el alto será el sobrante que reste en la venta si tiene paneles al norte o sur con altura determinada.
  • El panel central será de las dimensiones sobrantes cuando hay paneles al norte o sur y al oeste y este.

Por ejemplo en la siguiente imagen:


Es una ventana conformada por 5 paneles, el gris al norte, el naranja al sur, el rojo al oeste, el azul al este y el blanco al centro.
  • El panel gris tiene una altura de 150 pixeles y su ancho por default es el mismo que la ventana.
  • El panel naranja tiene una altura de 150 pixeles y su ancho es el mismo que el de la ventana.
  • Los paneles centrales tienen una altura por default que es el sobrante que dejan los paneles norte (gris) y sur (naranja) respecto al alto de la ventana.
  • Los paneles rojo y azul tienen un ancho de 200 pixeles.
  • Y el panel blanco tiene un ancho y alto por default que viene siendo el espacio sobrante que dejan los demás paneles.


■ Estructura

Para poder organizar los elementos en la pantalla mediante borderLayout primero se le debe indicar a la ventana o al panel contenedor que el mecanismo de organización sera borderLayout, esto es de la siguiente forma:

<ventana o panel>.setLayout(new BorderLayout);

Hecho esto solo basta indicar en qué punto se situará el elemento al agregarlo a dicho contenedor, ya sea la propia ventana o un panel, esto es así:

<ventana o panel>.add(elemento, BorderLayout.<ZONA>);

Donde:
"elemento" es el componente que se va agregar.
"ZONA" es el punto de borderLayout en el que se situará dicho elemento, los puntos pueden ser: NORTH, CENTER, SOUTH, WEST o EAST.


■ Código

El código de la imagen superior es el siguiente:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class Panel extends JPanel {
    public Panel(Color _color) {
        setBackground(_color);
        setPreferredSize(new Dimension(200, 150));
    }
}

class Ventana extends JFrame {
    // Se crean los paneles a partir de la clase Panel
    Panel panelNorte = new Panel(Color.GRAY);
    Panel panelOeste = new Panel(Color.RED);
    Panel panelCentro = new Panel(Color.WHITE);
    Panel panelEste = new Panel(Color.BLUE);
    Panel panelSur = new Panel(Color.ORANGE);
    public Ventana() {
        this.setSize(800, 600);
        this.setTitle("Ventana en Java");
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Se le indica a la ventana que se usará BorderLayout
        this.setLayout(new BorderLayout());
    
        // Se agregan los paneles en cada una de las zonas
        this.add(panelNorte, BorderLayout.NORTH);
        this.add(panelOeste, BorderLayout.WEST);
        this.add(panelCentro, BorderLayout.CENTER);
        this.add(panelEste, BorderLayout.EAST);
        this.add(panelSur, BorderLayout.SOUTH);
    }
}

public class Main {
    public static void main (String args[]) {
        // Se crea la ventana
        Ventana ventana = new Ventana();
        // Se muestra en pantalla
        ventana.setVisible(true);
    }
}


■ Explicación

La clase "Panel" hereda de JPanel y que hace es crear un panel con el color y dimensiones dadas, cada que se crea un objeto de esta clase se crea un panel diferente.

La clase "Ventana" hereda de JFrame y es la encargada de crear la ventana con los parámetros dados (dimensiones, título, etc.); en la misma clase se crean los paneles mostrados usando la clase "Panel".

En el método constructor de ventana se agregan los paneles uno por uno indicando la zona en la que se localizarán.

En la clase principal se crea un objeto de "Ventana" y se muestra.

domingo, 29 de septiembre de 2024

■ Java ► Elementos y Áreas (Layouts)

Cuando se trabaja con ventanas en Java dependiendo de lo que haga nuestro programa es necesario colocar elementos con los que interactuará el usuario, estos pueden ser botones, etiquetas, campos de texto, etc., y para acomodar estos elementos se usan los "layouts".


■ Layouts

Los layouts son formas de posicionar elementos en una ventana o panel, los paneles o la propia ventana son recipientes que pueden contener cualquier elemento que queramos mostrar en pantalla, incluso otro panel, es decir, un panel puede contener elementos y a su vez ese panel puede estar contenido en otro panel.

En la siguiente imagen se observan 2 paneles (rectángulos grises) contenidos en la ventana, uno tiene un texto que dice "NORTH" y el otro dice "SOUTH":


Una forma de acomodar elementos en la ventana es usando la característica "borderLayout" la cual permite acomodar elementos en la ventana o en un panel mediante tipo "puntos cardinales", donde "NORTH" es la parte superior, "SOUTH" es la parte inferior, "WEST" es la parte izquierda, "EAST" es la parte derecha y "CENTER" el centro de la ventana, en la imagen de arriba los paneles grises están situados en los puntos NORTH y SOUTH de la ventana.

Si agregáramos un tercer panel al centro, es decir, en la posición "CENTER", la ventana luciría así:





■ Tipos de Layouts

• BorderLayout

Una de las formas que nos permite acomodar elementos en la pantalla es borderLayout, como ya se mencionó en el ejemplo de arriba la forma de distribuir los elementos en la pantalla mediante borderLayout es indicando la posición del elemento de acuerdo a un punto en la pantalla, estos puntos solo pueden ser NORTH, SOUTH, WEST, EAST o CENTER, siendo la distribución de la siguiente forma:


Y a su vez dentro de cada panel, al agregar elementos se pueden distribuir de la misma manera, es decir, cada panel tiene un norte, sur, oeste, este y centro.


• BoxLayout

Este layout nos permite acomodar los elementos en forma lineal ya sea sobre el eje X o sobre el eje Y, es decir de forma vertical uno bajo el otro o de forma horizontal uno al lado del otro como se aprecia a continuación:

Eje X




Eje Y




• FlowLayout

Este tipo de layout lo que hace es colocar los elementos seguidos uno de otro de izquierda a derecha y el cómo se vean los elementos dependerá de su tamaño y de la distribución elegida, en este caso solo tiene tres opciones: CENTER, LEFT y RIGHT.

Por ejemplo si elegimos CENTER y los elementos son pequeños y caben varios a lo ancho de la ventana se centrarán así:




Y si los elementos miden de ancho lo mismo que la ventana entonces se acomodarán uno abajo del otro así:


Si elegimos LEFT y los elementos son pequeños se alinearán a la izquierda así:


Finalmente si elegimos RIGHT se alinearán a la derecha:




• GridLayout

Este tipo de layout sirve para acomodar los elementos como si se tratara de una tabla, es decir, en filas y columnas, al crearlo se le pasan dos parámetros, el primero es el número de filas y el segundo son las columnas, por ejemplo si le dijéramos que queremos los elementos en 3 filas por 2 columnas, se verían así:


Y si fuera al revés, 2 filas por 3 columnas, los elementos quedarían así:




■ ¿Cuál elegir?

Eso dependerá de cómo queramos distribuír los elementos en pantalla, dependiendo de nuestra necesidad uno podría ser mejor que otro, también está la posibilidad de combinarlos, por ejemplo crear 3 paneles al norte, centro y sur con borderLayout y los elementos de cada uno de esos paneles distribuirlos horizontalmente sobre X con boxLayout.

■ Java ► Crear una ventana a partir de una clase secundaria

Usar la clase principal para crear la ventana no es la mejor forma de hacerlo, lo ideal es crear una clase especial para nuestra ventana y en la clase principal simplemente instanciarla.

Esto es similar a lo que se hizo en el ejemplo anterior (crear ventana en la clase principal), pero en lugar de que la clase principal herede de JFrame lo hará una clase secundaria, que será la clase que contenga ventana, esto es así:

class Window extends JFrame {
    public Window() {
        // propiedades de la ventana
    }
}

public class Main {
    public static void main (String args[]) {
        // Aquí solo se crea el objeto de la clase
        // Y se le indica que muestre la ventana
    }
}

La clase "Window" es la clase en la que se programará la ventana, la clase "Main" es la clase principal donde se creará un objeto de la clase secundaria "Window", ese objeto vendrá siento la ventana en sí y solo deberá mandarse a mostrar para hacer visible la ventana cuando el programa se ejecute.


■ Código

// Se importa la librería
import javax.swing.*;

// Clase secundaria en la que se programa la ventana
class Window extends JFrame {
    public Window() {
        // Tamaño de la ventana
        this.setSize(500, 500);

        // Título de la ventana
        this.setTitle("Ventana en Java");

        // Posición inicial de la ventana
        this.setLocationRelativeTo(null);

        // Bloquea el redimensionado
        this.setResizable(false);

        // Indica que al cerrar la ventana se cierre el programa
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

// Clase principal
public class Main {
    public static void main (String args[]) {
        // Objeto de la clase Window
        Window ventana = new Window();

        // Se muestra la ventana
        ventana.setVisible(true);
    }
}


Al ejecutar el código el resultado será una ventana como esta:




■ Múltiples ventanas

Dado que la ventana se genera en la clase "Window" si la instanciamos varias veces podemos crear múltiples ventanas, tantas como queramos, esto es tan simple como crear más objetos de la misma clase:

Window ventana1 = new Window();
Window ventana2 = new Window();
Window ventana3 = new Window();

ventana1.setVisible(true);
ventana2.setVisible(true);
ventana3.setVisible(true);

sábado, 28 de septiembre de 2024

■ Java ► Crear una ventana vacía en la propia clase main

En Java, así como creamos programas que se ejecutan a base de texto (línea de comandos) también podemos crear programas mediante ventanas, es decir, mediante interfaces gráficas de usuario (GUI).

Para ello Java nos proporciona el componente JFrame de la librería swing por lo que para crear una ventana debemos importarlo a nuestro programa, esto de la siguiente forma:
import javax.swing.JFrame;

Una vez importado debemos hacer que la clase donde vamos a crear la ventana herede de él, en este caso como no vamos a usar clases secundarias será la clase principal la que herede del componente JFrame.

Pero, ¿Cómo hacemos que la clase herede de este componente?

Así como se vio en el apartado de "herencia", donde se muestra cómo podemos tener varias clases y heredar de ellas con la palabra reservada "extends", también se puede heredar de las clases propias de Java de la misma forma, en este caso de JFrame agregando a nuestra clase las palabras reservadas "extends JFrame".


■ Crear la ventana

Para poder crear una ventana en Java debemos crear una instancia de JFrame, este objeto será en sí nuestra ventana, pero eso no es todo, también debemos decirle de qué tamaño será, qué título tendrá, en qué parte de la pantalla se posicionará, si el usuario podrá cambiarla de tamaño o no, si será visible, entre otra cosas.

Todas estas características son métodos que JFrame ya tiene implícitos, únicamente debemos configurarlos de acuerdo a como queramos nuestra ventana, a continuación se muestra el código para crear una ventana vacía en Java, más adelante en otros apartados se verá cómo agregarle elementos.


■ Código

// Se importa la librería
import javax.swing.JFrame;

public class Ventana extends JFrame { // Se hereda de JFrame
    // Método principal
    public static void main (String args[]) {

        // Se crea una instancia de JFrame
        JFrame Ventana = new JFrame();

        // Título de la ventana
        Ventana.setTitle("Ventana en Java");

        // Se indica el tamaño de la ventana en pixeles
        Ventana.setSize(500, 500);

        // Se posiciona la ventana al centro de la pantalla
        Ventana.setLocationRelativeTo(null);

        // Se bloquea el redimensionado de la ventana
        Ventana.setResizable(false);

        // Se indica que la ventana será visible
        Ventana.setVisible(true);

        // Indica que al cerrar la ventana se cierra el programa
        Ventana.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}


Al ejecutar el código anterior deberá verse una ventana como la siguiente:



Para cambiar el título de la ventana o el tamaño solo se deben modificar los valores entre paréntesis de las propiedades: setTitle y setSize.

Como se mencionó al principio, de esta forma se crea la ventana en la clase main, pero esta no es la mejor forma de hacerlo, también podemos crear la ventana en una clase secundaria y en main solo invocarla, de esa forma podríamos invocar tantas ventanas como queramos.

jueves, 26 de septiembre de 2024

■ Lógica en los métodos de Math de Java

Los métodos de la clase Math de Java son funciones preprogramadas que nos ayudan a realizar determinadas operaciones de una forma más sencilla, por ejemplo si queremos el cuadrado de un número utilizaríamos el método "pow", el cual recibe dos parámetros, el primero es el número que queremos elevar a cierta potencia y el segundo es precisamente la potencia, en este caso para el cuadrado la potencia es 2, esto es así:

int num = 3;
int cuadrado = Math.pow(num, 2);

La variable "cuadrado" valdrá 9 ya que internamente Math.pow toma el número que le pasamos (num), lo eleva a la potencia que le indicamos (2) y nos devuelve el resultado.


■ Poniendo a prueba nuestra lógica

Pero ¿si quisieramos hacerlo manualmente?, tendríamos que pensar en cómo realizaríamos las operaciones para conseguir el mismo resultado, hacer esto supondría un buen ejercicio pues nos obliga a comprender qué es lo que está sucediendo y pensar de qué forma podemos llegar a dicho resultado, veamos algunos casos.


• Math.abs(double)

"abs" es una función de Math que devuelve el valor absoluto de un número, pero, ¿qué es el valor absoluto?, el valor absoluto de un número es la distancia que hay entre el número y el cero sin importar si el número es positivo o negativo, por ejemplo los números -6 y 5, veamos la siguiente recta numérica:


La distancia entre -6 y 0 es 6 ya que hay 6 unidades entre -6 y 0.
La distancia entre 5 y 0 es 5 ya que hay 5 unidades entre 5 y 0.

Podríamos decir que el valor absoluto de un número es el propio número pero siempre positivo y eso es precisamente lo que devuelve Math.abs(número).

int absoluto;
absoluto = Math.abs(-13); // Devolverá 13
absoluto = Math.abs(10);  // Devolverá 10

Si quisiéramos hacer una función propia que devuelva el valor absoluto de un número obviamente sin usar Math.abs, ¿cómo lo haríamos?.

Analizando el problema

Si el valor absoluto de un número es el mismo número pero positivo significa que:
 - El valor absoluto de un número positivo es el propio número.
 - El valor absoluto de un número negativo es el mismo número pero con el signo contrario, es decir, de negativo lo pasamos a positivo.

Entonces ¿cómo programaríamos eso?

Como siempre, en programación hay muchas soluciones y una de ellas podría ser:
1) Preguntar si el número es positivo o negativo.
2) Si el número es positivo, su valor absoluto es el mismo número.
3) Si el número es negativo lo multiplicaríamos por -1 para convertirlo a positivo y ese sería su valor absoluto.

Programando lo anterior:

public static double Absoluto(double numero) {
    double valor_absoluto;

    if (numero > -1) {
        valor_absoluto = numero;
    } else { // Si el número es negativo
        valor_absoluto = numero * -1; // Se convierte a positivo
    }

    return valor_absoluto;
}

Listo, ya tenemos una función propia que emula a Math.abs, recibe un número entero o decimal y regresa su valor absoluto.

int absoluto;
absoluto = Absoluto(-3); // Devuelve 3.0
absoluto = Math.abs(-3); // Devuelve 3

Ambas devuelven 3.


• Math.round(double)

Esta función lo que hace es redondear un número a su valor más cercano ya sea superior o inferior, por ejemplo si le pasamos 4.4 lo redondea a 4 y si le pasamos 4.6 lo redondea a 5, esto funciona como se muestra en la imagen de abajo:


Si nosotros quisiéramos hacer una función propia que haga lo mismo ¿cómo sería?

Analizando el problema

Si la función "round" redondea dependiendo de los decimales de esta forma:
Para los números positivos:
 - Si los decimales del número terminan en .49 o menor, es decir, no llega a .5 lo redondea hacia el siguiente entero inferior.
 - Si los decimales del número terminan en .5 o superior lo redondea hacia el siguiente entero superior.
Para los números negativos:
 - Si los decimales del número son menores que 0.5, el número se redondea hacia sí mismo, es decir, si el número es -3.4, redondeado sería -3.
 - Si los decimales son 0.5 o superior, el número se redondea al siguiente entero, por ejemplo si el número es -3.5, redondeado sería -4.


Partiendo de lo anterior, ¿cómo lo haríamos?

Una forma podría ser la siguiente:
1) Preguntar si el número es positivo o negativo.
2) Si es negativo:
 - Primero obtenemos el residuo del número pero tratándolo como si fuera positivo.
 - Luego evaluamos el residuo, si es menor a 0.5, entonces el número redondeado sería el propio número convertido a entero.
 - Si el residuo es 0.5 o mayor, convertimos el número a entero y le restamos 1.
3) Si es positivo:
 - Primero obtenemos directamente su residuo.
 - Luego si el residuo es menor a 0.5, el número redondeado sería el propio número convertido a entero.
 - Si el residuo es 0.5 o mayor, el número redondeado es el mismo número convertido a entero y sumándole 1.

Con "convertir a entero" entiéndase que el número decimal al ser pasado a entero pierde sus decimales, es decir, si el número es 2.5 y se convierte a entero queda solo el 2, si el número es -3.3 y se convierte a entero queda solo el -3, es decir, al convertir a entero solo queda la parte entera del número.

¿Cómo hacemos esto en Java?
Simplemente anteponemos (int) al número:
double n1 = 3.3;
int n2 = (int)n1;
System.out.println(n2); // Mostrará 3

A esto se le llama "cast" o "castear" el número, como si le dijéramos al número "sé que eres un double pero te vas a comportar como entero".


Programando lo anterior:

public static int redondear(double numero) {
int redondeado;
double residuo;

if (numero < 0) {
residuo = (numero * -1) % 1;
if (residuo < 0.5) {
redondeado = (int)numero;
} else {
redondeado = (int)numero - 1;
}
} else {
residuo = numero % 1;
if (residuo < 0.5) {
redondeado = (int)numero;
} else {
redondeado = (int)numero + 1;
}
}

return redondeado;
}

Hecho esto ya tenemos una función propia que se comporta igual que Math.round donde ambas funcionan de la siguiente forma:

double redondeado;
redondeado = redondear(4.2);  // Devuelve 4
redondeado = Math.round(4.2); // Devuelve 4
redondeado = redondear(4.6);  // Devuelve 5
redondeado = Math.round(4.6); // Devuelve 5
redondeado = redondear(-4.6);  // Devuelve -5
redondeado = Math.round(-4.6); // Devuelve -5


• Math.max(int/double, int/double)

Esta función es muy simple, recibe dos números y devuelve el mayor, y si son iguales solo devuelve uno de ellos.

Analizando el problema

Creo que en este caso no hay mucho que pensar, la cosa es así:
1) Se reciben dos números y si son iguales se deveuelve uno de ellos.
2) Si son diferentes se pregunta cuál es el mayor y ese es el que se devuelve.

Programando lo anterior:

public static double Mayor(double n1, double n2) {
    double mayor;
        
    if (n1 == n2) {
        mayor = n1;
    } else if (n1 > n2) {
        mayor = n1;   
    } else {
        mayor = n2;
    }

    return mayor;
}

Listo, tenemos una función que nos regresa el mayor de dos números, sean enteros o decimales.

int nMayor;
nMayor = Mayor(4, 7);    // Devolverá 7.0
nMayor = Math.max(4, 7); // Devolverá 7

Ambos devolverán 7.


• Math.pow(int/double, int/double)

Esta es una función que recibe dos números y lo que hace es elevar el primero a la potencia que indique segundo. Como ya se había mencionado al inicio, si quisiera elevar 3 al cuadrado usando Math.pow(base, potencia) le pasaría los números 3 y 2, donde 3 es el número a elevar (base) y 2 representa al cuadrado (potencia).

Analizando el problema

Sabemos que si se quiere elevar un número m a una potencia n una forma es multiplicar n-1 veces el número m por sí mismo, por ejemplo:

Si m = 2 y n = 2 la multiplicación sería → m * m
Si m = 2 y n = 3 entonces sería → m * m * m

Pero, ¿qué pasa si la potencia es negativa?

Por ejemplo 2⁻² es 0.25.
¿Cómo obtenemos eso?, la respuesta es dividiendo el número entre sí mismo n+1 veces (condierando a n como positivo), esto es:
Si m = 2 y n = -2, entonces la división sería → m / m / m / m
Si m = 2 y n = -3, entonces sería → m / m / m / m / m


Considerando esto, ¿cómo lo haríamos?

Algo así:
1) Primero preguntamos si el número base es mayor que 0.
2) Preguntamos si la potencia es igual a 0, si es así regresamos 1.
3) Si la potencia no es 0 preguntamos si es mayor a 0.
4) Si la potencia es mayor a 0 entonces se multiplica el número por si mismo el mismo número de veces que valga la potencia - 1, ejemplo si la potencia es 3 se multiplica dos veces, n * n * n.
5) Si la potencia es menor que 0, en lugar de multiplicar se divide el número entre sí mismo el mismo número de veces que vale la potencia convertida a positiva + 1, ejemplo si la potencia es -3 se dividiría 4 veces, n / n / n / n / n.

Programando lo anterior:

public static double Potencia(double base, double potencia) {
    double resultado = 0;
        
    if (base > 0) {
        if (potencia == 0) {
            resultado = 1;
        } else if (potencia > 0) {
            resultado = base;
            for (int i=1; i < potencia; i++) {
                resultado = resultado * base; 
            }
        } else {
            resultado = base;
            for (int i=0; i < ((potencia * -1) + 1); i++) {
                resultado = resultado / base;
            }
        }
    }

    return resultado;
}

Así tenemos una función propia que nos regresa un número elevado a la potencia que le indiquemos, igual que Math.pow().

int potencia;
potencia = Potencia(2, 3);  // Devolverá 8.0
potencia = Math.pow(2, 3);  // Devolverá 8
potencia = Potencia(2, -3); // Devolverá 0.125
potencia = Math.pow(2, -3); // Devolverá 0.125

Ambos regresan 8.


■ Conclusión

Las funciones preprogramadas del lenguaje nos ayudan a resolver situaciones comunes como lo es elevar un número al cuadrado, obtener su raíz, redondearlo, etc., haciendo uso de ellas nos ahorramos tener que codificarlas nosotros mismos sin embargo cuando se está aprendiendo intentar emularlas puede ser un buen ejercicio de lógica ya que debemos pensar en una solución que nos lleve al mismo resultado.