我有一个名为Shape的超类:
abstract class Shape {
private String color;
public Shape(String color) {
this.color=color;
}
public String getColor(){return color;}
abstract double Area();
}
和两个继承类:
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;
}
}
现在我创建了一个对象或形状(矩形和圆形)数组。我遇到的问题是当我想迭代这个数组的元素并打印它们的属性时。我想做这样的事情:
for (int i=0;i<lengthShapes;i++) {
System.out.println(shapes[i].getB()+shapes[i].getH()+shapes[i].getR());
}
我的意思是我怎么做才能识别出第i个位置的对象是一个Rectangle或Circle来打印它的属性,请记住我只有一个Shapes数组。我想我可以用接口来做,但是如何只使用抽象类来做。那可能吗? 感谢
答案 0 :(得分:2)
我是指如何识别第i个位置的对象是矩形还是圆形......
最简单的方法是使用instanceof
运算符。
e.g。
if(shapes[i] instanceof Rectangle) {
Rectangle rect = (Rectangle) shapes[i];
// ...
}
这不是一个好习惯(这些都是你自己的类,但你仍然需要检查对象的类型(在运行时)并使用显式转换)。
但如果你想要做的就是打印属性,那么你可以让两个子类正确地覆盖toString()
,然后就可以了 -
System.out.println(shapes[i]);
答案 1 :(得分:0)
您可以从班级Class
获取班级标识符信息。例如,要将当前类的名称设为String
,您可以使用方法getCanonicalName
:
System.out.println(shapes[i].getClass().getCanonicalName());
您是否考虑让Shape
类声明一个getAttributes
方法,该方法可以返回一个HashMap,其属性名称作为访问相应值的键?圆圈将具有“半径”键,矩形将具有“基本”和“高度”键,并且两者都具有“区域”和“颜色”键。
答案 2 :(得分:0)
通常,当您迭代异构集合的项目时(在这种情况下,同时包含Rectangle
和Circle
),您有两种选择:
Shape
)。instanceof
检查以不同方式处理每个子类型。Shape
通过这种设计,每个Shape
都有助于了解如何打印自己的属性。这使用了选择1 - 循环代码永远不需要知道它是Rectangle
还是Circle
。
要Shape
,您需要添加
abstract String getAttributesString();
Rectangle
将此实现为
@Override
String getAttributesString() {
return String.format("Rectangle {b=%f, h=%f}", b, h);
}
然后循环
for (Shape shape : shapes) {
System.out.println(shape.getAttributesString());
}
您还可以覆盖toString()
上的Object
方法,但通常最好只使用toString
进行调试,而不是例如需要显示的内容一个用户。类通常应该有toString
覆盖,用于打印实例数据的完整表示。
这是选择2.更改仅适用于循环代码 - 根本不需要修改形状。
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()));
}
}
这是两种选择的混合。
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 ... */
}
然后循环看起来像:
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()));
}
});
}
设计A很好,因为它避免了instanceof
和转换,但缺点是你必须将打印逻辑放在形状类本身,这会带来一些灵活性(如果你想要打印相同的话)在两种不同的情况下,形状列表不同?)。
设计B将打印逻辑放在您想要的位置,但是通过转换,您无法充分利用编译器的类型检查。这很容易出错,因为如果你要添加另一个Shape
子类型,你可能会忘记更新循环代码。
设计C类结合了A和B的最佳功能。但问题是它不可扩展。如果您正在编写其他人将用于定义自己的Shape
子类型的库,则他们无法执行此操作,因为它需要修改ShapeVisitor
接口。但是,如果您的代码不需要以这种方式扩展,那么它是合适的。
最终,Scala中的所有这些都更容易。 (是的,我意识到我不再回答你的问题了,现在就开心吧。)
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)
})
}