Interface for println(String)

Viewed 277

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.)

3 Answers

The easiest way would just to overload the method with a version that accepts a PrintWriter and a version that accepts a PrintStream:

public void sayHello(PrintStream outThingy) {
    outThingy.println("Hello World");
    outThingy.println();
};
public void sayHello(PrintWriter outThingy) {
    outThingy.println("Hello World");
    outThingy.println();
};

Here's a way you could do it and at least keep the client of the outThingy DRY. But, you'll make a trade off for effectively having a couple WET classes. Still, the amount of code is minimal.

// Printer allows for a common interface
interface Printer {
  void println(String line);
  void println();
}

// Used with PrintStream
class StreamPrinter implements Printer {
  private PrintStream ps;

  public StreamPrinter(PrintStream ps) {
    this.ps = ps;
  }

  @Override
  public void println(String line) {
    ps.println(line);
  }

  @Override
  public void println() {
    ps.println();
  }
}

// Used with PrintWriter
class TypeWriter implements Printer {
  private PrintWriter pw;

  public TypeWriter(PrintWriter pw) {
    this.pw = pw;
  }

  @Override
  public void println(String line) {
    pw.println(line);
  }

  @Override
  public void println() {
    pw.println();
  }
}

class Talker {
  // This class doesn't care!
  void sayHello(Printer printer) {
    printer.println("Hello world");
    printer.println();
  }
}

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);
Related