Search code examples
javainheritancemulti-level

type casting not working on inherited class


I have the following classes:

Shape Rectangle Square

Shape is the parent class , Rectangle extends Shape and Square extends Rectangle. I use StringBuilder and the following code:

for (int i = 0; i < 2; i++){
    x = Math.random() * 100;
    y = Math.random() * 100;

                  
    if (i % 2 == 0){
        width = Math.random() * 100;
        height = Math.random() * 100;
        Rectangle z = new Rectangle(x, y, height, width);
        list.add(z);
    }else if (i % 2 == 1){
        side = Math.random() * 100;
        Square z = new Square(x, y, side);
        list.add(z);
    }
}

for(Shape s : list){
   System.out.println(s.getStr0());
   System.out.println(s.getStr1());
}

Will print out the following:

Rectangle with height: 91.19 and width: 73.70, Centered at X: 14.21 Y: 81.81
Has area: 6720.81
Has perimeter: 329.78

Square with side: 66.42, Centered at X: 36.06 Y: 95.86
Has area: 4411.18
Has perimeter: 265.67

Is there a way for me to type cast or convert my square to a rectangle so I get the following:

Rectangle with height: 91.19 and width: 73.70, Centered at X: 14.21 Y: 81.81
Has area: 6720.81
Has perimeter: 329.78

Rectangle with height: 66.42 and width 66.42, Centered at X: 36.06 Y: 95.86
Has area: 4411.18
Has perimeter: 265.67

Here's the complete code:


import java.util.ArrayList;
public class Shape{
    private double x;
    private double y;
    private StringBuilder str = new StringBuilder();
    private StringBuilder str0 = new StringBuilder();
    private StringBuilder str1 = new StringBuilder();
    private StringBuilder str2 = new StringBuilder();

    public Shape(double x, double y){
        this.x = x;
        this.y = y;  
        String x1 = String.format("%.2f",this.x);
        String y1= String.format("%.2f",this.y);
        getStr().append("Centered at X: " + x1 + " Y: " + y1 );
    }

    public Shape(){
    }

    public static void main(String[] args){
        System.out.println('\u000C');

        ArrayList<Shape> list = new ArrayList<Shape>();
        double x;
        double y;        
        double height;
        double width;
        double side;

        for (int i = 0; i < 2; i++){
            x = Math.random() * 100;
            y = Math.random() * 100;

            if (i % 2 == 0){
                width = Math.random() * 100;
                height = Math.random() * 100;
                Rectangle z = new Rectangle(x, y, height, width);
                list.add(z);
            }else if (i % 2 == 1{
                side = Math.random() * 100;
                Square z = new Square(x, y, side);
                list.add(z);
            }
        }

        for(Shape s : list){
            System.out.println(s.getStr0());
            System.out.println(s.getStr1());
        }
    }
    public String toString(){
        String x = String.format("%.2f",this.x);
        String y = String.format("%.2f",this.y);
                
        return "Centered at X: " + x + " Y: " + y ;
    }
    public double calculateArea(){
        return -999.99;
    }
    public double calculateCircumference(){
        return -999.99;
    }
    public double calculatePerimeter(){
        return -999.99;
    }
    public double getX(){
        return this.x;
    } 
    public void setX(double x){
        this.x = x;
    }
    public double getY(){
        return this.y;
    }
    public void setY(double y){
        this.y = y;
    }
    public StringBuilder getStr(){
        return this.str;
    } 
    public void setStr(StringBuilder str){
        this.str = str;
    }
    public StringBuilder getStr0(){
        return this.str0;
    }
    public void setStr0(StringBuilder str0){
        this.str0 = str0;
    } 
    public StringBuilder getStr1(){
        return this.str1;
    } 
    public void setStr1(StringBuilder str1){
        this.str1 = str1;
    }    
}

public class Rectangle extends Shape{
    private double height;
    private double width;
    
    public Rectangle(double x, double y, double height, double width){
        super(x, y);
        this.height = height;
        this.width = width;
        
        String area = String.format("%.2f",this.calculateArea());
        String perimeter = String.format("%.2f",this.calculatePerimeter());
        
        
        
        getStr0().append(toString());        
        getStr1().append("Has area: " + area + "\n");
        getStr1().append("Has perimeter: " + perimeter + "\n");
        
    }
    public Rectangle(){
        
    }
    public double calculateArea(){
        return height * width;
    }
    public double calculatePerimeter(){
        return 2 * height + 2 * width;
    }
    public String toString(){
        
        String height = String.format("%.2f",this.height);
        String width = String.format("%.2f",this.width);        
        return "Rectangle with height: " + height + " and width: " + width +
        ", " + super.toString();
    }
}

public class Square extends Rectangle{
    double side;
    StringBuilder squarestr = new StringBuilder();

    public Square(double x, double y, double side){
        super(x, y, side, side);
        this.side = side;

        String area = String.format("%.2f",this.calculateArea());
        String perimeter = String.format("%.2f",this.calculatePerimeter());

        getStr0().delete(0,getStr0().length() );
        

        getStr1().delete(0,getStr1().length() );

        getStr0().append(toString());
        

        getStr1().append("Has area: " + area + "\n");
        getStr1().append("Has perimeter: " + perimeter + "\n");
        
        
    }

    public String toString(){

        String side = String.format("%.2f",this.side);

        return "Square with side: " + side + ", " + getStr() ;
    }

    public double calculateArea(){
        return side * side;
    }

    public double calculatePerimeter(){
        return 4 * side;
    }
}


Solution

  • No.

    "Cast" is a term resolved for only one thing, which is a syntactical construct. Anything in java source code that looks like: (SomeType) someExpression. The reason you should not be using that term for anything except that (and that should come up very rarely in discussion), is that the cast operator is used for 3 completely and utterly unrelated things, but you might, if you're not too familiar with java, confuse the 3. Which you appear to be doing. Best to avoid that confusion by using clearer terminology.

    So how different are these 3? Completely. Before you think that doesn't make sense, remember:

    String a = "Hello";
    String b = ", world!";
    String c = a + b;
    
    int x = 5;
    int y = 10;
    int z = x + y;
    

    Uses the exact same operator (+) for 2 utterly unrelated things: String concatenation, and numeric addition.

    The same thing applies to the cast operator.

    Conversion

    The only way a cast operation converts anything at all is if the type in the parens is a primitive. That is all. You have asked: "Can I convert a square to a rectangle or otherwise make that square somehow use rectangle's toString by involving a cast somehow" and the answer is an emphatic: No, because the cast operator cannot convert anything, unless you are casting one primitive to another. int x = (int) someDouble; is an actual conversion, but only the 8 primitives can do that (and in practice, boolean can't do the job either.

    Type check

    When the type in the parens is not a primitive, the cast operator typechecks. Given:

    Object o = someExpression;
    String x = (String) o;
    

    That cast operator injects the following code in your class file:

    1. Resolve the expression. This involves following the o pointer (in java, all non-primitives are pointers, in java terminology, 'reference'; same thing, though), and check the object you find there. Sure, that is at least an Object but it is likely to be something more specific than that. Is it a String or some subtype of that?
    2. If yes, DO NOTHING. Nothing is converted. The type of that expression (the whole (String) o node) is String so this will let you e.g. invoke .toLowerCase() on the result. That's not because this cast operation magically makes that o gain a toLowerCase() function. Nono; o is pointing at a String and all strings have a toLowerCase() function. javac merely wouldn't let you call it, because it didn't know that the object it finds when following o is, indeed, a String. WITH the cast, it does, because...
    3. If the answer is no, throw a ClassCastException. Again, no conversion happens.

    The other things casts do is a type assertion - that's where you decree an expression of some type, and this generates ZERO bytecode. You're just telling the compiler. This is what you get when you stick generics (the <> stuff) in there:

    List<Object> o = new ArrayList<Object>();
    List raw = o;
    List<String> uhoh = (List<String>) raw;
    o.add(5);
    String x = uhoh.get(0);
    

    The above code compiles, but you do get a warning. Proving that this injects no code whatsoever (the List is already guaranteed, and the <String> part, that's generics, so is a type assertion, i.e. the compiler just takes your word for it). This code will throw a ClassCastException on the last line if you run it which is bizarre given that there isn't a cast on there. Generally, this usage of the cast operator should only be used as a last resort, and always generates a warning because it is unsafe.