Table of Contents
The general objective of this lab is to recap inheritance and
polymorphism in Java. You will be using abstract
and
extends
for inheritance, creating generic and especific
classes in a hierarchy. Especific classes will implement the
abstract methods defined in the generic ones and will override the
rest of their methods to implement polimorphism. The class hierarchy
in this lab is composed by the superclass Figure
and
three of its clindrens: Circle
, Triangle
and Quadrilateral
. There is also a subclass of
Quadrilateral
called Rectangle
.
Once you have implemented all that classes, you will have to create a bunch of objects and insert them into a container. Then you will perform operations on all of them taking advantage of polimorphism.
The Figure
class represents a generic figure, that
will later be particularized into a more concrete class like
Circle
, Triangle
or
Rectangle
. In Java, this kind of generic, abstract
classes, are defined using the abstract
reserved
word.
In this first exercise you must define the Figure
abstract class. You can download an initial templete for this
class from here
.
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) { } }
Figure
.
Program the constructor of the class Figure
and
its get and set methods.
Circle
.
The class Circle
inherits from Figure
,
take this into account when implementing it. Follow these steps:
Declare the class Circle
inheriting from
Figure
, using extends
.
Implement its constructor. Said constructor must accept
three arguments: its name (String
), its center
(Point
) and its radius (int
). Use
the class Point.java
to represent points, and call the constructor of the
Figure
class through the super
reference on the Circle
's constructor.
Program the method area
that returns the area of
the circle. Use the constant Math.PI
in your calculations.
Program the method isRegular()
that returns
true
if the figure is regular or
false
if it is not. Please note that circles
are always regular figures.
Override the method toString()
to return a
textual representation of the name of the figure, its center
and its radius (the format of this textual representation is
up to you).
Program the get and set methods.
Triangle
.
The class Triangle
also inherits from
Figure
. Follow these steps:
Declare the Triangle
class to inherit from
Figure
.
The constructor will receive four arguments: the name of the
class and three points (Point v1
, Point
v2
and Point v3
), representing the three
vertexes of the triangle. Remember you have to call the
constructor of Figure
using super
.
Program its area
method. The area of a triangle
can be calculated form its vertexes usign the following
expresion: area =
0.5*|Ax(By-Cy)+Bx(Cy-Ay)+Cx(Ay-By)|
, where
x
is the abscissa and y
the
ordinate of the correspoding vertexes (A, B, C). In the
expression, |...|
represents the absolute value
of what is inside.
Program the method isRegular()
to tell if the
triangle is regular. A triangle is regular if all its side
lenghts are equal.
Override the toString
metod to return a
sensible textual representation of a triangle, including at
least its name and its vertexes.
Program the get and set methods.
Quadrilateral
.
The Quadrilateral
is another subclass of
Figure
. But this time, we will make it also an
abstract class, because it is difficult to calculate its area
without knowing a little bit more about its details. Follow
these steps:
Declare an abstract class Quadrilateral
that
inherits from Figure
.
Implement its constructor, which receives five arguments: its name and four points.
Program the isRegular
method to tell if the
figure is regular. Please note that a quadrilateral is
regular if it is also a rectangle. Use the method
checkRectangle
at the end of this section to
help you program the isRegular
method. How does
the checkRectangle
method works?
Override the toString
method to return a
sensible textual representation of a quadrilateral,
including its name and its vertexes.
Program the get and set methods.
/** * Indicates if the quadrilateral is a rectangle or square by * comparing sizes and diagonals. * */ 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); } else if (auxVertex.equals(v3)){ return v1.distance(v2) == v3.distance(v4) && v1.distance(v4) == v3.distance(v2); } else if (auxVertex.equals(v4)){ return v1.distance(v2) == v4.distance(v3) && v1.distance(v3) == v4.distance(v2); } else { return false; } }
Rectangle
.The subclass Rectangle
inherits from
Quadrilateral
. Follow these steps:
Declare the class Rectangle
that inherits from
Quadrilateral
.
Program its constructor, receiving 5 arguments, its name and
its 4 vertexes. Remember to call the constructor of the
Quadrilateral
class through the
super
reference. The constructor must check
that the four vertexes provided as its arguments are really
the vertexes of a rectangle, not just of a generic
quadrilateral. The constructor must throw an exception in
case the vertexes are wrong (you can read about exceptions
in this short
introduction to exceptions).
Program its area
method. In a rectangle, given
a vertex, the distance to its two nearest vertexex, would be
the base and height of the rectangle.
Override the toString
method to return a
sensible textual representation of a rectangle, including
its name and its vertexes. Can you reuse some code from the
Quadrilateral.toString
method in here?
The class FiguresSet
will contain several figures,
stored in an ArrayList<Figure>
and will be
able to tell the total area of the figures it holds. Here is a
templete for this class:
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 { } }
The class constructor must create an object of the
ArrayList
class to store the figures. Then the
user will be adding figures using the addFigure
method. The method totalArea
can then be used to
get the total area of all the figures that have been added. The
toString
method retuns a sensible textual
representation of all the figures included in the
FigureSet
.
Implement the class constructor.
Write the addFigure
method that allows to add
an Figure
to the container.
Write the totalArea
method that allows to
calculate the total area of the figures contained.
Write the toString
method that returns a
sensible textual representation of all the figures inside.
main
method.Create the main program that has to carry out the following tasks:
Create an object of the FiguresSet
class.
Create a circle.
Create a triangle.
Create a rectangle.
Add the circle, the triangle and the rectangle to the FiguresSet
object.
Print the result of the calculation of the total area of the figures along with the information about the figures.
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. * */ 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); } else if (auxVertex.equals(v3)){ return v1.distance(v2) == v3.distance(v4) && v1.distance(v4) == v3.distance(v2); } else if (auxVertex.equals(v4)){ return v1.distance(v2) == v4.distance(v3) && v1.distance(v3) == v4.distance(v2); } else { return false; } } // 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 (super.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()); } }
Interfaces can be very useful for having several alternative implementations of the same functionality: we would like the main program to use one or the other without noticing the changes.
In this exercise we will have several implementations of a class that calculates the number pi. We will be using the following interface:
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(); }
As some implementations will be able to calculate pi with a desired
precision, we will define also the following interface, that
inherits from PiProvider
and add some more methods to
deal with precisions.
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(); }
As you can see, the method setPrecision
will throw an
exception if the desired precision is not a valid precision value
(less than 1 or greater than the maximum precision allowed by each
particular implementation). Here is the code of this exception:
public class PrecisionException extends Exception { public PrecisionException(int precision) { super("Unsupported precision: " + precision); } }
Program and test a class called PiSimple
that
implements the PiProvider
interface and returns the
value of pi as (simply) 3.14.
import java.math.BigDecimal; public class PiSimple implements PiProvider { public BigDecimal computePi() { return new BigDecimal("3.14"); } }
Program and test a class called PiFromMath
that
implements the PiProvider
interface and returns the
value of pi defined by Math.PI
as a
BigDecimal
.
import java.math.BigDecimal; public class PiFromMath implements PiProvider { public BigDecimal computePi() { return new BigDecimal(Math.PI); } }
Program and test a class called PiFromBBP
that
implements the AdvancedPiProvider
interface, using
the code from the PiCalc
class you wrote in a
previous lab. You will only need to make a few changes to adapt
it.
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()); } } }
Please note that the new constructor will not receive any arguments, as the default desired precision will be 30 decimal places.
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; } }
Program and test a class called PiStored
that stores
a precalculated value of pi as an String
. When asked
for a new value of pi, it will return the corresponding stored
value (using the desired precision). The maximum precision will be
limited by the size of the stored string.
Its constructor must not receive any parameters. The default precision will be 30 decimal places.
You can use the following string as the value of pi:
private static final String PI = "3.14159265358979323846264338327950" + "2884197169399375105820974944592307" + "8164062862089986280348253421170679";
To copy a piece of the string, you can use the method substring
from the class
String
.
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; } }
Program and test a class called Circle
that:
BigDecimal
.
area
that receives an object
implementing the PiProvider
interface and returns
the area of the circle calculated using the value of pi supplied
by that object.
You must carefully make use of polymorphism so this class can use
any implementation of the PiProvider
interface. This
means also to foresee new implementations, different from the ones
you made yourself. Test your code using different instances of
each of your interface implementations.
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)); } }