Print Hollow Rectangle in Java
Problem
A hollow rectangle prints stars only along its outer border — the top row, bottom row, and the first and last column of every row in between — leaving the interior blank.
Given a number of rows and columns, print a rectangle outline of stars with a blank interior.
Java Program
public class HollowRectangle {
public static void main(String[] args) {
int rows = 4;
int cols = 6;
for (int i = 1; i <= rows; i++) {
StringBuilder line = new StringBuilder();
for (int j = 1; j <= cols; j++) {
if (i == 1 || i == rows || j == 1 || j == cols) {
line.append("*");
} else {
line.append(" "); // interior stays blank, but still takes up a column
}
}
System.out.println(line);
}
}
}Output
Core Logic
Checking whether each position sits on the rectangle's outer edge — the first or last row, or the first or last column — decides between printing a star or a blank space.
- 1The outer loop runs
ifrom 1 torows, and the inner loop runsjfrom 1 tocols, visiting every position in the grid. - 2A position gets a star when
i == 1,i == rows,j == 1, orj == cols— any of the four border conditions. - 3Every other position appends a plain space instead, keeping each row's total width consistent.
- 4Each row is built into a
StringBuilderbefore being printed as a single line.
rows = 4, cols = 6, the top and bottom rows print solid stars across all 6 columns, while the two middle rows print a star only at column 1 and column 6.Key Point: Printing a space instead of skipping the interior position entirely is what keeps every row the same width — omitting it would misalign the right-hand border star.
Why: The nested loop visits every position in the grid once to decide border-vs-interior, and only the loop counters are kept, not any explicit grid data.
Key Concepts
Approach 2: Java 8
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class HollowRectangleStream {
public static void main(String[] args) {
int rows = 4;
int cols = 6;
IntStream.rangeClosed(1, rows)
.mapToObj(i -> IntStream.rangeClosed(1, cols)
.mapToObj(j -> (i == 1 || i == rows || j == 1 || j == cols) ? "*" : " ")
.collect(Collectors.joining("")))
.forEach(System.out::println);
}
}
Output
Core Logic
Since each position's border-or-interior status depends only on its own row and column, mapping every column index directly to that test reproduces the outline without visiting neighboring cells.
- 1
IntStream.rangeClosed(1, rows)generates one stream element per rowi. - 2For each row, an inner
IntStream.rangeClosed(1, cols)generates that row's column indicesj. - 3
.mapToObj(j -> (i == 1 || i == rows || j == 1 || j == cols) ? "*" : " ")tests the same border condition the loop version does, per position. - 4
Collectors.joining("")concatenates that row's characters with no separator, matching the loop version's plain character append.
j = 1 and j = cols satisfy the border condition, so the joined row is a star, blanks, and a closing star.Key Point: The border test reads only its own i and j, so it maps onto a stream exactly as cleanly as it fits inside a nested loop, whether the grid is square or a general rectangle.
Why: The nested streams still evaluate the border condition once per grid position, the same total work as the loop version, without collecting the full grid.