mintchkin
mintchkin

Reputation: 1654

Why the coercion of null String to "null" String

While reading this question, I remembered a bug in a program I wrote while I was first learning java that took me forever to locate and essentially boiled down to the following behavior:

String s1 = "This was a triumph";
String n1 = null;
System.out.println(s1 + n); // prints "This was a triumphnull"

Some other notable examples (and perplexing counter-examples) of similar behavior:

// "nullThis was a triumph", coercion happens commutatively
System.out.println(n1 + s1);

// as per above question, println explicitly converts null Strings to "null"
System.out.println(n1);

// similar result
System.out.println(String.valueOf(n1));

// NullPointerException (!!); null not silently converted to "null"
// note that this is the kind of thing I expected to occur for the other examples
// when I wrote the buggy code in the first place
System.out.println(n1.toString());

While I suppose I technically understand this behavior, I definitely don't grok it, so my question is:

  1. From a language design standpoint, why does java convert a null String to a "null" String in so many cases?
  2. Why doesn't it do so in the cases in which...it doesn't do so?

EDIT

I appreciate the answers so far, but I just want to clarify that my confusion largely originates from how null Strings are treated so differently in this respect than other null Objects. For example:

Integer i1 = 42;
Integer n2 = null;
// Both produce NullPointerExceptions:
Integer.valueOf(n2);
System.out.println(i1 + n2);

I also want to reemphasize that a NullPointerException is the kind of behavior I expected, which is why I was so confused about the null/"null" String conversion in the first place.

Upvotes: 4

Views: 1035

Answers (2)

user719662
user719662

Reputation:

1. It was a design choice, dictated by the fact that String & StringBuffer class is supported since JDK1.0 (see http://docs.oracle.com/javase/6/docs/api/java/lang/String.html etc), that is since January 21, 1996 - and it supports concatenation using + operator since then. In J2SE 5.0 (from September 30, 2004), also known as JDK 1.5, both StringBuilder (non-thread safe, but faster - + started using it instead of StringBuffer now), autoboxing and generics (with erasure) were added - the whole paradigm shifted from metaprogramming using reflection to template programming, with not-so-obvious casting solutions. (see e.g. https://codegolf.stackexchange.com/questions/28786/write-a-program-that-makes-2-2-5 - Java solution uses Integer cache poisoning & boxing/unboxing to achieve this not-so-surprising result)

String.java

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

Objects.java

public static String toString(Object o) {
    return String.valueOf(o);
}

PrintStream.java

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
} 

StringBuilder.java

public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}

etc. (Java src.zip, JDK v8)

Virtually all Java library methods dealing with printing and toString conversion handle null this way (ie. call String.valueOf(), thus converting it to literal "null" String). Also, note that concat by + also works this way, because it translates to library calls during compilation (calling StringBuilder.append(Object o)).

2. Explicitly calling #toString() on a null Object reference will still cause a NPEx, as trying to call any other method on it.

3. (yeah, I know there was no part 3) for a fun time, try executing, eg.

System.out.println( ((String)null).toString() );

(obviously fails), and then

System.out.println( ((String)null).valueOf((String)null).toString() ); // works?! why!?

spoiler:

static methods.

Upvotes: 2

user2357112
user2357112

Reputation: 280867

Only objects have methods. null is not an object; it has no methods. Any attempt to call methods of null will raise a NullPointerException. This is somewhat similar to what you'd get if you tried to call 3.toString(), though it's a runtime error instead of compile-time.

The examples you've given where null converts to "null" all special-case null references in an attempt to provide a more friendly interface. They can do this because they aren't method calls on null; they can have extra handling like x == null? "null" : x.toString() built in. This is similar to why you can call System.out.println(3) when 3.toString() fails.


The handling of null Integers works by autoboxing, a completely unrelated mechanism to that which handles null in printing and string concatenation.

When an Integer (or one of the other 7 primitive wrapper types) appears in a context that requires a primitive int, the compiler automatically inserts an Integer.valueOf call to perform the conversion. Integer.valueOf has no safe default for nulls; while "null" is the obvious string form of null and is quite useful for debugging output, coercing null Integers to 0 or any other value would be less useful and much more bug-prone.

Upvotes: 3

Related Questions