Universidad Carlos III de Madrid

Grado en Ingeniería Telemática/Grado en Ingeniería de Sistemas de Comunicaciones

Enero-Mayo 2014

Orientación a Objetos y Herencia

Lab Section1. Sesión 6 (laboratorio): Orientación a Objetos y Herencia (III)

Exercise Section1.1. Puntos y Figuras Geométricas (II)

El objetivo general de esta práctica es repasar el uso de la herencia y el polimorfismo en Java. La herencia la basaremos en el uso de abstract y extends, con los que crearemos clases genéricas y específicas siguiendo una determinada jerarquía. Las clases específicas implementarán los métodos abstractos definidos en las genéricas y sobreescribirán otros métodos que permitirán demostrar el polimorfismo. La jerarquía de clases de este ejercicio estará compuesta por la clase Figure de la que derivarán las clases Circle, Triangle y Quadrilateral. Además, de la clase Quadrilateral derivará la clase Rectangle.

Una vez implementadas todas las clases, se creará un contenedor con objetos de diferentes clases, sobre el cual realizaremos operaciones de forma sencilla aprovechando la potencia del polimorfismo en Java.

Clases abstractas y herencia.

La clase Figure representa una figura genérica que se materializará posteriormente en una específica (Circle, Triangle, Rectangle). En Java, esto se representa mediante las clases abstractas que se declaran mediante la palabra reservada abstract.

El primer ejercicio de la práctica consiste en definir la siguiente clase abstracta Figure. Puedes descargar su código en Figure.java.

public abstract class Figure {

    // Name of the figure
    private String name;

    // Constructor of the figure
    public Figure(String name) {

    }

    // Calculates the area of a figure
    abstract public double area();

    // Indicates if the figure is regular or not
    abstract public boolean isRegular();

    // Gets the name of the figure
    protected String getName() {

    }

    // Sets the name of the figure
    protected void setName(String name) {

    }

}
La clase Figure.
  1. Programa el constructor de la clase Figure y los métodos get y set correspondientes.

La subclase Circle.

La clase Circle deriva de Figure y debes implementarla teniendo en cuenta esta situación. Realiza las siguientes acciones sobre la clase Circle:

  1. Declara la clase Circle para que herede (extends) de la clase Figure.

  2. Implementa el constructor. Dicho constructor debe recibir tres parámetros, un nombre (name) de tipo String, un centro (center) de tipo Point, y un radio (radius) de tipo int. Utiliza la clase Point.java y llama al constructor de la clase base Figure mediante la referencia super en la implementación del constructor de la clase Circle.

  3. Implementa el método area() que devuelva el área del círculo. Haz uso de Math.PI para el cálculo del área.

  4. Implementa el método isRegular() que devuelva true si la figura es regular y false en caso contrario. Ten en cuenta que un círculo siempre es una figura regular.

  5. Sobreescribe el método toString() para que devuelva el nombre de la figura, su centro y su radio.

  6. Implementa los métodos get y set necesarios en esta clase.

La subclase Triangle.

La clase Triangle también deriva de Figure. Realiza las siguientes acciones sobre la clase Triangle.java

  1. Declara la clase Triangle para que herede de la clase Figure.

  2. Implementa el constructor, el cual debe recibir cuatro parámetros, un nombre (name) de tipo String, y tres puntos (vertex1, vertex2 y vertex3) de tipo Point representando los tres vértices del triángulo. Ten en cuenta que debes llamar al constructor de la clase base Figure mediante la referencia super.

  3. Implementa el método area() que calcule el área del triángulo. Utiliza la siguiente fórmula area = 0.5*|Ax(By-Cy)+Bx(Cy-Ay)+Cx(Ay-By)| donde x e y representan las coordenadas en el eje x y en el eje y de los tres vértices del triángulo (A,B,C), y |x| el valor absoluto de x.

  4. Implementa el método isRegular() que indique si el triángulo es regular, es decir si sus tres lados son iguales.

  5. Sobreescribe el método toString() para que devuelva el nombre de la figura, y sus vértices.

  6. Implementa los métodos get y set necesarios en esta clase.

La subclase Quadrilateral.

La clase Quadrilateral es otra clase que deriva de Figure. Sin embargo, la clase Quadrilateral es una clase abstracta ya que no es posible implementar el método que calcula el área sin conocer el cuadrilátero concreto. Realiza las siguientes acciones sobre la clase Quadrilateral.

  1. Declara la clase Quadrilateral para que herede de la clase Figure.

  2. Implementa el constructor, el cual debe recibir cinco parámetros, un nombre (name) de tipo String, y cuatro puntos (vertex1, vertex2, vertex3 y vertex4) de tipo Point representando los cuatro vértices del cuadrilátero. Ten en cuenta que debes llamar al constructor de la clase base Figure mediante la referencia super.

  3. Implementa el método isRegular() que indique si el cuadrilatero es regular, es decir si es un rectángulo o un cuadrado. Para ello puedes llamar desde el método isRegular() al método privado checkRectangle() que a su vez invoca al método privado scalarProduct(). El código de ambos métodos se indica al final de este apartado. ¿Qué es lo que hacen estos métodos?

  4. Sobreescribe el método toString() para que devuelva el nombre de la figura, y sus vértices.

  5. Implementa los métodos get y set necesarios en esta clase.

    /**
     * Indicates if the quadrilateral is a rectangle or square by
     * comparing sizes and diagonals and calculating the scalar product
     * 
     */
    private boolean checkRectangle(Point v1, Point v2, Point v3, Point v4){
        Point auxVertex = v1.nearest(new Point[]{v2,v3,v4});
        if (auxVertex.equals(v2)){
            return v1.distance(v3) == v2.distance(v4)
                && v1.distance(v4) == v2.distance(v3)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v3,v4}));
        } else if (auxVertex.equals(v3)){
            return v1.distance(v2) == v3.distance(v4)
                && v1.distance(v4) == v3.distance(v2)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v4}));
        } else if (auxVertex.equals(v4)){
            return v1.distance(v2) == v4.distance(v3)
                && v1.distance(v3) == v4.distance(v2)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v3})); 
        } else {
            return false;
        }
    }

    private boolean scalarProduct(Point p1, Point p2, Point p3){
        return (p3.getY()-p1.getY())*(p2.getY()-p1.getY())
	    + (p3.getX()-p1.getX())*(p2.getX()-p1.getX()) == 0;
    }
La subclase Rectangle.

La clase Rectangle hereda de Quadrilateral. Realiza las siguientes acciones sobre la clase Rectangle

  1. Declara la clase Rectangle para que herede de la clase Quadrilateral.

  2. Implementa el constructor para que reciba cinco parámetros, un nombre (name) de tipo String, y cuatro puntos (vertex1, vertex2, vertex3 y vertex4) de tipo Point representando los cuatro vértices del rectángulo. Ten en cuenta que debes llamar al constructor de la clase base Quadrilateral mediante la referencia super. Comprueba como parte del constructor que el rectángulo efectivamente es un cuadrilátero regular y en caso contrario lanza una excepción (recomendamos que revises la documentación sobre excepciones)

  3. Implementa el método area() que calcule el área del rectángulo. Para ello debes conocer la base y la altura. En un rectángulo, dado un vértice V1, la distancia con los dos vértices más cercanos definen la base y la altura del rectángulo.

  4. Sobreescribe el método toString() para particularizarlo al rectángulo.

Polimorfismo. Cálculo del area de varias figuras.

La clase FiguresSet contiene un ArrayList de Figuras y permitirá calcular el área total de un conjunto de figuras. Esta clase tiene el siguiente aspecto:

import java.util.ArrayList;

public class FiguresSet {

    // Array of figures
    private ArrayList<Figure> figures;

    // Constructor of FiguresSet
    public FiguresSet() {

    }

    // Calculates the total area of the figures
    public double totalArea() {

    }

    // figures to String
    public String toString() {

    }

    // Adds a new figure to the FigureSet
    public void addFigure(Figure f) {

    }

    // Main program
    public static void main(String args[]) throws Exception {

    }

}

El constructor de la clase debe crear un objeto de la clase ArrayList sobre el que se irán añadiendo las distintas figuras. El método addFigure() permite añadir un objeto Figure en el contenedor. El método totalArea() es el que irá sumando las areas de las figuras que contiene el array y devolverá la suma total de ellas. El método toString() devuelve un String con la información de todas las figuras. total con algún mensaje descriptivo de las figuras.

  1. Implementa el constructor de la clase.

  2. Programa en método addFigure (Figure f) que permite añadir un objeto Figure al contenedor.

  3. Programa en método totalArea() que permite calcular el area total de las figuras del contenedor.

  4. Programa en método toString() que permite devolver la información sobre las figuras incluidas en el contenedor.

El método main de la clase FiguresSet

Crea el programa principal que tiene que realizar lo siguiente:

  1. Crear un objeto de la clase FiguresSet.

  2. Crear un círculo.

  3. Crea un triángulo.

  4. Crea un rectángulo.

  5. Añade el círculo, el triangulo y el rectángulo al objecto de la clase FigureSet.

  6. Imprime el resultado del calculo del area total de las figuras y también la información sobre las figuras.

Soluciones


public abstract class Figure {

    // Name of the figure
    private String name;

    // Constructor of the figure with a name
    public Figure (String name){
        this.name = name;
    }

    // Calculates the area of a figure
    public abstract double area();

    // Indicates if the figure is regular or not
    public abstract boolean isRegular();

    // Gets the name of the figure
    protected String getName(){
        return this.name;
    }

    // Sets the name of the figure
    protected void setName(String name){
        this.name = name;
    }
}

  
public class Point {
    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Returns the string representation of the point.
     *
     */
    public String toString() {
        return "(" + x + ", " + y + ")";
    }

    /**
     * Returns the distance to the origin.
     *
     */
    public double distance() {
        Point origin = new Point(0.0, 0.0);
        return distance(origin);
    }

    /**
     * Returns the x coordinate of the point.
     *
     */
    public double getX() {
        return x;
    }

    /**
     * Returns the y coordinate of the point.
     *
     */
    public double getY() {
        return y;
    }

    /**
     * Returns the distance to another point.
     *
     */
    public double distance(Point anotherPoint) {
        return Math.sqrt(Math.pow(x - anotherPoint.getX(), 2) +
                         Math.pow(y - anotherPoint.getY(), 2));
    }

    /**
     * Returns the quadrant in which the point is.
     *
     */
    public int quadrant() {
        if (x > 0.0 && y > 0.0) {
            return 1;
        } else if (x < 0.0 && y > 0.0) {
            return 2;
        } else if (x < 0.0 && y < 0.0) {
            return 3;
        } else if (x > 0.0 && y < 0.0) {
            return 4;
        } else {
            // (x==0.0 || y==0.0)
            return 0;
        }
    }

    /**
     * Returns the nearest point of the array in the parameter, or
     * null if array is empty.
     *
     */
    public Point nearest(Point[] otherPoints) {
        Point nearestPoint = null;
        double minDistance = Double.MAX_VALUE;
        double currentDistance;

        for (int i=0; i<otherPoints.length; i++) {
            currentDistance = this.distance(otherPoints[i]);
            if (currentDistance <= minDistance) {
                minDistance = currentDistance;
                nearestPoint = otherPoints[i];
            }
        }
        return nearestPoint;
    }
}

  

public class Circle extends Figure {

    public static final double PI = Math.PI;
    private Point center;
    private int radius;

    // Constructor of the circle
    public Circle (String name, Point center, int radius){
        super(name);
        this.center = center;
        this.radius = radius;
    }

    // Calculates the area of the circle
    public double area() {
        return PI*radius*radius;
    }

    // The circle is always a regular figure
    public boolean isRegular() {
        return true;
    }

    // Circle to String
    public String toString() {
        return ("Circle: " + getName() + "; Radius: " + getRadius()
                + "; Center" + getCenter().toString());
    }

    // Gets the center of the circle
    private Point getCenter(){
        return this.center;
    }

    // Sets the center of the circle
    private void setCenter(Point center){
        this.center = center;
    }

    // Gets the radius of the circle
    private int getRadius(){
        return this.radius;
    }

    // Sets the radius of the circle
    private void setRadius(int radius){
        this.radius = radius;
    }
}

  
public class Triangle extends Figure{

    // Vertexes of the triangle
    private Point vertex1;
    private Point vertex2;
    private Point vertex3;

    // Constructor of the triangle
    public Triangle(String name, Point vertex1, Point vertex2, Point vertex3) {
        super(name);
        this.vertex1 = vertex1;
        this.vertex2 = vertex2;
        this.vertex3 = vertex3;
    }

    // Calculates the area of the triangle
    public double area() {
        return 0.5 * Math.abs(vertex1.getX() * (vertex2.getY() - vertex3.getY())
                         + vertex2.getX() * (vertex3.getY() - vertex1.getY())
                         + vertex3.getX() * (vertex1.getY() - vertex2.getY()));
    }

    // Indicates if the triangle is regular (equilateral triangle) or not
    public boolean isRegular(){
        return (vertex1.distance(vertex2) == vertex2.distance(vertex3))
            && (vertex1.distance(vertex2) == vertex3.distance(vertex1));
    }

    // Triangle to String
    public String toString() {
        return ("Triangle: " + getName() + "; Vertexes: "
                + getVertex1().toString() + getVertex2().toString()
                + getVertex3().toString());
    }

    // Gets vertex1 of the triangle
    private Point getVertex1() {
        return vertex1;
    }

    // Gets vertex2 of the triangle
    private Point getVertex2() {
        return vertex2;
    }

    // Gets vertex3 of the triangle
    private Point getVertex3() {
        return vertex3;
    }

    // Sets vertex1 of the triangle
    private void setVertex1(Point vertex1) {
        this.vertex1 = vertex1;
    }

    // Sets vertex2 of the triangle
    private void setVertex2(Point vertex2) {
        this.vertex2 = vertex2;
    }

    // Sets vertex3 of the triangle
    private void setVertex3(Point vertex3) {
        this.vertex3 = vertex3;
    }
}

  

public abstract class Quadrilateral extends Figure {

    // Vertexes of the quadrilateral
    private Point vertex1;
    private Point vertex2;
    private Point vertex3;
    private Point vertex4;

    // Constructor of the quadrilateral
    public Quadrilateral(String name, Point vertex1, Point vertex2,
                         Point vertex3, Point vertex4) {
        super(name);
        this.vertex1 = vertex1;
        this.vertex2 = vertex2;
        this.vertex3 = vertex3;
        this.vertex4 = vertex4;
    }

    // Calculates the area of the quadrilateral
    public abstract double area();

    // Indicates if the quadrilateral is regular.
    public boolean isRegular(){
        return checkRectangle(vertex1, vertex2, vertex3, vertex4);
    }

    /**
     * Indicates if the quadrilateral is a rectangle or square by
     * comparing sizes and diagonals and calculating the scalar product.
     *
     */
    private boolean checkRectangle(Point v1, Point v2, Point v3, Point v4){
        Point auxVertex = v1.nearest(new Point[]{v2,v3,v4});
        if (auxVertex.equals(v2)){
            return v1.distance(v3) == v2.distance(v4)
                && v1.distance(v4) == v2.distance(v3)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v3,v4}));
        } else if (auxVertex.equals(v3)){
            return v1.distance(v2) == v3.distance(v4)
                && v1.distance(v4) == v3.distance(v2)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v4}));
        } else if (auxVertex.equals(v4)){
            return v1.distance(v2) == v4.distance(v3)
                && v1.distance(v3) == v4.distance(v2)
                && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v3}));
        } else {
            return false;
        }
    }

    private boolean scalarProduct(Point p1, Point p2, Point p3){
        return (p3.getY()-p1.getY())*(p2.getY()-p1.getY())
	    + (p3.getX()-p1.getX())*(p2.getX()-p1.getX()) == 0;
    }

    // Quadrilateral to String
    public String toString(){
        return (getName() + "; Vertexes: " + getVertex1().toString()
                + getVertex2().toString() + getVertex3().toString()
                + getVertex4().toString());
    }

    // Gets vertex1 of the quadrilateral
    protected Point getVertex1(){
        return vertex1;
    }

    // Gets vertex2 of the quadrilateral
    protected Point getVertex2(){
        return vertex2;
    }

    // Gets vertex3 of the quadrilateral
    protected Point getVertex3(){
        return vertex3;
    }

    // Gets vertex4 of the quadrilateral
    protected Point getVertex4(){
        return vertex4;
    }

    // Sets vertex1 of the quadrilateral
    protected void setVertex1(Point vertex1){
        this.vertex1 = vertex1;
    }

    // Sets vertex2 of the quadrilateral
    protected void setVertex2(Point vertex2){
        this.vertex2 = vertex2;
    }

    // Sets vertex3 of the quadrilateral
    protected void setVertex3(Point vertex3){
        this.vertex3 = vertex3;
    }

    // Sets vertex4 of the quadrilateral
    protected void setVertex4(Point vertex4){
        this.vertex4 = vertex4;
    }
}

  

public class Rectangle extends Quadrilateral {

    // Constructor of the rectangle
    public Rectangle(String name, Point vertex1, Point vertex2, Point vertex3,
                     Point vertex4) throws BadFigureException {
        super(name,vertex1,vertex2,vertex3,vertex4);
        if (isRegular() == false){
            throw new BadFigureException("The vertexes are incompatible with"
                                         + " a rectangle.");
        }
    }

    // Calculates the area of the rectangle (base times the height)
    public double area() {
        Point[] vertices234 = new Point[] {getVertex2(), getVertex3(),
                                           getVertex4()};
        Point auxVertex = getVertex1().nearest(vertices234);
        Point[] otherVertices = null;
        if (auxVertex.equals(getVertex2())) {
            otherVertices = new Point[] {getVertex3(), getVertex4()};
        } else if (auxVertex.equals(getVertex3())) {
            otherVertices = new Point[] {getVertex2(), getVertex4()};
        } else if (auxVertex.equals(getVertex4())) {
            otherVertices = new Point[] {getVertex2(), getVertex3()};
        }
        double base = getVertex1().distance(auxVertex);
        double height = auxVertex.distance(auxVertex.nearest(otherVertices));
        return base * height;
    }

    // Rectangle to String
    public String toString() {
        return ("Rectangle: " + super.toString());
    }
}

  
import java.util.ArrayList;

public class FiguresSet {

    // Array of figures
    private ArrayList<Figure> figures;

    // Constructor of FiguresSet
    public FiguresSet() {
        figures = new ArrayList<Figure>();
    }

    // Calculates the total area of the figures
    public double totalArea() {
        double totalArea = 0;
        for (int i=0; i<figures.size(); i++){
                totalArea = totalArea + figures.get(i).area();
        }
        return totalArea;
    }

    // figures to String
    public String toString() {
        String s = new String();
        for (int i=0; i<figures.size(); i++){
                s = s + figures.get(i).toString() + "\n";
        }
        return s;
    }

    // Adds a new figure to the FigureSet
    public void addFigure(Figure f) {
        figures.add(f);
    }

    // Main program
    public static void main(String args[]) throws Exception {
        FiguresSet figures = new FiguresSet();
        Circle c = new Circle("Circle1", new Point(1, 1), 4);
        Triangle t = new Triangle("Triangle1", new Point(1, 1),
                                  new Point(3, 1), new Point(2, 3));
        figures.addFigure(c);
        figures.addFigure(t);
        try {
            Rectangle r = new Rectangle("Rectangle1", new Point(1, 4),
                                        new Point(4, 1), new Point(1, 1),
                                        new Point(4, 4));
            figures.addFigure(r);
        } catch (BadFigureException e) {
            System.out.println("Error: " + e.getMessage());
        }
        System.out.println(figures.toString());
        System.out.println("Total area: " + figures.totalArea());
    }
}

  

Homework Section2. Actividades para casa

Exercise Section2.1. Interfaces para calcular Pi

Una de las situaciones en que las interfaces resultan útiles en Java es aquella en que se dispone de varias implementaciones alternativas para proporcionar una misma funcionalidad. En ese caso, es deseable que el resto del programa pueda utilizar cualquiera de estas implementaciones de forma homogénea, sin importar de qué implementación concreta se trate.

En este ejercicio trabajaremos con una situación de este tipo: dispondremos de varias implementaciones distintas para calcular el número pi. Para ello, definimos la siguiente interfaz:

import java.math.BigDecimal;

/**
 * Interface to be implemented by classes that compute the number pi.
 *
 */
public interface PiProvider {

    /**
     * Computes and returns the value of the number pi. Implementations
     * may decide the precision with which they compute the value.
     *
     * @return The number pi, with the precision each implementation
     *         decides.
     *
     */
    BigDecimal computePi();
}

Dado que algunas implementaciones son capaces de calcular pi con la precisión que se les solicite, se define también la siguiente interfaz, que hereda de PiProvider y añade varios métodos nuevos que permiten gestionar la precisión del cálculo, entendida como el número de dígitos de pi:

import java.math.BigDecimal;

/**
 * Interface to be implemented by classes that compute the number pi.
 * Classes that implement this interface can provide the value of pi
 * with the requested precision, understood as the number of exact
 * digits of the computed value.
 *
 */
public interface AdvancedPiProvider extends PiProvider {

    /**
     * Sets the desired precision.
     *
     * @param precision The desired precision (number of digits).
     *
     * @throws PrecisionException if the desired precision is
     *         negative, zero or bigger than the maximum precision
     *         this class can provide.
     *
     */
    void setPrecision(int precision) throws PrecisionException;

    /**
     * Returns the current value of precision.
     *
     * @return The current value of precision (number of digits).
     *
     */
    int getPrecision();

    /**
     * Returns the maximum precision with which this provider is able
     * to generate the value of pi.
     *
     * @return The maximum precision available from this provider, or
     *         Integer.MAX_VALUE if the provider can provide an
     *         arbitrarily big precision.
     *
     */
    int getMaximumPrecision();
}

Como se puede observar, el método setPrecision puede lanzar una excepción si el valor de precisión no es válido (menor que uno o superior a la máxima precisión a la que la implementación concreta dé soporte. A continuación se muestra el código de esta excepción:

public class PrecisionException extends Exception {
    public PrecisionException(int precision) {
        super("Unsupported precision: " + precision);
    }
}

Apartado 1

Programa y prueba una clase llamada PiSimple que implemente la interfaz PiProvider y devuelva siempre el valor de pi como 3,14.

Soluciones
import java.math.BigDecimal;

public class PiSimple implements PiProvider {

    public BigDecimal computePi() {
        return new BigDecimal("3.14");
    }
}

Apartado 2

Programa y prueba una clase llamada PiFromMath que implemente la interfaz PiProvider y devuelva el valor Math.PI convertido a BigDecimal.

Soluciones
import java.math.BigDecimal;

public class PiFromMath implements PiProvider {

    public BigDecimal computePi() {
        return new BigDecimal(Math.PI);
    }
}

Apartado 3

Programa y prueba una clase llamada PiFromBBP que implemente la interfaz AdvancedPiProvider, basándote en el código de la clase PiCalc vista en una práctica previa. Sólo tendrás que hacer algunas pequeñas adaptaciones a este código para que implemente la interfaz:

import java.math.BigDecimal;
import java.math.MathContext;

public class PiCalc {

    private int numDigits;
    private MathContext mc;

    public PiCalc(int numDigits) {
        this.numDigits = numDigits;
        mc = new MathContext(numDigits);
    }

    public BigDecimal compute() {
        BigDecimal pi = new BigDecimal(0);
        BigDecimal limit = new BigDecimal(1).movePointLeft(numDigits);
        boolean stop = false;
        for (int k = 0; !stop; k++) {
            BigDecimal piK = piFunction(k);
            pi = pi.add(piK);
            if (piK.compareTo(limit) < 0) {
                stop = true;
            }
        }
        return pi.round(mc);
    }

    private BigDecimal piFunction(int k) {
        int k8 = 8 * k;
        BigDecimal val1 = new BigDecimal(4);
        val1 = val1.divide(new BigDecimal(k8 + 1), mc);
        BigDecimal val2 = new BigDecimal(-2);
        val2 = val2.divide(new BigDecimal(k8 + 4), mc);
        BigDecimal val3 = new BigDecimal(-1);
        val3 = val3.divide(new BigDecimal(k8 + 5), mc);
        BigDecimal val4 = new BigDecimal(-1);
        val4 = val4.divide(new BigDecimal(k8 + 6), mc);
        BigDecimal val = val1;
        val = val.add(val2);
        val = val.add(val3);
        val = val.add(val4);
        BigDecimal multiplier = new BigDecimal(16);
        multiplier = multiplier.pow(k);
        BigDecimal one = new BigDecimal(1);
        multiplier = one.divide(multiplier, mc);
        val = val.multiply(multiplier);
        return val;
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("One command-line argument expected: number of "
                               + "digits.");
        } else {
            PiCalc piCalc = new PiCalc(Integer.parseInt(args[0]));
            System.out.println(piCalc.compute());
        }
    }
}

Al contrario que en el código de partida, el constructor de la nueva clase no debe recibir parámetros. Establecerá un valor por defecto de precisión de 30 dígitos.

Soluciones
import java.math.BigDecimal;
import java.math.MathContext;

public class PiFromBBP implements AdvancedPiProvider {

    private int numDigits;
    private MathContext mc;

    public PiFromBBP() {
        setPrecisionInternal(30);
    }

    public void setPrecision(int numDigits) throws PrecisionException {
        if (numDigits < 1) {
            throw new PrecisionException(numDigits);
        }
        setPrecisionInternal(numDigits);
    }

    public int getPrecision() {
        return numDigits;
    }

    public int getMaximumPrecision() {
        return Integer.MAX_VALUE;
    }

    public BigDecimal computePi() {
        BigDecimal pi = new BigDecimal(0);
        BigDecimal limit = new BigDecimal(1).movePointLeft(numDigits);
        boolean stop = false;
        for (int k = 0; !stop; k++) {
            BigDecimal piK = piFunction(k);
            pi = pi.add(piK);
            if (piK.compareTo(limit) < 0) {
                stop = true;
            }
        }
        return pi.round(mc);
    }

    private void setPrecisionInternal(int numDigits) {
        this.numDigits = numDigits;
        mc = new MathContext(numDigits);
    }

    private BigDecimal piFunction(int k) {
        int k8 = 8 * k;
        BigDecimal val1 = new BigDecimal(4);
        val1 = val1.divide(new BigDecimal(k8 + 1), mc);
        BigDecimal val2 = new BigDecimal(-2);
        val2 = val2.divide(new BigDecimal(k8 + 4), mc);
        BigDecimal val3 = new BigDecimal(-1);
        val3 = val3.divide(new BigDecimal(k8 + 5), mc);
        BigDecimal val4 = new BigDecimal(-1);
        val4 = val4.divide(new BigDecimal(k8 + 6), mc);
        BigDecimal val = val1;
        val = val.add(val2);
        val = val.add(val3);
        val = val.add(val4);
        BigDecimal multiplier = new BigDecimal(16);
        multiplier = multiplier.pow(k);
        BigDecimal one = new BigDecimal(1);
        multiplier = one.divide(multiplier, mc);
        val = val.multiply(multiplier);
        return val;
    }
}

Apartado 4

Programa y prueba una clase llamada PiStored que almacene un valor precalculado de pi como cadena de texto. Cuando se le solicite computar el valor, simplemente devolverá la subcadena pertinente (según la precisión que se haya establecido). La precisión máxima vendrá determinada por el número de caracteres de la cadena de texto que almacenes internamente.

El constructor no debe recibir parámetros. Establecerá un valor por defecto de precisión de 30 dígitos.

Puedes declarar el valor de pi con la siguiente cadena:

private static final String PI = "3.14159265358979323846264338327950"
                                 + "2884197169399375105820974944592307"
                                 + "8164062862089986280348253421170679";

Para quedarte con un trozo de la cadena, puedes utilizar el método substring de la clase String.

Soluciones
import java.math.BigDecimal;

public class PiStored implements AdvancedPiProvider {

    private int numDigits;
    private static final String PI = "3.14159265358979323846264338327950"
                                     + "2884197169399375105820974944592307"
                                     + "8164062862089986280348253421170679";

    public PiStored() {
        setPrecisionInternal(getMaximumPrecision());
    }

    public void setPrecision(int numDigits) throws PrecisionException {
        if (numDigits < 1 || numDigits > getMaximumPrecision()) {
            throw new PrecisionException(numDigits);
        }
        setPrecisionInternal(numDigits);
    }

    public int getPrecision() {
        return numDigits;
    }

    public int getMaximumPrecision() {
        return PI.length() - 1;
    }

    public BigDecimal computePi() {
        int length;
        if (numDigits == 1) {
            length = 1;
        } else {
            length = 1 + numDigits;
        }
        BigDecimal pi = new BigDecimal(PI.substring(0, length));
        return pi;
    }

    private void setPrecisionInternal(int numDigits) {
        this.numDigits = numDigits;
    }
}

Apartado 5

Programa y prueba una clase llamada Circle que:

  • Almacena internamente su radio, de tipo BigDecimal.
  • Proporciona un constructor que recibe el valor del radio.
  • Proporciona un método llamado area que recibe un objeto que implemente la interfaz PiProvider y devuelve al área del círculo calculada mediante el valor de pi que proporcione dicho objeto.

Utiliza adecuadamente el polimorfismo para que esta clase sea capaz de utilizar cualquier implementación de PiProvider, previendo que en el futuro podrían crearse nuevas implementaciones distintas a las que ya has desarrollado. Prueba el código utilizando instancias de cada una de las implementaciones que has hecho.

Soluciones
import java.math.BigDecimal;

public class Circle {
    private BigDecimal radius;

    public Circle(BigDecimal radius) {
        this.radius = radius;
    }

    public BigDecimal area(PiProvider piProvider) {
        return radius.multiply(radius).multiply(piProvider.computePi());
    }
}
import java.math.BigDecimal;

public class TestPi {

    public static void main(String[] args) throws PrecisionException {
        PiProvider piSimple = new PiSimple();
        System.out.println("Pi simple:\t"
                           + piSimple.computePi());

        PiProvider piFromMath = new PiFromMath();
        System.out.println("Pi from math:\t" + piFromMath.computePi());

        AdvancedPiProvider piFromBBP = new PiFromBBP();
        piFromBBP.setPrecision(40);
        if (piFromBBP.getPrecision() != 40) {
            System.out.println("Wrong precision in PiFromBBP");
        }
        System.out.println("Pi from BBP:\t" + piFromBBP.computePi());

        AdvancedPiProvider piStored = new PiStored();
        piStored.setPrecision(40);
        if (piStored.getPrecision() != 40) {
            System.out.println("Wrong precision in PiStored");
        }
        System.out.println("Pi stored:\t" + piStored.computePi());

        Circle circle = new Circle(new BigDecimal(2));
        System.out.println("\nArea simple:\t" + circle.area(piSimple));
        System.out.println("Area from math:\t" + circle.area(piFromMath));
        System.out.println("Area from BBP:\t" + circle.area(piFromBBP));
        System.out.println("Area stored:\t" + circle.area(piStored));
    }
}