1. 程式人生 > >在Java中,return null 是否安全, 為什麼?

在Java中,return null 是否安全, 為什麼?

Java程式碼中return value 為null 是不是在任何情況下都可以,為什麼不會throw NullPointerException?

Java語言層面:null值自身是不會引起任何問題的。它安安靜靜的待在某個地方(區域性變數、成員欄位、靜態欄位)不會有任何問題;它從一個地方被搬運到另一個地方也不會有任何問題(變數賦值、返回值等)。唯一會因為null值而引起NullPointerException的動作是“解引用”(dereference)——也就是通過這個引用要對其引用的物件做操作。俗話說就是所有隱含“obj.xxx”的操作中,obj為null值的情況。
在Java裡,下述操作隱含對引用的解引用:

 讀欄位(位元組碼 getfield):x.y,當x為null時拋NPE;
    寫欄位(位元組碼 putfield):x.y = z,當x為null時拋NPE。注意:z的值是什麼沒關係;
    讀陣列長度(位元組碼 arraylength):a.length,當a為null時拋NPE;
    讀陣列元素(位元組碼 <x>aload,<x>為型別字首):a[i],當a為null時拋NPE;
    寫陣列元素(位元組碼 <x>astore,<x>為型別字首):a[i] = x,當a為null時拋NPE。注意:x的值時什麼沒關係;
    呼叫成員方法(位元組碼 invokevirtual、invokeinterface、invokespecial):obj.foo(x, y, z),當obj為null時拋NPE。注意:引數的值是什麼沒關係;
    增強for迴圈(也叫foreach迴圈):
        對陣列時(實際隱含a.length操作):for (E e : a) { ... } , 當a為null時拋NPE;
        對Iterable時(實際隱含對Iterable.iterator()的呼叫):for (E e : es) { ... } ,當es為null時拋NPE;
    自動拆箱(實際隱含 <XXX>.<xxx>Value() 的呼叫,<XXX>為包裝型別名,<xxx>為對應的原始型別名): (int) integerObj,當integerObj為null時拋NPE;
    對String做switch(實際隱含的操作包含對String.hashCode()的呼叫):switch (s) { case "abc": ... } ,當s為null時拋NPE;
    建立內部類物件例項(位元組碼 new,但這裡特指建立內部類例項的情況):outer.new Inner(x, y, z),當outer為null時拋NPE;
    拋異常(位元組碼 athrow):throw obj,當obj(throw表示式的引數)為null時拋NPE;
    用synchronized關鍵字給物件加鎖(位元組碼 monitorenter / monitorexit):synchronized (obj) { ... },當obj為null時拋NPE。

Java語言裡所有其它語法結構都不會因為null值而隱含拋NPE的語義。當然,使用者可以在自己需要的地方顯式檢查null值然後自己丟擲NPE,就像:

java.util.Objects.requireNonNull(Object)

    /**
     * Checks that the specified object reference is not {@code null}. This
     * method is designed primarily for doing parameter validation in methods
     * and constructors, as demonstrated below:
     * <blockquote><pre>
     * public Foo(Bar bar) {
     *     this.bar = Objects.requireNonNull(bar);
     * }
     * </pre></blockquote>
     *
     * @param obj the object reference to check for nullity
     * @param <T> the type of the reference
     * @return {@code obj} if not {@code null}
     * @throws NullPointerException if {@code obj} is {@code null}
     */
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

自己主動throw new NullPointerException()這種情況JVM管不著,使用者程式碼主動指定的,使用者想怎麼搞就怎麼搞。

趣味題:在Java語言裡,只使用Java語言及標準庫的功能而不依賴第三方庫,檢查一個引用obj是否為null並在null時拋NPE的程式碼是什麼?
答案:obj.getClass()。這是因為getClass()是java.lang.Object類上的方法,因而無論什麼引用型別都可以使用。這在Java原始碼層面和在Java位元組碼層面上都是最短的。
當然這是個很邪惡的歪招,然而在OpenJDK的標準庫內部實現中並不少見。大家…還是用Objects.requireNonNull()就好了.

return null主要多了一個麻煩,凡是呼叫它的地方,都要想一想,是不是要判斷if (xxx == null),這樣程式碼不夠優雅。

語言層面上講,返回null沒有任何問題,大家都贊同。
工程實踐中,返回null是否就是個不好的習慣?我倒不這麼認為。我的觀點是,所有的方法呼叫,無論是自己工程的內部類方法還是第三方包中的方法,除非對方在Java Doc中顯式的說明了不會返回空,其它情況都應該懷疑有返回空指標的可能性。多一個判斷並沒有什麼不好,還能大大增加程式碼的健壯性。在另一方面,方法的編寫者也應該仔細的維護Java Doc,如果會返回空指標,那應該說明原因和語義,讓呼叫者有章可依。