4

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.

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

16 Comments

String.valueOf is the method you speak of in the last paragraph, IIRC.
@E_net4: I don't know what actually gets called. Objects.toString and String.valueOf do pretty much the same thing.
I would argue 1. That it is pretty much the definitive purpose of .toString() to "convert everything to a string", and 2. That conversely, performing the operation Integer(42) + Integer(null) results in a NullPointerException, rather than performing a silent conversion as happens in String addition
@mintchkin x.toString() means "find the object referenced by x and call its toString method with no arguments". A null reference does not point to any object, so there is no toString method to call.
@mintchkin: What silent coercion should it take on the Integer?
|
2

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.

9 Comments

Thanks for the source code illustration, it's interesting. I suppose I should have expressed that part of my confusion comes from String addition, which doesn't throw a npe, performing so differently from Integer addition, which does.
@mintchkin: Well, you can edit that into your question, and we can edit our answers with explanations of how Integer addition is a consequence of the unboxing logic and how that all works.
Historical context is important here: autoboxing (which enables Integer addition) came in Java 5, whereas String addition as syntactic sugar for StringBuffer operations has been there since day one.
I'd say that it's about the casting in Java in general - there ain't and won't be any direct casting between primitives (attempts fail at compile), while boxing allows you to, e.g., try to cast boolean->Boolean->Integer->int (fails at runtime)
@biziclop That's actually a very interesting point that at least sheds some light for me on why there's an apparent design inconsistency between null String and Integer objects, if not why null String objects were treated the way they are in the first place
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.