01
Objetos y clases

De C a Java · Objetos como estado y comportamiento

Objetos y clases

Semana 1 — De C a Java · Objetos como estado y comportamiento

Objetivos

  • Conocer cómo funciona el curso, el proyecto Bodega y el entorno de trabajo.
  • Explicar qué cambia al pasar de C a Java: bytecode, JVM y JIT; tipos primitivos y referencias; String y arreglos; garbage collector.
  • Compilar y ejecutar programas con javac, java y jshell, incluidos los archivos fuente compactos de Java 25.
  • Explicar por qué conviene juntar estado y comportamiento en objetos, y escribir una primera clase con campos, constructor y métodos.
  • Dejar listo el entorno de trabajo para todo el semestre.

Apuntes de la semana: notes/week-01.md · Código: code/week-01

Sesión A — Presentación del curso · De C a Java

Lecturas (Eck, Introduction to Programming Using Java, versión 9):

El libro cubre Java 17 y usa su propia clase TextIO para la entrada y la salida; nosotros usamos IO y archivos fuente compactos, que son de Java 25. Para eso:

Temas:

  • Programa, proyecto Bodega (P0–P5), exámenes, políticas y entorno (SYLLABUS).
  • Compilar a bytecode vs. a código máquina. La JVM: intérprete y compilador JIT. “Escribir una vez, ejecutar en cualquier parte”.
  • Las herramientas del JDK: javac, java (también con un archivo fuente directamente), jshell, javap.
  • Archivos fuente compactos: void main() e IO.println.
  • Tipos primitivos de tamaño fijo; boolean no es un número. Tipos de referencia: String (inmutable, con métodos) y arreglos (con length y verificación de índices).
  • Sin punteros ni free: new y el garbage collector.

Sesión B — Objetos como estado + comportamiento

Lecturas:

Temas:

  • El problema de los datos sueltos: un struct y funciones aparte; nada protege las reglas de los datos.
  • Objeto = estado (campos) + comportamiento (métodos). Mensaje y método.
  • Clase e instancia. Una primera clase: TransitCard, con campos, constructor y métodos.
  • new paso a paso: reservar, construir, devolver la referencia.
  • Cada objeto tiene su propio estado y su propia identidad (== entre referencias).

Laboratorio — Entorno de trabajo

Guía: labs/README.md

  1. Instalar Docker y levantar el contenedor del curso (env/run.sh). Comprobar la versión con java -version.
  2. jshell: evaluar expresiones con enteros, double, String y arreglos; provocar un ArrayIndexOutOfBoundsException y leer el mensaje.
  3. Escribir un archivo compacto propio y ejecutarlo de las dos maneras: java Archivo.java, y javac -d build seguido de java -cp build.
  4. En code/week-01: make, make test y make clean. Leer el Makefile (compila el código Java con javac y la versión en C de la tarjeta con gcc) y la salida de JUnit.
  5. Leer el enunciado de P0 y preparar su carpeta de trabajo dentro del contenedor.

Entregas

  • P0 publicado: Calentamiento: programas cortos en Java y la clase Product (producto) como primera clase del proyecto Bodega (proyectos). Entrega en semana 3.

Preguntas de repaso

  1. ¿Qué hay dentro de un archivo .class? ¿Por qué el mismo .class corre en Linux, Windows y macOS, y un ejecutable de C no?
  2. Si la JVM interpreta bytecode, ¿por qué un programa Java que corre varios segundos puede ser casi tan rápido como uno en C? ¿Qué significa mixed mode en java -version?
  3. ¿Qué guarda una variable int y qué guarda una variable String? ¿En qué se parece una referencia a un puntero de C y en qué no?
  4. En C, if (n) compila con n entero. ¿Qué pasa en Java y por qué el lenguaje lo decidió así?
  5. ¿Qué tres cosas hace new TransitCard("A-001"), y en qué orden?
  6. Dos tarjetas creadas con new tienen el mismo código y el mismo saldo. ¿Es a == b verdadero o falso? ¿Por qué?
  7. Den un ejemplo, distinto de la tarjeta, en el que tener los datos sueltos y las funciones aparte haga difícil encontrar un error.

Código de la semana

Compilar y probar dentro del contenedor del curso con make y make test.

Ejemplos (6)Pruebas (1)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

CardDemo.javaDescargar CardDemo.java
// Usa la clase TransitCard: crear objetos con new y enviarles mensajes.
void main() {
    TransitCard alice = new TransitCard("A-001");
    TransitCard bob = new TransitCard("A-002");

    alice.recharge(1000);
    boolean paid = alice.pay(250);
    IO.println(alice.id() + ": pagó " + paid + ", saldo " + alice.balance());

    // bob tiene su propio estado: el pasaje de alice no lo toca.
    paid = bob.pay(250);
    IO.println(bob.id() + ": pagó " + paid + ", saldo " + bob.balance());

    // Identidad: dos new crean dos objetos distintos.
    TransitCard same = alice;
    IO.println("alice == bob: " + (alice == bob));
    IO.println("alice == same: " + (alice == same));
}
FromC.javaDescargar FromC.java
// Lo que cambia al pasar de C a Java: tipos con tamaño fijo, String como
// objeto y arreglos que conocen su longitud.
void main() {
    // Tipos primitivos: el tamaño no depende de la máquina.
    int n = 2_147_483_647;          // int siempre tiene 32 bits
    long big = n + 1L;              // long siempre tiene 64 bits
    IO.println("n + 1 como int:  " + (n + 1));
    IO.println("n + 1 como long: " + big);

    // boolean no es un número: if (1) no compila en Java.
    boolean ready = n > 0;
    IO.println("ready = " + ready);

    // String es un objeto con métodos, no un char* terminado en '\0'.
    String name = "Ada Lovelace";
    IO.println(name.length() + " caracteres, en mayúsculas: " + name.toUpperCase());

    // Un arreglo es un objeto que sabe cuántos elementos tiene.
    int[] marks = new int[4];       // los elementos empiezan en 0
    marks[0] = 15;
    marks[3] = 18;
    int sum = 0;
    for (int i = 0; i < marks.length; i++) {
        sum += marks[i];
    }
    IO.println("marks.length = " + marks.length + ", suma = " + sum);
}
Hello.javaDescargar Hello.java
// El programa más corto en Java 25: un archivo fuente compacto.
// Se ejecuta directamente con: java Hello.java
void main() {
    IO.println("Hola, Java");
}
StructStyle.javaDescargar StructStyle.java
// La tarjeta de transporte escrita "al estilo C": un registro de datos
// sueltos y funciones aparte que reciben ese registro como parámetro.
class CardData {
    String id;
    int balance;                    // saldo en céntimos
}

void recharge(CardData card, int amount) {
    card.balance += amount;
}

boolean pay(CardData card, int fare) {
    if (card.balance < fare) {
        return false;
    }
    card.balance -= fare;
    return true;
}

void main() {
    CardData card = new CardData();
    card.id = "A-001";
    recharge(card, 1000);
    pay(card, 250);
    IO.println(card.id + ": " + card.balance + " céntimos");

    // Nada impide que cualquier parte del programa rompa los datos.
    card.balance = -500;
    IO.println(card.id + ": " + card.balance + " céntimos");
}
transit_card.cDescargar transit_card.c
/*
 * La tarjeta de transporte en C: un struct con los datos y funciones aparte
 * que reciben un puntero al struct. Es el punto de partida para comparar con
 * la clase TransitCard.
 */
#include <stdio.h>

struct card {
    char id[8];
    int balance;            /* saldo en céntimos */
};

void card_recharge(struct card *c, int amount) {
    c->balance += amount;
}

int card_pay(struct card *c, int fare) {
    if (c->balance < fare)
        return 0;           /* no alcanza el saldo */
    c->balance -= fare;
    return 1;
}

int main(void) {
    struct card c = { "A-001", 0 };
    card_recharge(&c, 1000);
    card_pay(&c, 250);
    printf("%s: %d céntimos\n", c.id, c.balance);

    c.balance = -500;       /* nada lo impide */
    printf("%s: %d céntimos\n", c.id, c.balance);
    return 0;
}
TransitCard.javaDescargar TransitCard.java
/**
 * Una tarjeta de transporte: su estado (código y saldo) y las operaciones
 * que lo cambian viven juntos en una clase.
 */
public class TransitCard {
    private final String id;
    private int balance;            // saldo en céntimos

    /** Crea una tarjeta con el código dado y saldo cero. */
    public TransitCard(String id) {
        this.id = id;
        this.balance = 0;
    }

    /** Agrega saldo a la tarjeta. */
    public void recharge(int amount) {
        balance += amount;
    }

    /** Cobra un pasaje si alcanza el saldo; devuelve si se pudo cobrar. */
    public boolean pay(int fare) {
        if (balance < fare) {
            return false;
        }
        balance -= fare;
        return true;
    }

    public String id() {
        return id;
    }

    public int balance() {
        return balance;
    }
}

Pruebas

Comprueban lo que afirman los apuntes; se ejecutan con make test.

TransitCardTest.javaDescargar TransitCardTest.java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

// Prueba mínima de muestra: comprueba que el entorno corre JUnit.
// Los unit tests se ven en detalle en la semana 3.
class TransitCardTest {

    @Test
    void newCardStartsEmpty() {
        TransitCard card = new TransitCard("A-001");
        assertEquals(0, card.balance());
    }

    @Test
    void payWithEnoughBalance() {
        TransitCard card = new TransitCard("A-001");
        card.recharge(1000);
        assertTrue(card.pay(250));
        assertEquals(750, card.balance());
    }

    @Test
    void payWithoutBalanceChangesNothing() {
        TransitCard card = new TransitCard("A-001");
        assertFalse(card.pay(250));
        assertEquals(0, card.balance());
    }
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 1: de C a Java y una primera clase.
#   make         compila todo a build/ (Java con javac; la versión en C con gcc)
#   make test    corre las demostraciones y la prueba JUnit de muestra
#   make clean   borra build/
JAVAC  = javac
JFLAGS = -Xlint:all -Werror
SRC    = $(wildcard *.java)
DEMOS  = Hello FromC StructStyle CardDemo
CC     = gcc
CFLAGS = -Wall -Wextra -Werror -O1

all: build/.stamp build/transit_card

build/transit_card: transit_card.c
	mkdir -p build
	$(CC) $(CFLAGS) -o $@ $<

build/.stamp: $(SRC)
	mkdir -p build
	$(JAVAC) $(JFLAGS) -cp $(JUNIT_JAR) -d build $(SRC)
	touch $@

test: all
	@echo "== transit_card (C)"; ./build/transit_card
	@for d in $(DEMOS); do echo "== $$d"; java -cp build $$d || exit 1; done
	java -jar $(JUNIT_JAR) execute --class-path build --scan-class-path \
	  --disable-banner --disable-ansi-colors --details=tree

clean:
	rm -rf build

.PHONY: all test clean