In this article, we will go through very simple code to print tabular format to console using simple pure java code.
Steps to print table:
- Simple table:
- Table data – Take table data in 2 dimensional array format. Each sub-array is a row. First sub-array will be header row.
- Column Lengths – For each column, check length of all strings in that column. Take max string length in that column as column width for that column.
- Format string – Prepare format string for printf method using column widths calculated in earlier step. Refer Formatter Documentation
- Left Justify – This format will also consider a left-justify boolean flag to prepare format string to left justify each row.
- Print Table – Iterate through array & print table row by row using System.out.printf with format prepared in earlier step.
- Additional features
- Beautify with horizontal lines – We will also add line at top, bottom & below header line to make it look more reader friendly.
- Cell wrapping – If certain cell value is more than certain fixed max width, then wrap the data of that cell to next line.
Simple table
This is a simple code to print table with all step above except horizontal lines & cell wrapping. This table has feature to auto adjust width as per the data.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public static void simpleTable() { /* * leftJustifiedRows - If true, it will add "-" as a flag to format string to * make it left justified. Otherwise right justified. */ boolean leftJustifiedRows = false; /* * Table to print in console in 2-dimensional array. Each sub-array is a row. */ String[][] table = new String[][] { { "id", "First Name", "Last Name", "Age" }, { "1", "John", "Johnson", "45" }, { "2", "Tom", "", "35" }, { "3", "Rose", "Johnson", "22" }, { "4", "Jimmy", "Kimmel", "" } }; /* * Calculate appropriate Length of each column by looking at width of data in * each column. * * Map columnLengths is <column_number, column_length> */ Map<Integer, Integer> columnLengths = new HashMap<>(); Arrays.stream(table).forEach(a -> Stream.iterate(0, (i -> i < a.length), (i -> ++i)).forEach(i -> { if (columnLengths.get(i) == null) { columnLengths.put(i, 0); } if (columnLengths.get(i) < a[i].length()) { columnLengths.put(i, a[i].length()); } })); System.out.println("columnLengths = " + columnLengths); /* * Prepare format String */ final StringBuilder formatString = new StringBuilder(""); String flag = leftJustifiedRows ? "-" : ""; columnLengths.entrySet().stream().forEach(e -> formatString.append("| %" + flag + e.getValue() + "s ")); formatString.append("|\n"); System.out.println("formatString = " + formatString.toString()); /* * Print table */ Stream.iterate(0, (i -> i < table.length), (i -> ++i)) .forEach(a -> System.out.printf(formatString.toString(), table[a])); } |
Output
1 2 3 4 5 6 7 8 |
columnLengths = {0=2, 1=10, 2=9, 3=3} formatString = | %2s | %10s | %9s | %3s | | id | First Name | Last Name | Age | | 1 | John | Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy | Kimmel | | |
With leftJustifiedRows = true
1 2 3 4 5 6 7 8 |
columnLengths = {0=2, 1=10, 2=9, 3=3} formatString = | %-2s | %-10s | %-9s | %-3s | | id | First Name | Last Name | Age | | 1 | John | Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy | Kimmel | | |
With longer width data. Column widths will adjust as per data.
1 2 3 |
String[][] table = new String[][] { { "id", "First Name", "Last Name", "Age" }, { "1", "John", "Johnson-Johnson-Johnson-Johnson", "45" }, { "2", "Tom", "", "35" }, { "3", "Rose", "Johnson", "22" }, { "4", "Jimmy-Jimmy-Jimmy", "Kimmel", "" } }; |
1 2 3 4 5 6 7 8 |
columnLengths = {0=2, 1=17, 2=31, 3=3} formatString = | %-2s | %-17s | %-31s | %-3s | | id | First Name | Last Name | Age | | 1 | John | Johnson-Johnson-Johnson-Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy-Jimmy-Jimmy | Kimmel | | |
Table with horizontal lines
Now we will modify above code to include horizontal lines at top & bottom of table & also below header line so that header appears separately.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
public static void tableWithLines() { /* * leftJustifiedRows - If true, it will add "-" as a flag to format string to * make it left justified. Otherwise right justified. */ boolean leftJustifiedRows = false; /* * Table to print in console in 2-dimensional array. Each sub-array is a row. */ String[][] table = new String[][] { { "id", "First Name", "Last Name", "Age" }, { "1", "John", "Johnson", "45" }, { "2", "Tom", "", "35" }, { "3", "Rose", "Johnson", "22" }, { "4", "Jimmy", "Kimmel", "" } }; /* * Calculate appropriate Length of each column by looking at width of data in * each column. * * Map columnLengths is <column_number, column_length> */ Map<Integer, Integer> columnLengths = new HashMap<>(); Arrays.stream(table).forEach(a -> Stream.iterate(0, (i -> i < a.length), (i -> ++i)).forEach(i -> { if (columnLengths.get(i) == null) { columnLengths.put(i, 0); } if (columnLengths.get(i) < a[i].length()) { columnLengths.put(i, a[i].length()); } })); System.out.println("columnLengths = " + columnLengths); /* * Prepare format String */ final StringBuilder formatString = new StringBuilder(""); String flag = leftJustifiedRows ? "-" : ""; columnLengths.entrySet().stream().forEach(e -> formatString.append("| %" + flag + e.getValue() + "s ")); formatString.append("|\n"); System.out.println("formatString = " + formatString.toString()); /* * Prepare line for top, bottom & below header row. */ String line = columnLengths.entrySet().stream().reduce("", (ln, b) -> { String templn = "+-"; templn = templn + Stream.iterate(0, (i -> i < b.getValue()), (i -> ++i)).reduce("", (ln1, b1) -> ln1 + "-", (a1, b1) -> a1 + b1); templn = templn + "-"; return ln + templn; }, (a, b) -> a + b); line = line + "+\n"; System.out.println("Line = " + line); /* * Print table */ System.out.print(line); Arrays.stream(table).limit(1).forEach(a -> System.out.printf(formatString.toString(), a)); System.out.print(line); Stream.iterate(1, (i -> i < table.length), (i -> ++i)) .forEach(a -> System.out.printf(formatString.toString(), table[a])); System.out.print(line); } |
Output
1 2 3 4 5 6 7 8 9 10 11 12 13 |
columnLengths = {0=2, 1=10, 2=9, 3=3} formatString = | %2s | %10s | %9s | %3s | Line = +----+------------+-----------+-----+ +----+------------+-----------+-----+ | id | First Name | Last Name | Age | +----+------------+-----------+-----+ | 1 | John | Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy | Kimmel | | +----+------------+-----------+-----+ |
With leftJustifiedRows = true
1 2 3 4 5 6 7 8 9 10 11 12 13 |
columnLengths = {0=2, 1=10, 2=9, 3=3} formatString = | %-2s | %-10s | %-9s | %-3s | Line = +----+------------+-----------+-----+ +----+------------+-----------+-----+ | id | First Name | Last Name | Age | +----+------------+-----------+-----+ | 1 | John | Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy | Kimmel | | +----+------------+-----------+-----+ |
With longer width data. Column widths will adjust as per data.
1 2 3 |
String[][] table = new String[][] { { "id", "First Name", "Last Name", "Age" }, { "1", "John", "Johnson-Johnson-Johnson-Johnson", "45" }, { "2", "Tom", "", "35" }, { "3", "Rose", "Johnson", "22" }, { "4", "Jimmy-Jimmy-Jimmy", "Kimmel", "" } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
columnLengths = {0=2, 1=17, 2=31, 3=3} formatString = | %-2s | %-17s | %-31s | %-3s | Line = +----+-------------------+---------------------------------+-----+ +----+-------------------+---------------------------------+-----+ | id | First Name | Last Name | Age | +----+-------------------+---------------------------------+-----+ | 1 | John | Johnson-Johnson-Johnson-Johnson | 45 | | 2 | Tom | | 35 | | 3 | Rose | Johnson | 22 | | 4 | Jimmy-Jimmy-Jimmy | Kimmel | | +----+-------------------+---------------------------------+-----+ |
Table with horizontal lines & Cell data wrapping
Now we will add feature for wrapping data in a table cell. We will define a ‘max width’ for cell. If any cell data is more than defined ‘max width’ then that data will be cropped & put to next line.
This is bit tricky as it takes certain manipulation of original table data i.e. 2-dimensional array. We will iterate through original table array & if we find any data more than ‘max width’, then we will add extra row in array where we will put cropped data after ‘max width’. This way we will prepare a new 2-dimensional array with final wrapped data & use that as a input to the table. Here is the code.
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
public static void tableWithLinesAndMaxWidth() { /* * leftJustifiedRows - If true, it will add "-" as a flag to format string to * make it left justified. Otherwise right justified. */ boolean leftJustifiedRows = true; /* * Maximum allowed width. Line will be wrapped beyond this width. */ int maxWidth = 30; /* * Table to print in console in 2-dimensional array. Each sub-array is a row. */ String[][] table = new String[][] { { "id", "First Name", "Last Name", "Age", "Profile" }, { "1", "John", "Johnson", "45", "My name is John Johnson. My id is 1. My age is 45." }, { "2", "Tom", "", "35", "My name is Tom. My id is 2. My age is 35." }, { "3", "Rose", "Johnson Johnson Johnson Johnson Johnson Johnson Johnson Johnson Johnson Johnson", "22", "My name is Rose Johnson. My id is 3. My age is 22." }, { "4", "Jimmy", "Kimmel", "", "My name is Jimmy Kimmel. My id is 4. My age is not specified. " + "I am the host of the late night show. I am not fan of Matt Damon. " } }; /* * Create new table array with wrapped rows */ List<String[]> tableList = new ArrayList<>(Arrays.asList(table)); List<String[]> finalTableList = new ArrayList<>(); for (String[] row : tableList) { // If any cell data is more than max width, then it will need extra row. boolean needExtraRow = false; // Count of extra split row. int splitRow = 0; do { needExtraRow = false; String[] newRow = new String[row.length]; for (int i = 0; i < row.length; i++) { // If data is less than max width, use that as it is. if (row[i].length() < maxWidth) { newRow[i] = splitRow == 0 ? row[i] : ""; } else if ((row[i].length() > (splitRow * maxWidth))) { // If data is more than max width, then crop data at maxwidth. // Remaining cropped data will be part of next row. int end = row[i].length() > ((splitRow * maxWidth) + maxWidth) ? (splitRow * maxWidth) + maxWidth : row[i].length(); newRow[i] = row[i].substring((splitRow * maxWidth), end); needExtraRow = true; } else { newRow[i] = ""; } } finalTableList.add(newRow); if (needExtraRow) { splitRow++; } } while (needExtraRow); } String[][] finalTable = new String[finalTableList.size()][finalTableList.get(0).length]; for (int i = 0; i < finalTable.length; i++) { finalTable[i] = finalTableList.get(i); } /* * Calculate appropriate Length of each column by looking at width of data in * each column. * * Map columnLengths is <column_number, column_length> */ Map<Integer, Integer> columnLengths = new HashMap<>(); Arrays.stream(finalTable).forEach(a -> Stream.iterate(0, (i -> i < a.length), (i -> ++i)).forEach(i -> { if (columnLengths.get(i) == null) { columnLengths.put(i, 0); } if (columnLengths.get(i) < a[i].length()) { columnLengths.put(i, a[i].length()); } })); System.out.println("columnLengths = " + columnLengths); /* * Prepare format String */ final StringBuilder formatString = new StringBuilder(""); String flag = leftJustifiedRows ? "-" : ""; columnLengths.entrySet().stream().forEach(e -> formatString.append("| %" + flag + e.getValue() + "s ")); formatString.append("|\n"); System.out.println("formatString = " + formatString.toString()); /* * Prepare line for top, bottom & below header row. */ String line = columnLengths.entrySet().stream().reduce("", (ln, b) -> { String templn = "+-"; templn = templn + Stream.iterate(0, (i -> i < b.getValue()), (i -> ++i)).reduce("", (ln1, b1) -> ln1 + "-", (a1, b1) -> a1 + b1); templn = templn + "-"; return ln + templn; }, (a, b) -> a + b); line = line + "+\n"; System.out.println("Line = " + line); /* * Print table */ System.out.print(line); Arrays.stream(finalTable).limit(1).forEach(a -> System.out.printf(formatString.toString(), a)); System.out.print(line); Stream.iterate(1, (i -> i < finalTable.length), (i -> ++i)) .forEach(a -> System.out.printf(formatString.toString(), finalTable[a])); System.out.print(line); } |
Output
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 |
columnLengths = {0=2, 1=10, 2=30, 3=3, 4=30} formatString = | %-2s | %-10s | %-30s | %-3s | %-30s | Line = +----+------------+--------------------------------+-----+--------------------------------+ +----+------------+--------------------------------+-----+--------------------------------+ | id | First Name | Last Name | Age | Profile | +----+------------+--------------------------------+-----+--------------------------------+ | 1 | John | Johnson | 45 | My name is John Johnson. My id | | | | | | is 1. My age is 45. | | | | | | | | 2 | Tom | | 35 | My name is Tom. My id is 2. My | | | | | | age is 35. | | | | | | | | 3 | Rose | Johnson Johnson Johnson Johnso | 22 | My name is Rose Johnson. My id | | | | n Johnson Johnson Johnson John | | is 3. My age is 22. | | | | son Johnson Johnson | | | | | | | | | | 4 | Jimmy | Kimmel | | My name is Jimmy Kimmel. My id | | | | | | is 4. My age is not specified | | | | | | . I am the host of the late ni | | | | | | ght show. I am not fan of Matt | | | | | | Damon. | | | | | | | +----+------------+--------------------------------+-----+--------------------------------+ |
The method iterate(T, UnaryOperator) in the type Stream is not applicable for the arguments (int, (( i) -> {}), (( i) -> {}))
I am using Java 8 .. Getting this error on Stream.Iterator can you please help, fixing this
Iterate method with 3 arguments was introduced in java 9. Below is api
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#iterate(T,java.util.function.Predicate,java.util.function.UnaryOperator)
For jdk 8 or less, just replace
static Stream iterate(T seed, Predicate super T> hasNext, UnaryOperator next)
with simple for loop
for (T index=seed; hasNext.test(index); index = next.apply(index)) { … }
Both have same effect. Hope this helps.