Understanding Class Hierarchies
Class hierarchies in Java enable structured relationships between classes through inheritance. This concept is foundational to object-oriented programming and enhances code reusability and organization, which simplifies managing complex systems.
In a class hierarchy, a superclass can pass down properties and methods to its subclasses. This allows subclasses to inherit functionality while also being able to define or modify behaviors that are specific to themselves. This leads to a more manageable code structure and helps in maintaining code consistency.
Components of Class Hierarchies
- Superclass: The parent class from which properties and methods are inherited.
- Subclass: A child class that inherits from the superclass and can override or extend its functionality.
- Polymorphism: The ability to treat objects of different classes through a common interface, allowing for method overriding.
Example Hierarchy
// public class Shape {
// protected String name;
// protected
// public Shape(String name) {
// this.name = name;
// }
// public double calc_area() {
// return 0.0;
// }
// public void print_shape() {
// System.out.println("Shape: " + name);
// }
// }
public class Shape {
protected String name;
public Shape(String name) {
this.name = name;
}
public double calc_area() {
return 0.0; // Default implementation
}
public void print_shape() {
System.out.println("Shape: " + name);
}
public String draw() {
return "Drawing a shape";
}
}
Derived Class: Rectangle
Now, let’s create a Rectangle
class that inherits from Shape
.
// public class Rectangle extends Shape {
// public int length;
// public int width;
// public Rectangle(String name, int length, int width) {
// super(name);
// this.length = length;
// this.width = width;
// }
// }
// class Circle extends Shape {
// @Override
// public double calc_area() {
// return length * width;
// }
// }
public class Rectangle extends Shape {
public int length;
public int width;
public Rectangle(String name, int length, int width) {
super(name);
this.length = length;
this.width = width;
}
@Override
public double calc_area() {
return length * width;
}
@Override
public String draw() {
return "Drawing a rectangle";
}
}
public class Circle extends Shape {
public int radius;
public Circle(String name, int radius) {
super(name);
this.radius = radius;
}
@Override
public double calc_area() {
return Math.PI * radius * radius;
}
@Override
public String draw() {
return "Drawing a circle";
}
}
Derived Class: Triangle
Next, let’s create a Triangle
class that also inherits from Shape
.
// public class Triangle extends Shape {
// private int side1;
// private int side2;
// private int side3;
// public Triangle(String name, int s1, int s2, int s3) {
// super(name);
// this.side1 = s1;
// this.side2 = s2;
// this.side3 = s3;
// }
// }
// class Square extends Shape {
// @Override
// public String draw() {
// return "Drawing a square";
// }
// }
public class Triangle extends Shape {
private int side1;
private int side2;
private int side3;
public Triangle(String name, int s1, int s2, int s3) {
super(name);
this.side1 = s1;
this.side2 = s2;
this.side3 = s3;
}
@Override
public double calc_area() {
// Using Heron's formula to calculate the area of the triangle
double s = (side1 + side2 + side3) / 2.0; // Semi-perimeter
return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
}
@Override
public void print_shape() {
super.print_shape(); // Call to the Shape's print_shape method
System.out.println("Area: " + calc_area());
}
@Override
public String draw() {
return "Drawing a triangle";
}
}
public class Square extends Shape {
private int sideLength;
public Square(String name, int sideLength) {
super(name);
this.sideLength = sideLength;
}
@Override
public double calc_area() {
return sideLength * sideLength; // Area of the square
}
@Override
public void print_shape() {
super.print_shape(); // Call to the Shape's print_shape method
System.out.println("Area: " + calc_area());
}
@Override
public String draw() {
return "Drawing a square";
}
}
Testing Our Classes
Let’s create instances of Rectangle
and Triangle
and call their methods.
public class Main {
public static void main(String[] args) {
Shape myCircle = new Circle("Circle", 5);
Shape mySquare = new Square("Square", 4);
System.out.println(myCircle.draw()); // Outputs: Drawing a circle
System.out.println(mySquare.draw()); // Outputs: Drawing a square
}
}
Main.main(null);
Drawing a circle
Drawing a square
In the example above, Shape
acts as the superclass, while Circle
and Square
are subclasses that override the draw
method to provide specific behavior. This demonstrates polymorphism, enabling us to reference subclasses using a superclass type and invoke the overridden methods dynamically at runtime. This is particularly powerful as it allows for flexibility in the code, facilitating easier changes and enhancements.
Benefits of Using Class Hierarchies
- Code Reusability: Common behaviors are defined in the superclass, significantly reducing code duplication and making it easier to maintain and update.
- Organized Structure: A clear hierarchical structure in your code aids in understanding the relationships between classes, making the codebase more intuitive and manageable.
- Polymorphism: Allows for dynamic method resolution, enabling methods to be invoked on objects of subclasses through references of the superclass type.
Practical Exercise: Popcorn Hack 1
Let’s implement the Triangle
subclass to deepen your understanding. Below is a half-completed method for the Triangle
class. Your task is to complete the draw
method:
```java class Shape {
public String draw() {
return "Drawing a shape";
} }
class Triangle extends Shape {
@Override
public String draw() {
// TODO: Implement this method
return "Drawing a triangle";
}
}
public class Main {
public static void main(String[] args) {
Shape myTriangle = new Triangle();
System.out.println(myTriangle.draw()); // Should output: "Drawing a triangle."
}
}
``` Make sure your implementation returns a unique string for the `Triangle` class. This exercise will help reinforce how subclasses can extend functionality.
class Shape {
public String draw() {
return "Drawing a shape";
}
}
class Triangle extends Shape {
@Override
public String draw() {
// TODO: Implement this method
return "Drawing a triangle";
}
}
public class Main {
public static void main(String[] args) {
Shape myTriangle = new Triangle();
System.out.println(myTriangle.draw()); // Should output: "Drawing a triangle."
}
}
Main.main(null);
Drawing a triangle
Expanding Your Skills: Adding a Rectangle Class
Next, let’s implement the Rectangle
subclass. Below is the basic setup for it. Your task is to implement the draw
method for the Rectangle
class:
```java class Rectangle extends Shape {
@Override
public String draw() {
// TODO: Implement this method
return "Drawing a rectangle";
} }
public static void main(String[] args) {
Shape myRectangle = new Rectangle();
System.out.println(myRectangle.draw()); // Should output: "Drawing a rectangle."
}
``` Complete the `draw` method in `Rectangle`, ensuring it returns a unique string. This will reinforce how multiple subclasses can have distinct implementations of the same method, enhancing your understanding of class hierarchies.
public class Rectangle extends Shape {
@Override
public String draw() {
// TODO: Implement this method
return "Drawing a rectangle";
}
}
public class Main {
public static void main(String[] args) {
Shape myRectangle = new Rectangle();
System.out.println(myRectangle.draw()); // Should output: "Drawing a rectangle."
}
}
Main.main(null);
Drawing a rectangle
Advanced Challenge: Area Calculation
Now, let’s enhance our Shape
class to include an area calculation feature. Modify the Shape
class to include an area
method, and implement it in your subclasses. Below is a structure to help you get started:
```java class Shape {
public String draw() {
return "Drawing a shape";
}
public double area() {
return 0; // Default implementation
} }
class Circle extends Shape {
@Override
public double area() {
// TODO: Implement area calculation
}
}
class Square extends Shape {
@Override
public double area() {
// TODO: Implement area calculation
}
}
// Implement for Triangle and Rectangle as well
``` Ensure each subclass calculates and returns its area correctly. This will allow you to practice method overriding further and understand how different shapes can extend base functionalities.
public class Shape {
public String draw() {
return "Drawing a shape";
}
public double area() {
return 0;
}
}
class Circle extends Shape {
private int radius;
public Circle(String name, int radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
class Square extends Shape {
private int sideLength;
public Square(String name, int sideLength) {
this.sideLength = sideLength;
}
@Override
public double area() {
return sideLength * sideLength;
}
}
class Rectangle extends Shape {
private int length;
private int width;
public Rectangle(String name, int length, int width) {
this.length = length;
this.width = width;
}
@Override
public double area() {
return length * width;
}
}
class Triangle extends Shape {
private int side1, side2, side3;
public Triangle(String name, int side1, int side2, int side3) {
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
}
@Override
public double area() {
double s = (side1 + side2 + side3) / 2.0; // Semi-perimeter
return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3)); // Heron's formula for area
}
}
public class Main {
public static void main(String[] args) {
Shape myCircle1 = new Circle("Circle", 6);
Shape mySquare1 = new Square("Square", 5);
Shape myRectangle1 = new Rectangle("Rectangle", 6, 5);
Shape myTriangle1 = new Triangle("Triangle", 3, 4, 5);
System.out.println("Area of Circle: " + myCircle1.area());
System.out.println("Area of Square: " + mySquare1.area());
System.out.println("Area of Rectangle: " + myRectangle1.area());
System.out.println("Area of Triangle: " + myTriangle1.area());
}
}
Main.main(null);
Area of Circle: 113.09733552923255
Area of Square: 25.0
Area of Rectangle: 30.0
Area of Triangle: 6.0
Homework Hack
For your homework, create your own class hierarchy for shapes. You should have a base class called Shape
with subclasses Triangle
, Rectangle
, and Hexagon
. Each subclass should implement a method called draw()
, returning a unique string for each shape type.
- `Triangle`: "Drawing a triangle."
- `Rectangle`: "Drawing a rectangle."
- `Hexagon`: "Drawing a hexagon."
Make sure to demonstrate polymorphism by creating an array of Shape
types and iterating through it to call the draw()
method. This will reinforce your understanding of class hierarchies and method overriding.
public class Shape {
public String draw() {
return "Drawing a shape";
}
}
class Triangle extends Shape {
@Override
public String draw() {
return "Drawing a triangle.";
}
}
class Rectangle extends Shape {
@Override
public String draw() {
return "Drawing a rectangle.";
}
}
class Hexagon extends Shape {
@Override
public String draw() {
return "Drawing a hexagon.";
}
}
public class Main {
public static void main(String[] args) {
Shape[] shapes = {
new Triangle(),
new Rectangle(),
new Hexagon()
};
for (Shape shape : shapes) {
System.out.println(shape.draw());
}
}
}
Main.main(null);
Drawing a triangle.
Drawing a rectangle.
Drawing a hexagon.