An immutable object has no changeable observable state after its object constructor finishes. Two clients may hold references to the same immutable object, but neither can change what the other observes. Thus sharing an immutable object avoids the interference normally associated with reference aliasing: for example, sharing a Java
String is safe because a text-changing operation returns another software object rather than modifying that String data type value.A
final reference alone does not establish an immutable object: it prevents reassignment of that reference, but its referent could still be mutable. An immutable object must also protect any reachable mutable state, for instance through defensive copying. With that qualification, the essential answer isFirst determine the width of each column by finding the longest rendered String data type value in it. Independently determine the number of rows as the largest column size. An empty column has width zero. Then traverse rows first and columns second: render a present entry with spaces on the appropriate side, or render an absent entry as an entire column of spaces. Put one separating space between adjacent columns and a newline after each row. In particular, an exhausted column must not shift later entries into its position.
Here is executable Java for the algorithm.
Column, defined in part (e), is an ordinary list of Data. The object interface in part (c) supplies the three queries used below. Each query sees stable content during this call; concurrent changes to formatting are outside this contract.static String replicate(char c, int n) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < n; ++i) s.append(c);
return s.toString();
}
static String printData(List<Column> columns) {
int[] width = new int[columns.size()];
int rows = 0;
for (int c = 0; c < columns.size(); ++c) {
Column column = columns.get(c);
rows = Math.max(rows, column.size());
for (Data d : column)
width[c] = Math.max(width[c], d.length());
}
StringBuilder out = new StringBuilder();
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < columns.size(); ++c) {
if (c != 0) out.append(' ');
Column column = columns.get(c);
if (r >= column.size()) {
out.append(replicate(' ', width[c]));
} else {
Data d = column.get(r);
String text = d.toString();
String pad = replicate(' ', width[c] - text.length());
out.append(d.leftJustified() ? text + pad : pad + text);
}
}
out.append('\n');
}
return out.toString();
}The algorithm uses the stipulated fixed-width characters and assumes entries contain no embedded newlines. If the column widths are and , every emitted row has the same width,An empty collection, or a collection consisting entirely of empty columns, gives the empty String data type value. Spaces at the end of a short last row are harmless and preserve the rectangular layout.
Use an abstract class to share the unchangeable alignment flag and the rule that the reported length equals the rendered text's length. This small class inheritance hierarchy will also serve the numeric software classes in part (d). The Java interface explicitly declares
toString() so that the display contract is visible even though all Java software objects already inherit that object method.interface Data {
boolean leftJustified();
int length();
String toString();
}
abstract class AbstractData implements Data {
private final boolean leftj;
protected AbstractData(boolean leftj) { this.leftj = leftj; }
public final boolean leftJustified() { return leftj; }
public final int length() { return toString().length(); }
public abstract String toString();
}
final class StringData extends AbstractData {
private final String text;
public StringData(String s, boolean leftj) {
super(leftj);
if (s == null) throw new IllegalArgumentException("null text");
text = s;
}
public String toString() { return text; }
}There are no setters, and the stored String data type value is an immutable object, so
StringData is itself an immutable object. Its object constructor rejects a null reference instead of deferring failure until display. Consequently,The UML class diagram separates text shared by every displayable item from numeric formatting shared only by numeric items. A dashed hollow-triangle arrow denotes realization of a Java interface; solid hollow-triangle arrows denote class inheritance. Both kinds of arrow point towards the more general type.
AbstractData owns alignment and computes length from the current rendered text. NumericData adds the minimum digit width. IntegerData and RealData each own their numeric value, while RealData alone owns the minimum number of decimal places. The numeric formatting object methods do not belong on Data: ordinary text has no meaningful setPlaces operation. The design choice isRetain
Data, AbstractData and StringData from part (c). The following are the additional software classes. Formatting denotes the supplied formatting facility with its two static format overloads; its implementation is expressly not required. These calls preserve the distinction between a minimum width and a maximum width: no stored value is truncated.abstract class NumericData extends AbstractData {
private int minDigits = 0;
protected NumericData(boolean leftj) { super(leftj); }
public final void setDigits(int n) { minDigits = Math.max(0, n); }
protected final int digits() { return minDigits; }
}
final class IntegerData extends NumericData {
private final int value;
public IntegerData(int n, boolean leftj) {
super(leftj);
value = n;
}
public String toString() {
return Formatting.format(value, digits());
}
}
final class RealData extends NumericData {
private final double value;
private int minPlaces = 0;
public RealData(double d, boolean leftj) {
super(leftj);
value = d;
}
public void setPlaces(int p) { minPlaces = Math.max(0, p); }
public String toString() {
return Formatting.format(value, digits(), minPlaces);
}
}Zero is the default minimum, and a nonpositive setting removes that minimum. Formatting a negative value, or a value already longer than the minimum, is delegated to the stated formatter contract. The inherited
length() calls the current toString(), so a change to either formatting setting immediately changes the measured column width. For example, after setDigits(3), an IntegerData containing 42 renders 042 and has length 3; after setPlaces(2), a RealData containing 2.7 renders 2.70 and has length at least 4.Unlike
StringData, these numeric software objects are mutable because their displayed state can change. Their numeric values and alignment remain fixed. The useful invariant isUse the weekday number as an array index. Before adding dates, add one empty entry to each weekday before
start; otherwise the first date in those columns would appear a week too early. For day , the slot number is , whose remainder modulo 7 selects the column. The source's example makes clear that start counts empty weekday cells, rather than individual printed space characters.[ref]final class Column extends ArrayList<Data> {
private static final long serialVersionUID = 1L;
}
static String month(int start, int numDays) {
if (start < 0 || start >= 7 || numDays < 1 || numDays > 31)
throw new IllegalArgumentException("invalid month parameters");
String[] days = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
int ncols = days.length;
List<Column> output = new ArrayList<Column>(ncols);
for (int c = 0; c < ncols; ++c) {
Column column = new Column();
column.add(new StringData(days[c], true));
output.add(column);
}
for (int c = 0; c < start; ++c)
output.get(c).add(new StringData("", true));
for (int d = 1; d <= numDays; ++d) {
int c = (start + d - 1) % ncols;
output.get(c).add(new IntegerData(d, false));
}
return printData(output);
}The two-character headers force every column to width two, and right justification aligns one-digit dates with the units digits of two-digit dates. Date appears in data row , below the header. For
month(1,30) the visible result isSu Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 Articles by others on the same topic
There are currently no matching articles.
