Reputation: 3477
I have a superclass called Shape:
abstract class Shape {
private String color;
public Shape(String color) {
this.color=color;
}
public String getColor(){return color;}
abstract double Area();
}
and two inheriting classes:
class Rectangle extends Shape {
private double b,h;
public Rectangle(double b,double h, String color) {
super(color);
this.b=b;
this.h=h;
}
public double getB() { return b;}
public double getH() { return h;}
public double Area() {
return b*h;
}
}
class Circle extends Shape {
private double r;
public Circle(double r,String color) {
super(color);
this.r=r;
}
public double getR() {
return r;
}
public double Area() {
return 3.14*r*r;
}
}
Now I have created an array of objects or Shapes (rectangles and circles). The problem that I have is when I want to iterate over the elements of this array and print their atributes. I want to do something like this:
for (int i=0;i<lengthShapes;i++) {
System.out.println(shapes[i].getB()+shapes[i].getH()+shapes[i].getR());
}
I mean how I can do to recognize that the object in the ith position is a Rectangle or a Circle for printing its atributes, keep in mind that I only have an array of Shapes. I think I can do it with interfaces, but how to do it only using abstract classes. Is that possible? Thanks
Upvotes: 0
Views: 289
Reputation: 30736
In general, when you're iterating over the items of a heterogeneous collection (in this case, containing both Rectangle
s and Circle
s), you have two choices:
Shape
).instanceof
checks to handle each subtype differently.Shape
With this design, every Shape
is reponsible for knowing how to print its own attributes. This uses choice 1 - the looping code never needs to know whether it has a Rectangle
or a Circle
.
To Shape
, you'd add
abstract String getAttributesString();
Rectangle
implements this as
@Override
String getAttributesString() {
return String.format("Rectangle {b=%f, h=%f}", b, h);
}
Then the loop is
for (Shape shape : shapes) {
System.out.println(shape.getAttributesString());
}
You could also override the toString()
method on Object
, but it's usually best to only use toString
for debugging, rather than for something that, for example, needs to be shown to a user. Classes should generally have a toString
override that prints a full representation of the instance's data.
This is choice 2. The change is only to the loop code - It does not require modifying the shapes at all.
for (Shape shape : shapes) {
if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
}
This is sort of a mix of the two choices.
interface ShapeVisitor {
void visitRectangle(Rectangle rectangle);
void visitCircle(Circle circle);
}
abstract class Shape {
void visit(ShapeVisitor visitor);
/* ... the rest of your code ... */
}
class Rectangle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitRectangle(this);
}
/* ... the rest of your code ... */
}
class Circle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitCircle(this);
}
/* ... the rest of your code ... */
}
Then the loop looks like:
for (Shape shape : shapes) {
shape.visit(new ShapeVisitor() {
@Override
void visitRectangle(Rectangle rectangle) {
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
}
@Override
void visitCircle(Circle circle) {
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
});
}
Design A was nice because it avoids instanceof
and casting, but the drawback was that you had to put the printing logic in the shape classes themselves, which takes away some flexibility (what if you wanted to print the same list of shapes differently in two different situations?).
Design B put the printing logic where you wanted it, but with casting you don't get to take full advantage of the compiler's type checking. It's error-prone because if you were to, for instance, add another Shape
subtype, you could forget to update the loop code.
Design C sort of combines the best features of A and B. The problem, though, is that it isn't extensible. If you're writing a library that other people are going to use to define their own Shape
subtypes, they can't do that because it would require modifying the ShapeVisitor
interface. But it is suitable if your code doesn't need to be extensible in that way.
Ultimately, all of this is way easier in Scala. (yes, I realize I am no longer answering your question, and just having fun now.)
sealed trait Shape {
def color: String
def area: Double
}
case class Rectangle(b: Double, h: Double, color: String) extends Shape {
def area: Double = b*h
}
case class Circle(r: Double, color: String) extends Shape {
def area: Double = math.Pi*r*r
}
for (shape <- shapes) {
println(shape match {
case rectangle: Rectangle =>
"Rectangle {b=%f, h=%f}".format(rectangle.b, rectangle.h)
case circle: Circle =>
"Circle {r=%f}".format(circle.r)
})
}
Upvotes: 0
Reputation: 19855
You can obtain the class identifier info from class Class
. For instance, to get the current class's name as a String
you can use the method getCanonicalName
:
System.out.println(shapes[i].getClass().getCanonicalName());
Have you considered having your Shape
class declare a getAttributes
method which could return a HashMap with attribute names as keys to access the corresponding values? Circles would have "radius" keys, Rectangles would have "base" and "height" keys, and both would have "area" and "color" keys.
Upvotes: 0
Reputation: 51030
I mean how I can do to recognize that the object in the ith position is a Rectangle or a Circle ...
The easiest way to do that is by using the instanceof
operator.
e.g.
if(shapes[i] instanceof Rectangle) {
Rectangle rect = (Rectangle) shapes[i];
// ...
}
Which is not considered a good practice (these are all your own class but still you will have to check the type of the object (at runtime) and use explicit casting).
But if all you want to do is print the attributes, then you can just let both the subclasses override the toString()
properly and then you can just do -
System.out.println(shapes[i]);
Upvotes: 2