Echo in Java and many other languages
Echo
in Java is simple, isn’t it?
But have you ever thought about how many ways there are to implement Echo in Java?
I’ll show you some.
And while I’m at it, I’ll also show you Echo in bash, C, Clojure, Common Lisp, Go, Groovy, JavaScript, Objective-C, Pascal, Perl, PHP, Prolog, Python, Ruby, Scala and Scheme.
While this is mostly for developers who learn Java or another language, I hope that there will be one or two things that are also surprising for experienced developers in Java as well as other languages.
What is Echo
? Requirements?
The echo
command is a command found in almost all CLIs, like UNIX shells (sh
, bash
etc.), Windows cmd.exe
etc. which will simply print all its arguments, concatenated with space, followed by newline.
Here are a few examples
1 christian@armor01:~$ echo
2
3 christian@armor01:~$ echo foo bar
4 foo bar
5 christian@armor01:~$ echo foo bar
6 foo bar
7 christian@armor01:~$ echo "foo bar"
8 foo bar
9 christian@armor01:~$ echo 1 2 3
10 1 2 3
11 christian@armor01:~$ echo "Hello, world!"
12 Hello, world!
So, arguments are concatenated with a single whitespace. The last argument will be printed without a following whitespace. In any case, the last thing printed will be a newline.
Solutions
Java 8
Let’s look at Java 8 first, because it provides the most convenient solution. If you’re just interested in solving the problem nicely today, this is the preferred solution.
1 import static java.lang.String.join;
2 import static java.lang.System.out;
3
4 public class Echo {
5 public static void main(final String... args) {
6 out.println(join(" ", args));
7 }
8 }
or, if you prefer without import static
:
1 public class Echo {
2 public static void main(final String... args) {
3 System.out.println(String.join(" ", args));
4 }
5 }
Iterative Java 1-4
1 public class Echo {
2 public static void main(final String[] args) {
3 for (int i = 0; i < args.length; i++) {
4 if (i > 0) System.out.print(" ");
5 System.out.print(args[i]);
6 }
7 System.out.println();
8 }
9 }
Iterative Java 5-7
import static java.lang.System.out;
public class Echo {
public static void main(final String... args) {
for (final String arg : args) {
if (arg != args[0]) out.print(" ");
out.print(arg);
}
out.println();
}
}
Recursive Java 1-4
1 public class Echo {
2 public static void main(final String[] args) {
3 echo(args, 0);
4 System.out.println();
5 }
6 public static void echo(final String[] args, final int num) {
7 if (num >= args.length) return;
8 if (num > 0) System.out.print(" ");
9 System.out.print(args[num]);
10 echo(args, num + 1);
11 }
12 }
Recursive Java 5-7 using List
1 import static java.lang.System.out;
2 import static java.util.Arrays.asList;
3 import java.util.List;
4
5 public class Echo {
6 public static void main(final String... args) {
7 echo(asList(args));
8 out.println();
9 }
10 public static void echo(final List<String> args) {
11 switch (args.size()) {
12 case 0: return;
13 case 1: out.print(args.get(0)); return;
14 }
15 out.print(args.get(0) + " ");
16 echo(args.subList(1, args.size()));
17 }
18 }
Recursive Java 5-7 using ListIterator
1 import static java.lang.System.out;
2 import static java.util.Arrays.asList;
3 import java.util.ListIterator;
4
5 public class Echo {
6 public static void main(final String... args) {
7 echo(asList(args).listIterator());
8 out.println();
9 }
10 public static void echo(final ListIterator<String> args) {
11 if (!args.hasNext()) return;
12 if (args.hasPrevious()) out.print(" ");
13 out.print(args.next());
14 echo(args);
15 }
16 }
Iterative Java 1-4 with less I/O overhead, w/o explicit StringBuffer
1 public class Echo {
2 public static void main(final String[] args) {
3 String echo = "";
4 for (int i = 0; i < args.length; i++) {
5 if (i > 0) echo += " ";
6 echo += args[i];
7 }
8 System.out.println(echo);
9 }
10 }
Iterative Java 1-4 with less I/O overhead, w/ explicit StringBuffer
1 public class Echo {
2 public static void main(final String[] args) {
3 final StringBuffer echo = new StringBuffer();
4 for (int i = 0; i < args.length; i++) {
5 if (i > 0) echo.append(" ");
6 echo.append(args[i]);
7 }
8 System.out.println(echo);
9 }
10 }
Iterative Java 5-7 with less I/O overhead, w/o explicit StringBuilder
import static java.lang.System.out;
public class Echo {
public static void main(final String... args) {
String echo = "";
for (final String arg : args) {
if (arg != args[0]) echo += " ";
echo += arg;
}
out.println(echo);
}
}
Iterative Java 5-7 with less I/O overhead, w/ explicit StringBuilder
import static java.lang.System.out;
public class Echo {
public static void main(final String... args) {
final StringBuilder echo = new StringBuilder();
for (final String arg : args) {
if (arg != args[0]) echo.append(" ");
echo.append(arg);
}
out.println(echo);
}
}
Recursive Java 1-4 with less I/O overhead
1 public class Echo {
2 public static void main(final String[] args) {
3 System.out.println(echo(args, 0));
4 }
5 public static String echo(final String[] args, final int num) {
6 if (num >= args.length) return "";
7 if (num > 0) return " " + args[num] + echo(args, num + 1);
8 return args[num] + echo(args, num + 1);
9 }
10 }
Recursive Java 5-7 using List
with less I/O overhead
1 import static java.lang.System.out;
2 import static java.util.Arrays.asList;
3 import java.util.List;
4
5 public class Echo {
6 public static void main(final String... args) {
7 out.println(echo(asList(args)));
8 }
9 public static String echo(final List<String> args) {
10 switch (args.size()) {
11 case 0: return "";
12 case 1: return args.get(0);
13 default: return args.get(0) + " " + echo(args.subList(1, args.size()));
14 }
15 }
16 }
Recursive Java 5-7 using ListIterator
with less I/O overhead
1 import static java.lang.System.out;
2 import static java.util.Arrays.asList;
3 import java.util.ListIterator;
4
5 public class Echo {
6 public static void main(final String... args) {
7 out.println(echo(asList(args).listIterator()));
8 }
9 public static String echo(final ListIterator<String> args) {
10 if (!args.hasNext()) return "";
11 return args.next() + (args.hasNext() ? " " + echo(args) : "");
12 }
13 }
For comparison: Echo in other programming languages
bash
1 #!/bin/bash
2 printf "%s\n" "$*"
C
Iterative solution in C without counter
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, char *argv[])
5 {
6 while (*++argv) {
7 printf("%s", *argv);
8 if (argv[1]) printf(" ");
9 }
10 printf("\n");
11 return EXIT_SUCCESS;
12 }
Iterative solution in C with counter
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, char *argv[])
5 {
6 int i;
7 if (argc > 1)
8 printf("%s", argv[1]);
9 for (i = 2; i < argc; i++)
10 printf(" %s", argv[i]);
11 printf("\n");
12 return EXIT_SUCCESS;
13 }
Recursive solution in C
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 void echo(char *argv[])
5 {
6 if (!*argv) return;
7 printf("%s", *argv);
8 if (argv[1]) printf(" ");
9 echo(++argv);
10 }
11
12 int main(int argc, char *argv[])
13 {
14 echo(++argv);
15 printf("\n");
16 return EXIT_SUCCESS;
17 }
Clojure
1 (println (clojure.string/join " " *command-line-args*))
As we can see, the solution in Clojure is very simple.
Common Lisp
1 (format t "~{~A~^ ~}~%" *args*)
Go
1 package main
2 import "fmt"
3 import "os"
4 import "strings"
5 func main() {
6 fmt.Println(strings.Join(os.Args[1:], " "))
7 }
Groovy
1 println args.join(" ")
JavaScript
1 console.log(process.argv.slice(2).join(" "));
Objective-C
Basically the same as C, because no features of Objective-C are required.
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int argc, char *argv[])
5 {
6 while (*++argv) {
7 printf("%s", *argv);
8 if (argv[1]) printf(" ");
9 }
10 printf("\n");
11 return EXIT_SUCCESS;
12 }
Pascal
1 PROGRAM Echo;
2
3 VAR
4 I: Integer;
5 BEGIN
6 for I := 1 to ParamCount - 1 do
7 Write(ParamStr(I), ' ');
8 if (ParamCount > 0) then
9 Write(ParamStr(ParamCount));
10 WriteLn();
11 END.
Perl
1 #!/bin/perl
2
3 print "@ARGV\n"
PHP
1 <?php
2 echo implode(" ", array_slice($argv, 1)), "\n";
3 ?>
Prolog
Behold the beauty of Prolog syntax!
1 main(Argv) :- echo(Argv).
2
3 echo([]) :- nl.
4 echo([Last]) :- write(Last), echo([]).
5 echo([H|T]) :- write(H), write(' '), echo(T).
Prolog has no support for joining strings. Still, the Prolog listing is very nice, it is beautiful. See how pattern matching, list operations and recursion make it very simple and straight forward. For an empty list, it outputs a newline. For a list with only one element, it outputs that element and recurses to the empty list. For any other list, it outputs the head element, a space, and recurses to the tail of the list.
Python
1 import sys
2 print ' '.join(sys.argv[1:])
Ruby
1 printf("%s\n", ARGV.join(' '))
Scala
1 println(args.mkString(" "))
Scheme
1 (display (string-join (list-tail (command-line) 1) " "))
2 (newline)
Conclusions
It’s obvious that there’s more than one way to solve a problem.
Languages which provide a proper join()
function via the API somehow make the life easier.
Of all the languages that do not provide such a join()
function and the writes are performed immediately, I like the solution in Prolog best.
Java 8 is awesome - almost!
What’s really nice about Java 8 is that the solution provided by Java 8 is the cleanest, shortest and fastest at the same time. That’s what functional programming really is about.
However, Java 8 didn’t manage to get rid of boilerplate. As many other programming languages - most notably, because being somewhat designed for the JVM, Clojure, Groovy and Scala - demonstrate, this boilerplate isn’t necessary at all.
Recursion in Java sucks
And that’s for more than just one reason. Recursion works best in languages with the following attributes:
- Pattern matching method signatures
- Tail recursion optimization
- List slices
- More than one return value (return list)
As we can see, Java supports neither of these. And that’s what makes writing recursive programs hard.
Before Java 8, Java provided too many ways how to solve the problem
Although Java 8 added yet one more way how to solve the problem, Java 8 made the situation much better.
Before Java 8, a problem as simple as echo
already turned into a trade-off between readability and performance.
Clearly, for echo
, performance wouldn’t be an issue anyway.
I’m just saying, as there might be different, bigger problems.
TODO:2015-08-02:christian:3: Solutions in C++, C#, Assembly x86, Assembly x64, Assembly ARM, COBOL, Fortran, D, Scratch, Lua, F#, Erlang, Haskell, Awk, bash, Eiffel, Modula-2, OCaml, Oberon, ML, SmallTalk, TcL, R, Dart, Ada, Logo, Whitespace, Brainfuck, Befunge, LOLCODE, Malbolge, Cluster, Kotlin