"¡Hola, amigo! Hoy vas a aprender cómo hacer algo nuevo: reemplazar el objeto System.out".
System.out es una variable PrintStream estática llamada en la clase System . Esta variable tiene el modificador final , por lo que simplemente le asigna un nuevo valor. Sin embargo, la clase System tiene un método especial para hacer esto: setOut(PrintStream stream) . Y eso es lo que usaremos.

"Interesante. ¿Y con qué lo reemplazaremos?"

"Necesitamos algún objeto que pueda recopilar los datos de salida. ByteArrayOutputStream es el más adecuado para este trabajo. Esta es una clase especial que es una matriz dinámica (redimensionable) e implementa la interfaz OutputStream".

"¿Un adaptador entre una matriz y OutputStream?"

"Algo así. Así es como se ve nuestro código".

Código
public static void main(String[] args) throws Exception
{
 //Save the current PrintStream in a special variable
 PrintStream consoleStream = System.out;

 //Create a dynamic array
 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 //Create an adapter for the PrintStream class
 PrintStream stream = new PrintStream(outputStream);
 //Set it as the current System.out
 System.setOut(stream);

 //Call a function that knows nothing about our changes
 printSomething();

 //Convert the data written to our ByteArray into a string
 String result = outputStream.toString();

 //Put everything back to the way it was
 System.setOut(consoleStream);
}

public static void printSomething()
{
 System.out.println("Hi");
 System.out.println("My name is Amigo");
 System.out.println("Bye-bye!");
}

"¿Y qué haremos con esta línea?"

"Bueno, lo que queramos. Por ejemplo, lo invertiríamos. Entonces el código se vería así:"

Código
public static void main(String[] args) throws Exception
{
 //Save the current PrintStream in a special variable
 PrintStream consoleStream = System.out;

 //Create a dynamic array
 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 //Create an adapter for the PrintStream class
 PrintStream stream = new PrintStream(outputStream);
 //Set it as the current System.out
 System.setOut(stream);

 //Call a function that knows nothing about our changes
 printSomething();

 //Convert the data written to our ByteArray into a string
 String result = outputStream.toString();

 //Put everything back to the way it was
 System.setOut(consoleStream);

 //Reverse the string
 StringBuilder stringBuilder = new StringBuilder(result);
 stringBuilder.reverse();
 String reverseString = stringBuilder.toString();

 //Output it to the console
 System.out.println(reverseString);
}

public static void printSomething()
{
 System.out.println("Hi");
 System.out.println("My name is Amigo");
 System.out.println("Bye-bye!");
}

"¡Qué interesante! Ahora estoy empezando a entender un poco las grandes capacidades que brindan estas clases pequeñas".
"Gracias por la interesante lección, Bilaabo".