"Olá amigo! Hoje você vai aprender a fazer uma coisa nova: substituir o objeto System.out."
System.out é uma variável PrintStream estática chamada na classe System . Essa variável tem o modificador final , então você simplesmente atribui a ela um novo valor. No entanto, a classe System tem um método especial para fazer isso: setOut(PrintStream stream) . E é isso que vamos usar.

"Interessante. E com o que vamos substituí-lo?"

"Precisamos de algum objeto que possa coletar os dados de saída. ByteArrayOutputStream é mais adequado para este trabalho. Esta é uma classe especial que é um array dinâmico (redimensionável) e implementa a interface OutputStream."

"Um adaptador entre uma matriz e OutputStream?"

"Algo assim. Aqui está a aparência do nosso 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!");
}

"E o que faremos com esta linha?"

"Bem, o que quisermos. Por exemplo, nós inverteríamos. Então o código ficaria assim:"

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!");
}

"Que interessante! Agora estou começando a entender um pouco das grandes capacidades que essas classes pequenas oferecem."
"Obrigado pela lição interessante, Bilaabo."