I'd like to write Java code (say, a method) that will print some lines.
The object onto which to print shall be provided by the caller. I'd like my code to not care what exactly that object is and simply call that objects' println()
or println(String)
methods. It should work whether that object is a java.io.PrintStream
(e.g. System.out
) or a java.io.PrintWriter
(e.g. constructed by the caller with new PrintWriter(System.out)
or new PrintWriter(new ByteArrayOutputStream())
).
This would be easy if the potential classes of a "printlineable" object would share some interface that mandated the println()
and println(String)
methods. However they don't.
So what do I put into the signature to receive such an object without violating the DRY principle by writing twice what is essentially the same implementation, just with swapped out types (as I would have to when simply overloading the function)?
public void sayHello( ??? outThingy) {
outThingy.println("Hello World");
outThingy.println();
// This just a minimal example.
// The real implementation might be more involved
// and non-trivial, so that avoiding duplication
// becomes a real concern.
};
// sayHello should be usable like this:
sayHello(System.out);
// but also like this:
baos = new ByteArrayOutputStream();
pw = new PrintWriter(baos)
sayHello(pw);
pw.flush();
System.out.println(baos.toString());
Or should the fact that PrintStream
and PrintWriter
don't share such an interface be treated as indication that they aren't interchangeable in the regard of providing a way to print lines? (Rather than that being some kind of historical oversight back when these classes were specified.)
You might be interested in a different, more functional approach. Instead of worrying about what each type offers and how to find a common interface
between them, you can achieve the same thing with less code by using a Consumer
and a Runnable
as representations of the println
methods.
// This is the common class
class FuncPrinter {
private Consumer<String> consumer;
private Runnable runnable;
public FuncPrinter(PrintWriter writer) {
consumer = writer::println;
runnable = writer::println;
}
public FuncPrinter(PrintStream stream) {
consumer = stream::println;
runnable = stream::println;
}
public void println(String line) {
consumer.accept(line);
}
public void println() {
runnable.run();
}
}
class Talker {
void sayHello(FuncPrinter fp) {
fp.println("Hello World");
fp.println();
}
}
And you could use it like so:
Talker t = new Talker();
FuncPrinter fp = new FuncPrinter(System.out);
t.sayHello(fp);
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
PrintWriter pw = new PrintWriter(ostream);
fp = new FuncPrinter(pw);
t.sayHello(fp);
fp = new FuncPrinter(
line -> System.out.println(line),
() -> System.out.println(42));
t.sayHello(fp);