Tipos de Datos

Código 1: codigo/jugs/tipos/Punto.java - Una clase que representa el tipo de datos de Punto
package codigo.jugs.tipos;
/**
* Representa un punto en un plano cartesiano.
* *
Un punto asocia dos números reales que son las coordenadas del punto.
*/
  public class Punto {
  private double x;
  private double y;
/**
* Construye un punto con el estado dado
*/
  public Punto(double x, double y) {
  this.x = x;
  this.y = y;
} 
/
**
* @return el valor de X para este punto
*/
  public double getX() {
  return x;
} 
/
**
* @return el valor de Y para este punto
*/
  public double getY() {
  return y;
}
} 
codigo/jugs/tipos/geometria/Geometria.java
package codigo.jugs.tipos.geometria;
import codigo.jugs.basica.tipos.Punto;
/**
* Esta clase contiene métodos relacionados con tipos de datos
* relacionados con la Geometría
*/
  public class Geometria {
/**
* Retorna la distancia entre p1 y p2.
* La distancia aplica la fórmula pitagórica los atributos
* de los puntos dados.
* *
@param p1 un punto no nulo
* @param p2 un punto no nulo
* @return la distancia entre p1 y p2 como se explicó anteriormente
*/
    public static double distancia(Punto p1, Punto p2) {
    var cuadradoDeDiferenciasXs = Math.pow(p1.getX() - p2.getX(), 2.0);
    var cuadradoDeDiferenciasYs = Math.pow(p1.getY() - p2.getY(), 2.0);
    var distancia = Math.sqrt(cuadradoDeDiferenciasXs + cuadradoDeDiferenciasYs);
    return distancia;
  }
} 
codigo/jugs/tipos/geometria/Main.java
package codigo.jugs.tipos;
    public class Main {
    public static void main(String[] args) {
    var p1 = new Point(0.0, 1.0);
    var p2 = new Point(1.0, 0.0);
    System.out.println(Geometria.distancia(p1, p2));
  }
} 
1.4142135623730951

Antes de usar tipos, codificamos la función de distancia con cuatro parámetros dobles. Veamos las diferencias:

Código 2 - Función distancia usando tipos de datos arriba y sin tipos de datos abajo
public static double distancia(Punto p1, Punto p2) {
    var squareOfDifferenceOfXs = Math.pow(p1.x() - p2.x(), 2.0);
    var squareOfDifferenceOfYs = Math.pow(p1.y() - p2.y(), 2.0);
    var distance = Math.sqrt(squareOfDifferenceOfXs + squareOfDifferenceOfYs);
      return distance;
} 

  public static double distancia(double x1, double y1, double x2, double y2) {
    var squareOfDifferenceOfXs = Math.pow(x1 - x2, 2.0);
    var squareOfDifferenceOfYs = Math.pow(y1 - y2, 2.0);
    var distance = Math.sqrt(squareOfDifferenceOfXs + squareOfDifferenceOfYs);
  return distance;
} 

La versión de abajo tiene algunos problemas: estamos representando un punto como dos valores dobles, pero no es evidente que esos parámetros tengan alguna relación, puede que lo intuyas por el nombre pero nada más. No hay nada dentro del código que deje claro que el primer y el segundo parámetro y el tercero y el cuarto y último están relacionados. Si tienes varios métodos que usen la misma información, la misma variable representando un punto, debemos mover esas dos variables individualmente e intentar usarlas en el orden esperado. Es mucho mejor si podemos darle un nombre a las variables, un Punto, y podemos tratarlas como una única variable que tenga 2 valores mis posibilidad de confundirlos.

La línea 03 contiene una declaración de clase especificada por la palabra clave class, que es como puedes crear tus propios tipos. Un tipo encapsula datos, define propiedades que siempre serán verdaderas para el tipo, llamadas invariantes, y define qué operaciones son válidas en esos tipos. Las clases también tienen visibilidad pero solo tienen 2 y la más común es que sean públicas. Como ocurre con muchos elementos, las clases también tienen bloques, por lo que verás, en las líneas 03 y 20, el inicio y el final de un bloque. Los identificadores de clase siguen las mismas reglas que los que has visto pero la convención es que comienzan con una letra mayúscula. De esa manera, cuando lees código, es muy fácil ver qué es una clase, qué es una función y qué es una variable.

Las clases tienen 3 elementos principales: atributos (variables), constructores y métodos (funciones). Las líneas 05 y 06 declaran dos atributos: un atributo de tipo double denominado x y un atributo de tipo double denominado y. Al igual que los parámetros, los atributos necesitan definir su tipo. Tienen modificadores de visibilidad, pero a diferencia de las funciones, el modificador más común es el privado. Lo que significa que otro código no podrá ver los atributos directamente.

Al leer la sentencia class puedes ver que un Punto es la encapsulación de dos variables de tipo double, llamadas x e y.

La línea 08 inicia un constructor. Puedes saber que es un constructor porque no tiene un tipo de retorno y su identificador es el mismo que el nombre de la clase, incluyendo las mayúsculas. Al igual que las funciones, los constructores también pueden tomar parámetros. Los constructores se utilizan para inicializar los atributos de una clase. El constructor más común es aquel que toma los mismos números y tipos de parámetros que sus atributos. Luego usa esos parámetros para inicializar los atributos como ves en las líneas 09 y 10. Cada vez que veas la palabra clave this, se refiere al objeto en el que se está operando, por lo que <this.x> es el atributo x del objeto con el que estás interactuando. Los constructores también tienen el concepto de visibilidad y normalmente se utiliza un constructor público para crear objetos de una clase. Puedes tener muchos constructores en una clase y eso suele ser muy bueno porque significa que tienes más flexibilidad en la forma de crear objetos. Ahora está claro que para construir puntos necesitas conocer los valores de <x> e <y>.

Si bien hasta ahora hemos usado solo método públicos, con los atributos vamos a ir en contravía: todos los atributos, por ahora, van a ser privados. Si bien hay atributos que pueden no ser privados, igualmente es cierto que un atributo muy, muy rara vez debe ser público. Al hacer un atributo público este código falla por fuera de la clase Punto:

var miPunto = new Punto(-1.0, 3.0);
var abcisa = miPunto.x; // ERROR
var ordenada = miPunto.y; // ERROR

Si es cierto que la variable <miPunto> de tipo Punto, tiene atributos <x> e <y> no los puede acceder directamente como en el código anterior. ¿Entonces cómo puedo conocer la información que contiene un punto?

No sería de mucha utilidad si podemos crear objetos pero no interactuar con ellos, por lo que puedes mezclar datos y comportamiento en las clases. Las líneas 13 y 17 definen métodos. Los métodos son funciones que forman parte de una clase y operan sobre sus atributos, pero en términos prácticos un método es una forma especializada de una función por lo tanto todas las buenas prácticas que hemos visto para funciones, se aplican a métodos.

Para el Código 1 sólo estamos agregando los métodos básicos: accesores, métodos destinados a exponer algunos de los atributos de la clase. Así podemos conocer la información que contiene un punto. ¿No es mucho trabajo hacer los atributos privados y usar accesores? Se debe a la encapsulación: si los atributos son privados, puedes en el futuro cambiarlos de alguna manera y reimplementar los accesores y el resto del código funcionará sin ningún cambio. Puede sonar extraño que pensemos en modificar los atributos de una clase, pero sucede más de lo que uno se imaginaría especialmente en software complejo.

Métodos no accesores

Código 2: Un tipo de dato que representa una coordenada en una matriz.
package codigo.jugs.tipos;
    public class Coordenada {
    private int hilera;
    private int columna;
    public Coordenada(int hilera, int columna) {
    this.hilera = hilera;
    this.columna = columna;
} /
**
* Comprueba si dos coordenadas son ortogonales, es decir están una al lado de la otra.
* *
Esta implementación verifica si 2 coordenadas tienen el mismo valor para alguno de
* sus atributos y para el otro atributo la diferencia no es más de uno.
* *
@param otra una coordenada no nulo.
* @return verdadero si otra es ortogonal al objeto sobre el que se está operando
*/
    public boolean sonOrtogonales(Coordenada otra) {
    if (hilera == otra.hilera || column == otra.column) {
      return Math.abs(columna - otra.columna) == 1 || Math.abs(hilera - other.hilera) == 1;
  } 
return false;
  }
} 
package codigo.jugs.tipos;
    public class Main {
    public static void main(String[] args) {
    var c1 = new Coordenada(3, 3);
    var c2 = new Coordenada(3, 2);
    System.out.println(c1.sonOrtogonales(p2));
  }
} 
true

Las clases pueden tener cualquier tipo de método. En el Código 2 definimos una clase Coordenada que encapsula una fila y una columna. Es útil calcular cuándo dos coordenadas son ortogonales: ambas coordenadas comparten uno de sus valores y la diferencia del otro valor es exactamente 1. Por ejemplo, imagina una cuadrícula como esta:

(0, 0) (0, 1) (0, 2)
(1, 0) (1, 1) (1, 2)
(2, 0) (2, 1) (2, 2)

Puedes ver que la coordenada (1, 1) tiene 4 vecinos ortogonales: (0, 1), comparten la misma columna y su fila está separada por 1, (1, 2), comparten la misma fila y su columna está separada por 1, (2 , 1) y (1, 0) por la misma razón.

Las líneas entre 22 y 27 implementan dicho método. Todos los métodos tienen un modificador de visibilidad y su firma: un valor de retorno, un identificador y un conjunto de parámetros. No hay limitaciones sobre lo que puedes devolver o sobre los parámetros, tienes total libertad. Este método toma una Coordenada como parámetro, lo que significa que opera con dos: una Coordenada implícita en el código llamada this y la otra es el parámetro. Esto quizás se puede ver mejor cuando usamos el método:

var unoUno = new Coordenada(1, 1);
var unoDos = new Coordenada(1, 2);
unoUno.sonOrtogonales(unoDos); // sonOrtogonales opera sobre 2 coordenadas:
                               // <unoUno> y <unoDos>. En el código de sonOrtogonales,
                              // la variable sobre la que se llama el método, <unoUno>
                              // se referencia como <this> y la variable que es el
                              // parámetro se referencia con el nombre del parámetro

En la línea 23 verificamos si las dos coordenadas comparten la misma fila o la misma columna, y si eso es cierto, en la línea 24 revisamos la siguiente condición para ser ortogonales: que las filas o columnas estén separadas por 1, es decir que el valor absoluto de su diferencia es 1.

Podemos implementar más métodos para poder hacer otras operaciones sobre los tipos de datos, pero también podemos crear funciones (también llamadas métodos estáticos) que utilicen nuestros tipos de datos. Exploremos la diferencia.

Estatico vs No- Estatico

Código 3 - Punto de clase con métodos.
package codigo.jugs.geometria.puntos;
    public class Punto {
    private double x;
    private double y;
    public Punto(double x, double y) {
    this.x = x;
    this.y = y;
} 
    public double distancia(Punto p) {
    var cuadradoDeDiferenciasDeXs = Math.pow(this.x - p.x, 2.0);
    var cuadradoDeDiferenciasDeYs = Math.pow(this.y - p.y, 2.0);
    var distancia = Math.sqrt(cuadradoDeDiferenciasDeXs +
    cuadradoDeDiferenciasDeYs);
      return distancia;
  }
} 
Código 4 - Usando distancia como un método no estático
package code.jugs.points;
  public class Main {
    public static void main(String[] args) {
    var p1 = new Punto(0.0, 1.0);
    var p2 = new Punto(1.0, 0.0);
      System.out.println(p1.distancia(p2));
  }
} 

Antes de este capítulo, siempre usamos el modificador static en nuestras funciones. Ahora que hablamos de objetos, vamos a escribir más método no estáticos que estáticos en nuestros tipos de datos. Entonces, ¿cuál es la diferencia?

Puedes pensar que los métodos no estáticos siempre son parte de un objeto. Primero necesitas un objeto y luego puedes llamar al método no estático en el objeto. Este método obtendrá acceso al atributo del objeto utilizando la palabra clave <this>.

Presta mucha atención a la línea 05 del código 4. Mira que el método de distancia se llama desde p1. Como se mencionó anteriormente, primero necesitas un objeto, el objeto sobre el que operarás. Ahora presta atención a las líneas 14 y 15 del Código 3 de Punto: en esas líneas accedes a los atributos de los 2 puntos necesarios para calcular la distancia. Para el objeto sobre el que estás operando usas <this>, para el parámetro accedes a ellos de la misma manera que lo has hecho antes.

Veamos ambos códigos juntos, usando métodos no estáticos, arriba, y métodos estáticos, abajo:

Código 5 - Función usando funciones no estáticas vs estáticas
public class Main {
    public static void main(String[] args) {
      var p1 = new Punto(0.0, 1.0);
      var p2 = new Punto(1.0, 0.0);
      p1.distancia(p2); // no-estática
  }
} 
public class Main {
    public static void main(String[] args) {
      var p1 = new Punto(0.0, 1.0);
      var p2 = new Punto(1.0, 0.0);
      Geometria.distancia(p1, p2); // estática
  }
} 

¿Entonces cuál es mejor?

Como ocurre con muchas cosas en programación, no es útil pensar en términos de ser "mejor" o "peor". Lo que hay que pensar es en términos de ventajas y desventajas.

Una ventaja de los métodos no-estáticos es que tienen visibilidad de los atributos de un objeto: es común tener atributos que no están expuestos con accesores, por lo tanto, tener visibilidad de todos los atributos podría facilitar la implementación de ciertos métodos. Si ese no es el caso, entonces los métodos no tienen ninguna ventaja sobre los métodos estáticos desde el punto de vista de su implementación.

Pero no solo es importante la implementación: Los métodos no-estáticos usan una sintaxis en la que parece que los objetos se "comunican". En el código de arriba, p1 y p2 inician una conversación: p1 inicia la conversación al invocar su método distancia() y p2 se une a la conversación como parámetro. El resultado de esa conversación es su valor de retorno. Esta es una excelente manera de modelar sistemas complejos. Pero nuevamente, esta es una preferencia personal. Cuando se trabaja en equipos, lo más importante es tener un estilo de codificación coherente.

Igualdad

Código 6 - no uses == para verificar igualdad de objetos
package codigo.jugs.geometria;
    public class Main {
    public static void main(String[] args) {
      var p1 = new Point(0.0, 0.0);
      var p2 = new Point(0.0, 0.0);
        System.out.println(p1 == p2);
    }
} 
false

¿Qué crees que debería pasar si usas “==” con dos objetos como puntos? Teniendo en cuenta cómo se comparan otras variables como los ints, podrías pensar que debería funcionar parecido, pero como ves en el código anterior el comportamiento puede parecer inesperado.

Cuando vimos Strings, dijimos que no debían ser comparados usando == y que deberías usar el método equals(), pero qué pasa con otros tipos de datos que creamos: Resulta que somos nosotros los que tenemos que agregar el método equals():

Código 07 - implementando equals para Punto
package codigo.jugs.tipos;
    public class Punto {
    private double x;
    private double y;
    public Punto(double x, double y) {
    this.x = x;
    this.y = y;
} 
  public boolean equals(Object otro) {
    return otro instanceof Punto p && this.x == p.x && this.y == p.y;
  }
} 
package codigo.jugs.tipos;
    public class Main {
    public static void main(String[] args) {
    var p1 = new Punto(0.0, 0.0);
    var p2 = new Punto(0.0, 0.0);
      System.out.println(p1.equals(p2));
      System.out.println(p1.equals("un String"));
  }
} 
True
false

La línea 13 implementa el método equals() para el tipo Punto. El tipo de retorno parece esperado, pero ¿por qué usas Object como parámetro y qué significa? Bueno, Object es un tipo muy importante en Java ya que Object es implícitamente el supertipo de todos los tipos en Java. No profundizaremos mucho en qué es un supertipo, pero recuerda que existe una estrecha relación entre todos los tipos de datos, los que creas y los que se proporcionan en la biblioteca estándar de Java. Dado que hay infinitos tipos posibles, es conveniente tener un método que puedas utilizar para comparar 2 tipos de datos cualesquiera.

El operador <instanceof> verifica que un parámetro es de un cierto tipo de dato, así que lo primero que preguntamos es si el parámetro es realmente un punto, y damos el nombre <p> a ese punto para verificar si los atributos <x> e <y> son iguales. Este es un patrón más o menos sencillo de utilizar, aunque al principio puede parecer engorroso pero te acostumbras rápidamente.

Implementación por defecto de una clase

Código 08 - una clase minimalista
public class Foo {
} 
public class Main {
    public static void main(String[] args) {
      var foo = new Foo();
      var foo2 = new Foo();
    System.out.println(foo.toString());
    System.out.println(foo.equals(foo2));
  }
} 
code.jugs.foo.Foo@251a69d7
false

¡Espera! ¿Cómo es posible llamar a toString() y a equals() en la clase Foo si no los has implementado? Bueno, se heredan de la clase Object. ¡Pero primero lo primero!

Java, hoy en día, tiene dos tipos principales: tipos primitivos y tipos de referencia. Has visto algunas primitivas: int, boolean, char, etc. Has visto algunos tipos de referencia: String, LocalDate y la clase Punto y Longitud en este capítulo. Los tipos de referencia son fáciles de detectar porque es necesario utilizar el operador <new> para crearlos.

Algo importante que debes saber es que en Java todas las clases heredan directa o indirectamente de la clase Object. La herencia es un tema fuera del alcance de este material, pero si heredas de una clase los métodos de esa clase. Eso garantiza en Java que todas las clases tienen un conjunto básico de métodos, por lo que otras clases pueden depender de ellos. Ya has visto dos de esos métodos: equals(Object) y toString() incluso si no los escribes tú mismo para tu clase, puedes llamarlos en cualquier clase que crees, aunque lo que retornan puede parece confuso.

Si no escribes to propio toString(), imprimirá una representación interna de dónde está almacenado el objeto. Esto puede cambiar de una computadora a otra, ¡así que nunca confíes en ello! Implementar toString() es muy sencillo, por ejemplo para Punto:

Código 09 - implementando toString()
package codigo.jugs.tipos;
    public class Punto {
    private double x;
    private double y;
    public Punto(double x, double y) {
    this.x = x;
    this.y = y;
  }
    public String toString() {
    return String.format("(%f, %f)", x, y);
  }
} 
package codigo.jugs.tipos;
    public class Main {
    public static void main(String[] args) {
    var p1 = new Punto(0.0, 1.0);
    System.out.println(p1.toString());
  }
} 
(0.000000, 1.000000)

En el código 09 ves una implementación de toString(), desde que uses esa firma, habrás implementado toString() correctamente. La implementación del método ya dependerá del tipo de dato.

Como hemos dicho anteriormente, equals() por sí mismo no hace nada muy interesante, deberás implementarlo como lo hemos visto antes. Solo ten en cuenta que si no lo implementas, igual lo podrás usar aunque recuerda su comportamiento poco intuitivo.

Hay más métodos que heredan tu objeto, aunque esos 2 son los que probablemente uses más por ahora. Así que recuerde que métodos como toString() y equals(Object) son especiales, ya que se heredan, ¡y luego en Java todas las clases heredan de Object directa o indirectamente!

Ejercicios

E9.1

Escribe un tipo de dato Autor, para representar el nombre de una persona.

Constructor
Autor(String nombre, String apellido)
Métodos
String nombre()
String apellido()
String toString()

Asserts

var nombre = "John";
var apellido = "Doe";
var johnDoe = new Autor(nombre, apellido);
assertAll(
() -> assertEquals(nombre, user.nombre()),
() -> assertEquals(apellido, user.apellido()),
() -> assertEquals("Doe, John", user.toString())
) 
var nombre = "Jane";
var apellido = "Roe";
var janeRoe = new Autor(nombre, apellido);
assertEquals(nombre, janeRoe.nombre());
assertEquals(apellido, janeRoe.apellido());
assertEquals("Roe, Jane", janeRoe.toString()); 

E9.2

Escribe una clase Longitud.

Constructor
Longitud (double medida)
Métodos
double medida()
Longitud sumar(Longitud otra)
boolean equals(Object obj)

Asserts

var measure = 3.14;
assertEquals(measure, new Longitud(measure).measure(), 0.0); 
assertEquals(new Longitud(3.0), new Longitud(1.0).suma(new Longitud(2.0)), 0.0);
            
assertEquals(new Longitud(3.25), new Longitud(0.75).suma(nueva Longitud(2.50)), 0.0);
            

E9.3

Escribe una clase UPC que encapsule un String que representa un número UPC. El constructor toma un String sin su dígito de verificación y tiene un accesor que devuelve el número con el dígito de verificación. El método para calcular el dígito de verificación lo puedes encontrar en el ejercicio E6.2 que se encuentra en el capítulo “Condicionales”.

Constructor
UPC(String número)
Métodos
String número()

Asserts

var número = "03600024145";
var númeroConVerificación = number + "7";
assertEquals(númeroConVerificación, new UPC(número).número()); 
var número = "12345678901";
var númeroConVerificación = number + "2";
assertEquals(númeroConVerificación, new UPC(número).número());
            

E9.4

Escribe una clase Círculo. Recuerda usar la clase Punto con el método no-estático distancia que se encuentra en este capítulo.

Constructor
Círculo(Punto centro, double radio)
Métodos
Punto centro()
double área()
double circunferencia()
boolean seInterceptan(Círculo otro)

Utiliza estas fórmulas para los métodos:

  • Área: Math.PI * radio* radio
  • Circunferencia: 2 * Math.PI * radio
  • Intersección: Encuentra la distancia entre los centros de ambos círculos. Los círculos se interceptan si la distancia es igual o menor que la suma de los radios.

Asserts

assertEquals(Math.PI, new Círculo(new Punto(0.0, 0.0), 1.0).área(), 1e-3);
assertEquals(12.566, new Círculo(new Punto(3.0, 4.0), 2.0).área(), 1e-3); 
assertEquals(Math.PI * 2.0, new Círculo(new Punto(0.0, 0.0), 1.0).circunferencia(),
1e-3);
assertEquals(12.566, new Círculo(new Puntot(0.0, 0.0), 2.0).circunferencia(), 1e-3);
            
assertEquals(new Círculo(new Punto(1.0, 2.0), 3.0), (new Círculo(new Punto(1.0, 2.0), 3.0)));
assertNotEquals(new Círculo(new Punto(0.0, 0.0), 1.0), (new Círculo(new Punto(0.0, 1.0), 1.0)));
assertNotEquals(new Círculo(new Punto(0.0, 0.0), 1.0), (new Círculo(new Punto(1.0, 0.0), 1.0)));
assertNotEquals(new Círculo(new Punto(0.0, 0.0), 1.0), (new Círculo(new Punto(0.0, 0.0), 2.0)));
            
var ceroCero = new Círculo(new Punto(0.0, 0.0), 1.0);
var unoCero = new Círculo(new Punto(1.0, 0.0), 1.0);
assertTrue(ceroCero.seIntercepta(unoCero));
            
var ceroCero = new Círculo(new Punto(0.0, 0.0), 1.0);
var dosDos = new Círculo(new Punto(2.0, 2.0), 1.0);
assertFalse(ceroCero.intersects(dosDos));
            

E9.5

Escribe una clase Fracción que tenga operaciones para sumar, restar, multiplicar y dividir fracciones. Una fracción puede considerarse como la encapsulación de dos números enteros llamados: numerador y denominador.

Un concepto muy importante para las fracciones es la reducción. Cuando operas una fracción, quieres usar la fracción con el numerador y denominador más pequeño que sea equivalente. Para ello, necesitamos tener una manera de encontrar el máximo común divisor, mcd, y el mínimo común múltiplo, mcm. Puedes utilizar estas funciones para calcularlos:

public class Aritmética {
    public static int mcd(int a, int b) {
    return b == 0 ? a : mcd(b, a % b);
  }
    public static int mcm(int a, int b) {
    return a / mcd(a, b) * b;
  }
}
            

Así se debe implementar la clase Fraction (supongamos que el objeto sobre el que se opera es <this> y el parámetro es <that>):

Constructor:

  1. Encuentra el MCD de los parámetros: numerador y denominador. Utiliza Math.abs para asegurarte que sea positivo.
  2. Calcula numerador / MCD, llamémoslo <numeradorReducido>
  3. Calcula el denominador / MCD, llamémoslo <denominadorReducido>.
  4. Si el parámetro denominador es negativo, entonces el this.numerador y this.denominador son numeradorReducido * -1 y denominadorReducido * -1. De lo contrario, this.numerador y this.denominador son iguales a numeradorReducido y denominadorReducido.

Sumar: La fracción resultante es

this.numerator*MCM/this.denominator + that.numerator*MCM/that.denominator MCM

Restar: La fracción resultante es

this.numerator*MCM/this.denominator - that.numerator*MCM/that.denominator MCM

Otra alternativa es implementar la resta usando la suma:

Donde:

Multiplicar: la fracción resultante es

this.numerator * that.numerator this.denominator * that.denominator

División: La fracción resultante es

this.numerator * that.denominator this.denominator * that.numerator

Otra alternativa es definir la división en términos de la multiplicación:

Donde:

Constructor
Fracción (int numerador, int denominador)
Métodos
Fracción sumar(Fracción that)
Fracción restar(Fracción that)
Fracción multiplicar(Fracción that)
Fracción dividir(Fracción that)

Asserts

assertEquals(new Fracción(1, 2), new Fracción(2, 4)); 
assertEquals(new Fracción(1, 2), new Fracción(-1, -2));
assertEquals(new Fracción(-1, 2), new Fracción(1, -2));
            
assertEquals(new Fracción(1, 1), new Fracción(1, 4).suma(nueva Fracción(3, 4)));
assertEquals(new Fracción(7, 12), new Fracción(1, 3).sumar(new Fracción(1, 4)));
            
assertEquals(new Fracción(3, 4), new Fracción(1, 1).restar(new Fracción(1, 4)));
            
assertEquals(new Fracción(5, 24), new Fracción(5, 6).multiplicar(new Fracción(1, 4)));
            
assertEquals(new Fracción(1, 1), new Fracción(-1, 2).dividir(new Fracción(-1, 2)));
            
Scroll to Top