Parcial de práctica — Programación Orientada a Objetos
Temas: partes 1 y 2 del curso (semanas 1 a 8) · Duración: 2 horas
Este es un examen de práctica con el formato del parcial. Sirve para medir
cuánto tiempo les toma cada tipo de pregunta: resuélvanlo en papel, con el
reloj corriendo, y recién después comprueben sus respuestas con java o
jshell.
Indicaciones
- En el código cuenta la idea, no los errores menores de sintaxis.
- En las preguntas de “qué imprime”, justifiquen cada respuesta: una salida correcta sin explicación no basta.
- Todo el código de los enunciados compila con
javac -Xlint:all(Java 25), salvo donde una pregunta propone cambiarlo.IO.printlnes el de Java 25. - Los montos van en céntimos, como enteros, igual que en el curso.
| Parte | Preguntas |
|---|---|
| A. Leer código | 1–4 |
| B. Escribir código | 5–6 |
| C. Diseño | 7–8 |
| D. Preguntas cortas | 9–12 |
Parte A — Leer código
Pregunta 1 — Referencias y arreglos de objetos
public class P1Cards {
static class TransitCard {
private final String id;
private int balance; // céntimos
TransitCard(String id) {
this.id = id;
}
void recharge(int amount) {
balance += amount;
}
String id() {
return id;
}
int balance() {
return balance;
}
}
public static void main(String[] args) {
TransitCard[] cards = { new TransitCard("A-001"), new TransitCard("A-002") };
TransitCard[] same = cards;
TransitCard[] copy = new TransitCard[2];
for (int i = 0; i < 2; i++) {
copy[i] = cards[i];
}
same[0].recharge(500);
copy[1].recharge(200);
copy[0] = new TransitCard("A-003");
copy[0].recharge(100);
for (TransitCard t : cards) {
t = new TransitCard("X");
t.recharge(999);
}
IO.println(cards[0].balance() + " " + cards[1].balance() + " " + copy[0].balance());
IO.println((cards == same) + " " + (cards == copy) + " " + (cards[1] == copy[1]));
int n = 1;
String code = "A-00" + n;
IO.println((code == cards[0].id()) + " " + code.equals(cards[0].id()));
}
}
a) ¿Qué imprime el programa? Justifiquen cada valor.
b) Dibujen el stack y el heap justo antes del primer
IO.println: las variables de main, los arreglos y las tarjetas, con sus
saldos. ¿Cuántos objetos TransitCard se crearon en total? ¿Cuántos siguen
siendo alcanzables desde main?
c) Expliquen el resultado de la última línea. ¿Con qué deberían
comparar dos códigos de tarjeta, y por qué el == “funciona” a veces?
Pregunta 2 — Overloading, override y dynamic dispatch
public class P2Dispatch {
static class Notification {
String channel() {
return "genérico";
}
long cost() {
return 0;
}
}
static class SmsNotification extends Notification {
@Override
String channel() {
return "sms";
}
@Override
long cost() {
return 5;
}
}
static class PushNotification extends Notification {
@Override
String channel() {
return "push";
}
}
static class Dispatcher {
String route(Notification n) {
return "general/" + n.channel();
}
String route(SmsNotification s) {
return "operadora/" + s.channel();
}
}
static class AuditDispatcher extends Dispatcher {
@Override
String route(Notification n) {
return "auditado+" + super.route(n);
}
}
public static void main(String[] args) {
Notification a = new SmsNotification();
SmsNotification b = new SmsNotification();
Notification c = new PushNotification();
Dispatcher d = new AuditDispatcher();
IO.println("1. " + d.route(a));
IO.println("2. " + d.route(b));
IO.println("3. " + d.route((Notification) b));
IO.println("4. " + new Dispatcher().route(c));
IO.println("5. " + d.route(c));
IO.println("6. " + (a.cost() + c.cost()));
}
}
a) ¿Qué imprime cada línea, de la 1 a la 6? Para cada una de las
líneas 1 a 5 digan qué decidió el compilador (qué firma de route) y qué
decidió la JVM al ejecutar (la versión de qué clase).
b) ¿Por qué la línea 2 no pasa por AuditDispatcher, si d
apunta a un AuditDispatcher? ¿Qué habría que agregar a AuditDispatcher
para que también audite esa llamada?
c) Se agrega, después de la declaración de d, la línea
SmsNotification s = a;. ¿Compila? Si en su lugar se agrega
SmsNotification s = (SmsNotification) c;, ¿compila? ¿Qué pasa al ejecutarla?
Pregunta 3 — Constructores en cadena
public class P3Constructors {
static class Product {
private static int created = 0;
private final String name;
private final long priceCents;
Product(String name, long priceCents) {
created++;
this.name = name;
this.priceCents = priceCents;
IO.println("Product(" + name + ", " + priceCents + ")");
}
Product(String name) {
this(name, 100);
IO.println("Product(" + name + ")");
}
static int created() {
return created;
}
}
static class PerishableProduct extends Product {
private final int days;
PerishableProduct(String name, long priceCents, int days) {
super(name, priceCents);
this.days = days;
IO.println("PerishableProduct(" + name + ", " + days + " días)");
}
PerishableProduct(String name) {
this(name, 250, 7);
IO.println("PerishableProduct(" + name + ")");
}
}
public static void main(String[] args) {
new PerishableProduct("leche");
IO.println("--");
new Product("arroz");
IO.println("--");
Product p = new PerishableProduct("yogur", 400, 10);
IO.println("creados: " + Product.created());
}
}
a) ¿Qué imprime el programa? Indiquen qué constructor llama a cuál.
b) En total se ejecutaron siete cuerpos de los constructores de
este programa. ¿Por qué created() devuelve 3?
c) Si se borra la línea super(name, priceCents); del primer
constructor de PerishableProduct, ¿compila? Expliquen qué agrega el
compilador en su lugar y por qué no le sirve.
Pregunta 4 — Leer un switch con patrones
Las tres formas de descuento de una bodega, como interfaz sealed:
public class P4Discounts {
sealed interface Discount permits Percentage, Fixed, None { }
record Percentage(int percent) implements Discount { }
record Fixed(long cents) implements Discount { }
record None() implements Discount { }
static long finalPrice(Discount d, long price) {
return switch (d) {
case Percentage(int p) when p >= 100 -> 0;
case Percentage(int p) -> price - price * p / 100;
case Fixed(long c) when c > price -> 0;
case Fixed(long c) -> price - c;
case None() -> price;
};
}
public static void main(String[] args) {
IO.println(finalPrice(new Percentage(20), 1000));
IO.println(finalPrice(new Fixed(1500), 1000));
IO.println(finalPrice(new Percentage(150), 1000));
IO.println(finalPrice(new Fixed(300), 1000));
IO.println(finalPrice(new None(), 1000));
}
}
a) ¿Qué imprime main?
b) Si se borra la línea case None() -> price;, ¿compila? ¿Qué
diría el compilador y por qué es una ayuda?
c) Si se intercambian las dos líneas de Percentage (la que no
tiene when queda primero), ¿compila? ¿Por qué?
d) ¿Qué pasa con finalPrice(null, 1000)?
Parte B — Escribir código
Pregunta 5 — Una clase con invariantes y sus pruebas
Escriban la clase WaterTank, un tanque de agua de una casa:
WaterTank(int capacity): la capacidad en litros. Tiene que ser positiva; si no, se lanzaIllegalArgumentException. El tanque empieza vacío.int fill(int liters): agrega agua. Si no cabe todo, llena el tanque hasta su capacidad y devuelve los litros que rebalsaron (0 si cupo todo).int drain(int liters): saca agua. Si se pide más de lo que hay, saca lo que hay. Devuelve los litros que de verdad salieron.int level(),int capacity()yboolean isEmpty().fillydrainrechazan conIllegalArgumentExceptionuna cantidad que no sea positiva, sin cambiar el nivel.
a) Enuncien el invariante de la clase. ¿Qué método lo establece y cuáles tienen que preservarlo?
b) Escriban la clase completa, con campos private.
c) Escriban cuatro pruebas JUnit, cada una con un nombre que diga qué se espera, que cubran: el rebalse al llenar; sacar más de lo que hay; un constructor con capacidad inválida; y que una cantidad inválida no cambia el nivel.
Pregunta 6 — Una jerarquía pequeña
Una empresa de buses vende pasajes interprovinciales. Todo pasaje tiene el nombre del pasajero y una tarifa base en céntimos, y se puede pagar: implementa la interfaz de la semana 7.
public interface Payable {
/** Monto por pagar, en céntimos. */
long amountDue();
}
Ticketes una clase abstracta. Su constructor rechaza un nombre vacío y una tarifa base que no sea positiva. Declaraabstract long price(). SuamountDue()devuelve el precio. Su método concretoString receipt()devuelve, por ejemplo,"Luis: S/ 30.25"para un precio de 3025 céntimos. Tiene ademásstatic long total(List<Ticket> tickets).AdultTicketcobra la tarifa base.StudentTicketes el medio pasaje: la mitad de la tarifa base, redondeada hacia abajo al céntimo.
a) Escriban las tres clases.
b) Escriban una prueba JUnit que arme una List<Ticket> con
pasajes de los dos tipos y verifique total.
c) ¿Por qué conviene que Ticket sea una clase abstracta y
Payable una interfaz, y no al revés? ¿Qué pasa si StudentTicket no escribe
price()?
Parte C — Diseño
Pregunta 7 — Del enunciado al diagrama de clases
Un taller mecánico registra a sus clientes (nombre y DNI) y sus vehículos (placa y marca). Un cliente puede tener varios vehículos; cada vehículo es de un solo cliente. Cada vez que un vehículo entra al taller se abre una orden de trabajo, con la fecha y una lista de ítems. Un ítem es mano de obra (horas por tarifa por hora) o un repuesto (cantidad por precio unitario). La orden calcula su total sumando sus ítems. Cuando la orden se cierra, ya no acepta ítems nuevos.
a) Dibujen el diagrama de clases UML (a mano o en Mermaid): las clases con sus campos y métodos principales y su visibilidad, las asociaciones con su multiplicidad, y la relación entre la orden y sus ítems.
b) ¿Modelarían “ítem” como interfaz o como clase abstracta? Justifiquen según lo que comparten la mano de obra y el repuesto.
c) Enuncien el invariante de la orden de trabajo y digan qué clase lo protege.
Pregunta 8 — Sustitución de Liskov
public class Thermometer {
private double current;
private double max;
private int readings;
/** Registra una lectura. Después: current() == celsius, max() >= celsius. */
public void record(double celsius) {
current = celsius;
if (readings == 0 || celsius > max) {
max = celsius;
}
readings++;
}
public double current() {
return current;
}
public double max() {
return max;
}
public int readings() {
return readings;
}
}
Tres subclases:
import java.util.ArrayList;
import java.util.List;
/** (i) Guarda además todas las lecturas. */
public class HistoryThermometer extends Thermometer {
private final List<Double> history = new ArrayList<>();
@Override
public void record(double celsius) {
history.add(celsius);
super.record(celsius);
}
public List<Double> history() {
return List.copyOf(history);
}
}
/** (ii) Un sensor de exteriores: descarta en silencio las lecturas absurdas. */
public class OutdoorThermometer extends Thermometer {
@Override
public void record(double celsius) {
if (celsius < -20 || celsius > 50) {
return;
}
super.record(celsius);
}
}
/** (iii) Rechaza con una excepción las lecturas fuera de rango. */
public class StrictThermometer extends Thermometer {
@Override
public void record(double celsius) {
if (celsius < -20 || celsius > 50) {
throw new IllegalArgumentException("lectura fuera de rango: " + celsius);
}
super.record(celsius);
}
}
a) Para cada subclase, digan si cumple el principio de sustitución de Liskov. Si no lo cumple, digan qué se rompe: ¿una precondición reforzada o una postcondición debilitada?
b) Escriban un método de prueba assertRecordContract(Thermometer t) que verifique lo que cualquier cliente de Thermometer espera de
record, que pase con un Thermometer y que falle con cada subclase que no
cumple. El objeto tiene que llegar al método como Thermometer.
c) Propongan un diseño para el sensor de exteriores que no viole el principio.
Parte D — Preguntas cortas
Respondan cada una en tres o cuatro líneas.
Pregunta 9
¿Por qué un record es inmutable? ¿Es inmutable
record Order(String id, ArrayList<String> items)? Expliquen.
Pregunta 10
Una clase hace override de equals pero no de hashCode. ¿Funciona
ArrayList.contains con objetos iguales pero distintos? ¿Y
HashSet.contains? ¿A qué se debe la diferencia?
Pregunta 11
¿Cuándo conviene composición en lugar de herencia? Den un ejemplo de la biblioteca estándar de Java en que la herencia fue un error y digan qué permite hacer que no debería.
Pregunta 12
Parent y Child extends Parent declaran cada una un método
static String kind(). Con Parent p = new Child();, ¿cuál se ejecuta en
p.kind()? ¿Por qué no es dynamic dispatch?