Sunday, May 10, 2009

Tricky use of static initializer in Java - Override println


Can we get a different output without changing the main-method definition?


public class HelloMain {

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Mr. main.");
}

}


Can we have the output of the above code-segment as "Hi, Mr. main. Bye!" without changing the main-method code?

Yeah, we can have the required output by using 'static initializer' blocks effectively. As
we know that a static initializer block is called when a class is loaded into memory and hence it'll obviously run before the main method. Let's see how this can be used here.

The required output can be broken into three pieces - "Hi, ", "Mr. main.", and " Bye!".
These parts should get printed in this order only which means the string "Hi, " should be printed before the 'println' call inside the main method prints "Mr. main." and subsequently the last string " Bye!" should be printed.

As we know that 'println' method by default ends with a new line and hence to have the last
part (" Bye!") being printed in continuation with the main-method println string, we probably have only two ways:-
  1. Re-define the default 'println' behavior - we can create an anonymous subclass (as we would not be requiring that anywhere else) of the PrintStream class and then re-define the 'println' method as per our needs.
  2. Changing the default line separator - we can use the third part " Bye!" as the new line separator (it can be passed as a command-line argument while calling the class) in which case the 'println' of the 'main' would print this as the line separator and we will end up getting the required output.
Solution #1: Overriding 'println' in a static initializer block in Java


import java.io.PrintStream;

public class HelloMain
{

static
{
// as we are using System.out as the output stream in main
final PrintStream currentOut = System.out;

// anonymous as we would need this sub class here only
PrintStream newOut = new PrintStream(currentOut)
{
// Overriding 'println' method
public void println(String string)
{

// Printing Part - 1 first
print("Hi, ");

//Printing Part -2
print(string);

// Printing Part - 3, but this should use original 'println' def
// and hence the usage of 'super' comes here.
super.println(" Bye!");
}
};

// Now we are ready with the modified PrintStream and hence setting that

System.setOut(newOut);

}

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Mr. main.");
}
}


Output:
below is the screenshot displaying the output of Solution #1



Solution #2:
using an appropriate 'line separator' with a static initializer block



public class HelloMain {

static
{
//Printing Part - 1
System.out.print("Hi, ");
}

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Mr. main."); //Printing Part - 2
}
}


Part - 3 is printed by changing the default line separator, which is done by setting the string " Bye!" as the new line separator using command line arguments.


java -Dline.separator=" Bye!" HelloMain


Output:
below is the screenshot displaying the output of Solution #2



Liked the article? Subscribe to this blog for regular updates. Wanna follow it to tell the world that you enjoy GeekExplains? Please find the 'Followers' widget in the rightmost sidebar.



Share/Save/Bookmark


2 comments:

Vivek Athalye said...

Hi...

In solution #2, u r relying on user to set proper line.separator value (" Bye!").

Instead, why not set it through code using System.setProperty()??

Only thing is, we need to reset the output stream in System class, similar to what you have shown in solution #1.

Here is the code:
import java.io.*;

public class HelloMain {

static
{

String oldLineSep = System.getProperty("line.separator");
System.setProperty("line.separator", " Bye!!!");
// OR
// System.setProperty("line.separator", " Bye!!!" + oldLineSep); // this allows me to keep \n in the line.separator


PrintStream newOut = new PrintStream(System.out)
{ };
System.setOut(newOut);


System.out.print("Hi, ");
}

/**
* @param args
*/
public static void main(String[] args) {
System.out.println("Mr. main."); //Printing Part - 2
}
}

what say?

btw, is there any way to have \n (newline) added in the line.separator property, when u r defining it on command line? (-Dline.separator=" Bye!\n" doesn't work)

Geek said...

In the second option my emphasis was on using the 'line.separator' property and not really on how do we actually implement it. As you have said, we can of course set that programmatically instead of relying on a command line argument.

Regarding your question of whether we can use '\n' embedded in the value passed to a command line argument, the answer is NO. In fact, it was logged as a bug on Sun's Bug Database, but it was closed as 'not a bug' citing the reason that interpretation of command line text is a shell dependent stuff and hence it's not reasonable to expect it to work in line with escape sequence handling capabilities of any particular programming language. Read more about it here.

Thank you for your participation. You raised a very good point. Keep visiting/posting!